File Coverage

blib/lib/FusionInventory/Agent/Tools/Hardware.pm
Criterion Covered Total %
statement 262 653 40.1
branch 107 404 26.4
condition 22 84 26.1
subroutine 22 41 53.6
pod 2 2 100.0
total 415 1184 35.0


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Tools::Hardware;
2              
3 2     2   6493418 use strict;
  2         8  
  2         79  
4 2     2   8 use warnings;
  2         3  
  2         111  
5 2     2   9 use base 'Exporter';
  2         56  
  2         276  
6              
7 2     2   15 use English qw(-no_match_vars);
  2         3  
  2         28  
8              
9 2     2   1722 use FusionInventory::Agent::Tools;
  2         3  
  2         226  
10 2     2   696 use FusionInventory::Agent::Tools::Network;
  2         2  
  2         10999  
11              
12             our @EXPORT = qw(
13             getDeviceInfo
14             getDeviceFullInfo
15             );
16              
17             my %types = (
18             1 => 'COMPUTER',
19             2 => 'NETWORKING',
20             3 => 'PRINTER',
21             4 => 'STORAGE',
22             5 => 'POWER',
23             6 => 'PHONE',
24             7 => 'VIDEO',
25             8 => 'KVM',
26             );
27              
28             my %sysobjectid;
29              
30             my %sysdescr_first_word = (
31             '3com' => { vendor => '3Com', type => 'NETWORKING' },
32             'alcatel-lucent' => { vendor => 'Alcatel-Lucent', type => 'NETWORKING' },
33             'allied' => { vendor => 'Allied', type => 'NETWORKING' },
34             'alteon' => { vendor => 'Alteon', type => 'NETWORKING' },
35             'apc' => { vendor => 'APC', type => 'NETWORKING' },
36             'apple' => { vendor => 'Apple', },
37             'avaya' => { vendor => 'Avaya', type => 'NETWORKING' },
38             'axis' => { vendor => 'Axis', type => 'NETWORKING' },
39             'baystack' => { vendor => 'Nortel', type => 'NETWORKING' },
40             'broadband' => { vendor => 'Broadband', type => 'NETWORKING' },
41             'brocade' => { vendor => 'Brocade', type => 'NETWORKING' },
42             'brother' => { vendor => 'Brother', type => 'PRINTER' },
43             'canon' => { vendor => 'Canon', type => 'PRINTER' },
44             'cisco' => { vendor => 'Cisco', type => 'NETWORKING' },
45             'dell' => { vendor => 'Dell', },
46             'designjet' => { vendor => 'Hewlett-Packard', type => 'PRINTER' },
47             'deskjet' => { vendor => 'Hewlett-Packard', type => 'PRINTER' },
48             'd-link' => { vendor => 'D-Link', type => 'NETWORKING' },
49             'eaton' => { vendor => 'Eaton', type => 'NETWORKING' },
50             'emc' => { vendor => 'EMC', type => 'STORAGE' },
51             'enterasys' => { vendor => 'Enterasys', type => 'NETWORKING' },
52             'epson' => { vendor => 'Epson', type => 'PRINTER' },
53             'extreme' => { vendor => 'Extreme', type => 'NETWORKING' },
54             'extremexos' => { vendor => 'Extreme', type => 'NETWORKING' },
55             'force10' => { vendor => 'Force10', type => 'NETWORKING' },
56             'foundry' => { vendor => 'Foundry', type => 'NETWORKING' },
57             'fuji' => { vendor => 'Fuji', type => 'NETWORKING' },
58             'h3c' => { vendor => 'H3C', type => 'NETWORKING' },
59             'hp' => { vendor => 'Hewlett-Packard', },
60             'ibm' => { vendor => 'IBM', type => 'COMPUTER' },
61             'juniper' => { vendor => 'Juniper', type => 'NETWORKING' },
62             'konica' => { vendor => 'Konica', type => 'PRINTER' },
63             'kyocera' => { vendor => 'Kyocera', type => 'PRINTER' },
64             'lexmark' => { vendor => 'Lexmark', type => 'PRINTER' },
65             'netapp' => { vendor => 'NetApp', type => 'STORAGE' },
66             'netgear' => { vendor => 'NetGear', type => 'NETWORKING' },
67             'nortel' => { vendor => 'Nortel', type => 'NETWORKING' },
68             'nrg' => { vendor => 'NRG', type => 'PRINTER' },
69             'officejet' => { vendor => 'Hewlett-Packard', type => 'PRINTER' },
70             'oki' => { vendor => 'OKI', type => 'PRINTER' },
71             'powerconnect' => { vendor => 'PowerConnect', type => 'NETWORKING' },
72             'procurve' => { vendor => 'Hewlett-Packard', type => 'NETWORKING' },
73             'ricoh' => { vendor => 'Ricoh', type => 'PRINTER' },
74             'sagem' => { vendor => 'Sagem', type => 'NETWORKING' },
75             'samsung' => { vendor => 'Samsung', type => 'PRINTER' },
76             'sharp' => { vendor => 'Sharp', type => 'PRINTER' },
77             'toshiba' => { vendor => 'Toshiba', type => 'PRINTER' },
78             'wyse' => { vendor => 'Wyse', type => 'COMPUTER' },
79             'xerox' => { vendor => 'Xerox', type => 'PRINTER' },
80             'xirrus' => { vendor => 'Xirrus', type => 'NETWORKING' },
81             'zebranet' => { vendor => 'Zebranet', type => 'PRINTER' },
82             'ztc' => { vendor => 'ZTC', type => 'NETWORKING' },
83             'zywall' => { vendor => 'ZyWall', type => 'NETWORKING' }
84             );
85              
86             my @sysdescr_rules = (
87             {
88             match => qr/Switch/,
89             type => 'NETWORKING',
90             },
91             {
92             match => qr/JETDIRECT/,
93             type => 'PRINTER',
94             },
95             {
96             match => qr/Linux TS-\d+/,
97             type => 'STORAGE',
98             vendor => 'Qnap'
99             },
100             );
101              
102             # common base variables
103             my %base_variables = (
104             CPU => {
105             oid => '.1.3.6.1.4.1.9.9.109.1.1.1.1.3.1',
106             type => 'count',
107             },
108             SNMPHOSTNAME => {
109             oid => '.1.3.6.1.2.1.1.5.0',
110             type => 'string',
111             },
112             LOCATION => {
113             oid => '.1.3.6.1.2.1.1.6.0',
114             type => 'string',
115             },
116             CONTACT => {
117             oid => '.1.3.6.1.2.1.1.4.0',
118             type => 'string',
119             },
120             UPTIME => {
121             oid => '.1.3.6.1.2.1.1.3.0',
122             type => 'string',
123             },
124             MEMORY => {
125             oid => [
126             '.1.3.6.1.4.1.9.2.1.8.0',
127             '.1.3.6.1.2.1.25.2.3.1.5.1',
128             ],
129             type => 'memory',
130             },
131             RAM => {
132             oid => '.1.3.6.1.4.1.9.3.6.6.0',
133             type => 'memory',
134             },
135             );
136              
137             # common interface variables
138             my %interface_variables = (
139             IFNUMBER => {
140             oid => '.1.3.6.1.2.1.2.2.1.1',
141             type => 'none'
142             },
143             IFDESCR => {
144             oid => '.1.3.6.1.2.1.2.2.1.2',
145             type => 'string',
146             },
147             IFNAME => {
148             oid => [
149             '.1.3.6.1.2.1.31.1.1.1.1',
150             '.1.3.6.1.2.1.2.2.1.2',
151             ],
152             type => 'string',
153             },
154             IFTYPE => {
155             oid => '.1.3.6.1.2.1.2.2.1.3',
156             type => 'constant',
157             },
158             IFMTU => {
159             oid => '.1.3.6.1.2.1.2.2.1.4',
160             type => 'count',
161             },
162             IFSTATUS => {
163             oid => '.1.3.6.1.2.1.2.2.1.8',
164             type => 'constant',
165             },
166             IFINTERNALSTATUS => {
167             oid => '.1.3.6.1.2.1.2.2.1.7',
168             type => 'constant',
169             },
170             IFLASTCHANGE => {
171             oid => '.1.3.6.1.2.1.2.2.1.9',
172             type => 'none'
173             },
174             IFINOCTETS => {
175             oid => '.1.3.6.1.2.1.2.2.1.10',
176             type => 'count',
177             },
178             IFOUTOCTETS => {
179             oid => '.1.3.6.1.2.1.2.2.1.16',
180             type => 'count',
181             },
182             IFINERRORS => {
183             oid => '.1.3.6.1.2.1.2.2.1.14',
184             type => 'count',
185             },
186             IFOUTERRORS => {
187             oid => '.1.3.6.1.2.1.2.2.1.20',
188             type => 'count',
189             },
190             MAC => {
191             oid => '.1.3.6.1.2.1.2.2.1.6',
192             type => 'mac',
193             },
194             IFPORTDUPLEX => {
195             oid => '.1.3.6.1.2.1.10.7.2.1.19',
196             type => 'constant',
197             },
198             IFALIAS => {
199             oid => '.1.3.6.1.2.1.31.1.1.1.18',
200             type => 'string',
201             },
202             );
203              
204             my %consumable_types = (
205             3 => 'TONER',
206             4 => 'WASTETONER',
207             5 => 'CARTRIDGE',
208             6 => 'CARTRIDGE',
209             8 => 'WASTETONER',
210             9 => 'DRUM',
211             10 => 'DEVELOPER',
212             12 => 'CARTRIDGE',
213             15 => 'FUSERKIT',
214             18 => 'MAINTENANCEKIT',
215             20 => 'TRANSFERKIT',
216             21 => 'TONER',
217             32 => 'STAPLES',
218             );
219              
220             # printer-specific page counter variables
221             my %printer_pagecounters_variables = (
222             TOTAL => {
223             oid => [
224             '.1.3.6.1.4.1.1347.42.2.1.1.1.6.1.1', #Kyocera specific counter
225             '.1.3.6.1.2.1.43.10.2.1.4.1.1' #Default Value
226             ]
227             },
228             BLACK => {
229             oid => '.1.3.6.1.4.1.1347.42.2.1.1.1.7.1.1' #Kyocera specific counter
230             },
231             COLOR => {
232             oid => '.1.3.6.1.4.1.1347.42.2.1.1.1.8.1.1' #Kyocera specific counter
233             },
234             RECTOVERSO => { },
235             SCANNED => {
236             oid => '.1.3.6.1.4.1.1347.46.10.1.1.5.3' #Kyocera specific counter ( total scan counter)
237             },
238             PRINTTOTAL => {
239             oid => '.1.3.6.1.4.1.1347.42.3.1.1.1.1.2' #Kyocera specific counter
240             },
241             PRINTBLACK => {
242             oid => '.1.3.6.1.4.1.1347.42.3.1.2.1.1.1.1' #Kyocera specific counter
243             },
244             PRINTCOLOR => {
245             oid => '.1.3.6.1.4.1.1347.42.3.1.2.1.1.1.2' #Kyocera specific counter
246             },
247             COPYTOTAL => {
248             oid => '.1.3.6.1.4.1.1347.42.3.1.1.1.1.2' #Kyocera specific counter
249             },
250             COPYBLACK => {
251             oid => '.1.3.6.1.4.1.1347.42.3.1.2.1.1.2.1' #Kyocera specific counter
252             },
253             COPYCOLOR => {
254             oid => '.1.3.6.1.4.1.1347.42.3.1.2.1.1.2.2' #Kyocera specific counter
255             },
256             FAXTOTAL => {
257             oid => '.1.3.6.1.4.1.1347.42.3.1.1.1.1.4' #Kyocera specific counter
258             }
259             );
260              
261             sub getDeviceInfo {
262 4     4 1 1310 my (%params) = @_;
263              
264 4         4 my $snmp = $params{snmp};
265 4         5 my $datadir = $params{datadir};
266 4         5 my $logger = $params{logger};
267              
268 4         2 my $device;
269              
270             # manufacturer, type and model identification attempt, using sysObjectID
271 4         11 my $sysobjectid = $snmp->get('.1.3.6.1.2.1.1.2.0');
272 4 100       10 if ($sysobjectid) {
273 3         6 my $match = _getSysObjectIDInfo(
274             id => $sysobjectid,
275             datadir => $datadir,
276             logger => $logger
277             );
278 3 100       11 $device->{TYPE} = $match->{type} if $match->{type};
279 3 100       8 $device->{MODEL} = $match->{model} if $match->{model};
280             $device->{MANUFACTURER} = $match->{manufacturer}
281 3 100       6 if $match->{manufacturer};
282 3 100       6 $device->{EXTMOD} = $match->{module} if $match->{module};
283             }
284              
285             # vendor and type identification attempt, using sysDescr
286 4         13 my $sysdescr = $snmp->get('.1.3.6.1.2.1.1.1.0');
287 4 100       8 if ($sysdescr) {
288              
289             # first word
290 3         10 my ($first_word) = $sysdescr =~ /(\S+)/;
291 3         8 my $result = $sysdescr_first_word{lc($first_word)};
292              
293 3 50       6 if ($result) {
294 0 0       0 $device->{VENDOR} = $result->{vendor} if $result->{vendor};
295 0 0       0 $device->{TYPE} = $result->{type} if $result->{type};
296             }
297              
298             # whole sysdescr value
299 3         7 foreach my $rule (@sysdescr_rules) {
300 9 50       29 next unless $sysdescr =~ $rule->{match};
301 0 0       0 $device->{VENDOR} = $rule->{vendor} if $rule->{vendor};
302 0 0       0 $device->{TYPE} = $rule->{type} if $rule->{type};
303 0         0 last;
304             }
305 3         5 $device->{DESCRIPTION} = $sysdescr;
306             }
307              
308             # fallback type identification attempt, using type-specific OID presence
309 4 100       10 if (!exists $device->{TYPE}) {
310 2 50 33     23 if (
311             $snmp->get('.1.3.6.1.2.1.43.11.1.1.6.1.1') ||
312             $snmp->get('.1.3.6.1.2.1.25.3.2.1.3.1')
313             ) {
314 0         0 $device->{TYPE} = 'PRINTER'
315             }
316             }
317              
318             # fallback model identification attempt, using type-specific OID value
319 4 100       8 if (!exists $device->{MODEL}) {
320 3 50 66     13 my $model = exists $device->{TYPE} && $device->{TYPE} eq 'PRINTER' ?
321             $snmp->get('.1.3.6.1.2.1.25.3.2.1.3.1') :
322             $snmp->get('.1.3.6.1.2.1.47.1.1.1.1.13.1') ;
323 3 50       6 $device->{MODEL} = $model if $model;
324             }
325              
326             # fallback manufacturer identification attempt, using type-agnostic OID
327 4 100       7 if (!exists $device->{MANUFACTURER}) {
328 2         3 my $manufacturer = $snmp->get('.1.3.6.1.2.1.43.8.2.1.14.1.1');
329 2 50       4 $device->{MANUFACTURER} = $manufacturer if $manufacturer;
330             }
331              
332             # fallback vendor, using manufacturer
333 4 100 33     11 if (!exists $device->{VENDOR} && exists $device->{MANUFACTURER}) {
334 2         3 $device->{VENDOR} = $device->{MANUFACTURER};
335             }
336              
337             # remaining informations
338 4         13 foreach my $key (keys %base_variables) {
339 28         23 my $variable = $base_variables{$key};
340              
341 28         16 my $raw_value;
342 28 100       35 if (ref $variable->{oid} eq 'ARRAY') {
343 4         3 foreach my $oid (@{$variable->{oid}}) {
  4         8  
344 8         11 $raw_value = $snmp->get($oid);
345 8 50       13 last if defined $raw_value;
346             }
347             } else {
348 24         34 $raw_value = $snmp->get($variable->{oid});
349             }
350 28 50       40 next unless defined $raw_value;
351              
352 0         0 my $type = $variable->{type};
353 0 0       0 my $value =
    0          
    0          
354             $type eq 'memory' ? _getCanonicalMemory($raw_value) :
355             $type eq 'string' ? _getCanonicalString($raw_value) :
356             $type eq 'count' ? _getCanonicalCount($raw_value) :
357             $raw_value;
358              
359 0 0       0 $device->{$key} = $value if defined $value;
360             }
361              
362 4         8 my $mac = _getMacAddress($snmp);
363 4 50       7 $device->{MAC} = $mac if $mac;
364              
365 4         10 my $serial = _getSerial($snmp, $device->{TYPE});
366 4 50       6 $device->{SERIAL} = $serial if $serial;
367              
368 4         8 my $firmware = _getFirmware($snmp, $device->{TYPE});
369 4 50       6 $device->{FIRMWARE} = $firmware if $firmware;
370              
371 4         7 my $results = $snmp->walk('.1.3.6.1.2.1.4.20.1.1');
372             $device->{IPS}->{IP} = [
373 4 50       7 sort values %{$results}
  0         0  
374             ] if $results;
375              
376 4         12 return $device;
377             }
378              
379             sub _getSysObjectIDInfo {
380 3     3   8 my (%params) = @_;
381              
382 3 50       7 return unless $params{id};
383              
384 3 100       17 _loadSysObjectIDDatabase(%params) if !%sysobjectid;
385              
386 3         6 my $logger = $params{logger};
387 3         11 my $prefix = qr/(?:
388             SNMPv2-SMI::enterprises |
389             iso\.3\.6\.1\.4\.1 |
390             \.1\.3\.6\.1\.4\.1
391             )/x;
392             my ($manufacturer_id, $device_id) =
393 3         69 $params{id} =~ /^ $prefix \. (\d+) (?:\. ([\d.]+))? $/x;
394              
395 3 50       9 if (!$manufacturer_id) {
396 0 0       0 $logger->debug("invalid sysobjectID $params{id}: no manufacturer ID")
397             if $logger;
398 0         0 return;
399             }
400              
401 3 50       7 if (!$device_id) {
402 0 0       0 $logger->debug("invalid sysobjectID $params{id}: no device ID")
403             if $logger;
404             }
405              
406 3         3 my $match;
407              
408             # attempt full match first
409 3 50       4 if ($device_id) {
410 3         7 $match = $sysobjectid{$manufacturer_id . '.' . $device_id};
411 3 100       5 if ($match) {
412 1 50       3 $logger->debug(
413             "full match for sysobjectID $params{id} in database"
414             ) if $logger;
415 1         4 return $match;
416             }
417             }
418              
419             # fallback to partial match
420 2         4 $match = $sysobjectid{$manufacturer_id};
421 2 100       4 if ($match) {
422 1 50       3 $logger->debug(
423             "partial match for sysobjectID $params{id} in database: ".
424             "unknown device ID"
425             ) if $logger;
426 1         4 return $match;
427             }
428              
429             # no match
430             $logger->debug(
431 1 50       2 "no match for sysobjectID $params{id} in database: " .
432             "unknown manufacturer ID"
433             ) if $logger;
434 1         4 return;
435             }
436              
437             sub _loadSysObjectIDDatabase {
438 2     2   4 my (%params) = @_;
439              
440 2 100       9 return unless $params{datadir};
441              
442 1         7 my $handle = getFileHandle(file => "$params{datadir}/sysobject.ids");
443 1 50       4 return unless $handle;
444              
445 1         25 while (my $line = <$handle>) {
446 3605 100       4114 next if $line =~ /^#/;
447 3590 100       4740 next if $line =~ /^$/;
448 3523         2093 chomp $line;
449 3523         5540 my ($id, $manufacturer, $type, $model, $module) = split(/\t/, $line);
450 3523         7423 $sysobjectid{$id} = {
451             manufacturer => $manufacturer,
452             type => $type,
453             model => $model
454             };
455 3523 100       7717 $sysobjectid{$id}->{module} = $module if $module;
456             }
457              
458 1         12 close $handle;
459             }
460              
461             sub _getSerial {
462 4     4   5 my ($snmp, $type) = @_;
463              
464             # Entity-MIB::entPhysicalSerialNum
465 4         13 my $entPhysicalSerialNum = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.11');
466 4 50       7 return _getCanonicalSerialNumber($entPhysicalSerialNum)
467             if $entPhysicalSerialNum;
468              
469             # Printer-MIB::prtGeneralSerialNumber
470 4         7 my $prtGeneralSerialNumber = $snmp->get_first('.1.3.6.1.2.1.43.5.1.1.17');
471 4 50       5 return _getCanonicalSerialNumber($prtGeneralSerialNumber)
472             if $prtGeneralSerialNumber;
473              
474             # vendor specific OIDs
475 4         11 my @oids = (
476             '.1.3.6.1.4.1.2636.3.1.3.0', # Juniper-MIB
477             '.1.3.6.1.4.1.248.14.1.1.9.1.10.1', # Hirschman MIB
478             '.1.3.6.1.4.1.253.8.53.3.2.1.3.1', # Xerox-MIB
479             '.1.3.6.1.4.1.367.3.2.1.2.1.4.0', # Ricoh-MIB
480             '.1.3.6.1.4.1.641.2.1.2.1.6.1', # Lexmark-MIB
481             '.1.3.6.1.4.1.1602.1.2.1.4.0', # Canon-MIB
482             '.1.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.1.0', # Brother-MIB
483             '.1.3.6.1.4.1.318.1.1.4.1.5.0', # MasterSwitch-MIB
484             '.1.3.6.1.4.1.6027.3.8.1.1.5.0', # F10-C-SERIES-CHASSIS-MIB
485             '.1.3.6.1.4.1.6027.3.10.1.2.2.1.12.1', # FORCE10-SMI
486             );
487 4         6 foreach my $oid (@oids) {
488 40         51 my $value = $snmp->get($oid);
489 40 50       51 next unless $value;
490 0         0 return _getCanonicalSerialNumber($value);
491             }
492              
493 4         9 return;
494             }
495              
496             sub _getFirmware {
497 4     4   5 my ($snmp, $type) = @_;
498              
499 4         5 my $entPhysicalSoftwareRev = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.10');
500 4 50       6 return $entPhysicalSoftwareRev if $entPhysicalSoftwareRev;
501              
502 4         9 my $entPhysicalFirmwareRev = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.9');
503 4 50       7 return $entPhysicalFirmwareRev if $entPhysicalFirmwareRev;
504              
505 4         7 my $ios_version = $snmp->get('.1.3.6.1.4.1.9.9.25.1.1.1.2.5');
506 4 50       6 return $ios_version if $ios_version;
507              
508 4         7 my $firmware = $snmp->get('.1.3.6.1.4.1.248.14.1.1.2.0');
509 4 50       7 return $firmware if $firmware;
510              
511 4         4 return;
512             }
513              
514             sub _getMacAddress {
515 4     4   5 my ($snmp) = @_;
516              
517             # use BRIDGE-MIB::dot1dBaseBridgeAddress if available
518 4         3 my $address_oid = ".1.3.6.1.2.1.17.1.1.0";
519 4         10 my $address = _getCanonicalMacAddress($snmp->get($address_oid));
520              
521 4 50 33     9 return $address if $address && $address =~ /^$mac_address_pattern$/;
522              
523             # fallback on ports addresses (IF-MIB::ifPhysAddress) if unique
524 4         3 my $addresses_oid = ".1.3.6.1.2.1.2.2.1.6";
525 4         9 my $addresses = $snmp->walk($addresses_oid);
526             my @addresses =
527             uniq
528 0         0 grep { $_ ne '00:00:00:00:00:00' }
529 0         0 grep { $_ }
530 0         0 map { _getCanonicalMacAddress($_) }
531 4         5 values %{$addresses};
  4         20  
532              
533 4 50 33     9 return $addresses[0] if @addresses && @addresses == 1;
534              
535 4         7 return;
536             }
537              
538             sub getDeviceFullInfo {
539 0     0 1 0 my (%params) = @_;
540              
541 0         0 my $snmp = $params{snmp};
542 0         0 my $logger = $params{logger};
543              
544             # first, let's retrieve basic device informations
545 0         0 my $info = getDeviceInfo(%params);
546 0 0       0 return unless $info;
547              
548             # description is defined as DESCRIPTION for discovery
549             # and COMMENTS for inventory
550 0 0       0 if (exists $info->{DESCRIPTION}) {
551 0         0 $info->{COMMENTS} = $info->{DESCRIPTION};
552 0         0 delete $info->{DESCRIPTION};
553             }
554              
555             # host name is defined as SNMPHOSTNAME for discovery
556             # and NAME for inventory
557 0 0       0 if (exists $info->{SNMPHOSTNAME}) {
558 0         0 $info->{NAME} = $info->{SNMPHOSTNAME};
559 0         0 delete $info->{SNMPHOSTNAME};
560             }
561              
562             # device ID is set from the server request
563 0         0 $info->{ID} = $params{id};
564              
565             # device TYPE is set either:
566             # - from the server request,
567             # - from initial identification
568 0   0     0 $info->{TYPE} = $params{type} || $info->{TYPE};
569              
570             # second, use results to build the object
571 0         0 my $device = { INFO => $info };
572              
573 0         0 _setGenericProperties(
574             device => $device,
575             snmp => $snmp,
576             logger => $logger
577             );
578              
579             _setPrinterProperties(
580             device => $device,
581             snmp => $snmp,
582             logger => $logger,
583             datadir => $params{datadir}
584 0 0 0     0 ) if $info->{TYPE} && $info->{TYPE} eq 'PRINTER';
585              
586             _setNetworkingProperties(
587             device => $device,
588             snmp => $snmp,
589             logger => $logger,
590             datadir => $params{datadir}
591 0 0 0     0 ) if $info->{TYPE} && $info->{TYPE} eq 'NETWORKING';
592              
593             # external processing for the $device
594 0 0       0 if ($device->{INFO}->{EXTMOD}) {
595             runFunction(
596             module => "FusionInventory::Agent::Tools::Hardware::" . $device->{INFO}->{EXTMOD},
597 0         0 function => "run",
598             logger => $logger,
599             params => {
600             snmp => $snmp,
601             device => $device,
602             logger => $logger,
603             },
604             load => 1
605             );
606              
607             # no need to send this to the server
608 0         0 delete $device->{INFO}->{EXTMOD};
609             }
610              
611             # convert ports hashref to an arrayref, sorted by interface number
612 0         0 my $ports = $device->{PORTS}->{PORT};
613 0 0 0     0 if ($ports && %$ports) {
614             $device->{PORTS}->{PORT} = [
615 0         0 map { $ports->{$_} }
616 0         0 sort { $a <=> $b }
617 0         0 keys %{$ports}
  0         0  
618             ];
619             } else {
620 0         0 delete $device->{PORTS};
621             }
622              
623 0         0 return $device;
624             }
625              
626             sub _setGenericProperties {
627 0     0   0 my (%params) = @_;
628              
629 0         0 my $device = $params{device};
630 0         0 my $snmp = $params{snmp};
631 0         0 my $logger = $params{logger};
632              
633             # ports is a sparse hash of network ports, indexed by interface identifier
634             # (ifIndex, or IFNUMBER in agent output)
635 0         0 my $ports;
636              
637 0         0 foreach my $key (keys %interface_variables) {
638 0         0 my $variable = $interface_variables{$key};
639 0 0       0 next unless $variable->{oid};
640              
641 0         0 my $results;
642 0 0       0 if (ref $variable->{oid} eq 'ARRAY') {
643 0         0 foreach my $oid (@{$variable->{oid}}) {
  0         0  
644 0         0 $results = $snmp->walk($oid);
645 0 0       0 last if $results;
646             }
647             } else {
648 0         0 $results = $snmp->walk($variable->{oid});
649             }
650 0 0       0 next unless $results;
651              
652 0         0 my $type = $variable->{type};
653             # each result matches the following scheme:
654             # $prefix.$i = $value, with $i as port id
655 0         0 while (my ($suffix, $raw_value) = each %{$results}) {
  0         0  
656 0 0       0 my $value =
    0          
    0          
    0          
657             $type eq 'mac' ? _getCanonicalMacAddress($raw_value) :
658             $type eq 'constant' ? _getCanonicalConstant($raw_value) :
659             $type eq 'string' ? _getCanonicalString($raw_value) :
660             $type eq 'count' ? _getCanonicalCount($raw_value) :
661             $raw_value;
662 0 0       0 $ports->{$suffix}->{$key} = $value if defined $value;
663             }
664             }
665              
666 0         0 my $highspeed_results = $snmp->walk('.1.3.6.1.2.1.31.1.1.1.15');
667 0         0 my $speed_results = $snmp->walk('.1.3.6.1.2.1.2.2.1.5');
668             # ifSpeed is expressed in b/s, and available for all interfaces
669             # HighSpeed is expressed in Mb/s, available for fast interfaces only
670 0         0 while (my ($suffix, $speed_value) = each %{$speed_results}) {
  0         0  
671 0         0 my $highspeed_value = $highspeed_results->{$suffix};
672 0 0       0 $ports->{$suffix}->{IFSPEED} = $highspeed_value ?
673             $highspeed_value * 1000 * 1000 : $speed_value;
674             }
675              
676 0         0 my $results = $snmp->walk('.1.3.6.1.2.1.4.20.1.2');
677             # each result matches the following scheme:
678             # $prefix.$i.$j.$k.$l = $value
679             # with $i.$j.$k.$l as IP address, and $value as port id
680 0         0 foreach my $suffix (sort keys %{$results}) {
  0         0  
681 0         0 my $value = $results->{$suffix};
682 0 0       0 next unless $value;
683             # safety checks
684 0 0       0 if (! exists $ports->{$value}) {
685 0 0       0 $logger->warning(
686             "unknown interface $value for IP address $suffix, ignoring"
687             ) if $logger;
688 0         0 next;
689             }
690 0 0       0 if ($suffix !~ /^$ip_address_pattern$/) {
691 0 0       0 $logger->error("invalid IP address $suffix") if $logger;
692 0         0 next;
693             }
694 0         0 $ports->{$value}->{IP} = $suffix;
695 0         0 push @{$ports->{$value}->{IPS}->{IP}}, $suffix;
  0         0  
696             }
697              
698 0         0 $device->{PORTS}->{PORT} = $ports;
699             }
700              
701             sub _setPrinterProperties {
702 0     0   0 my (%params) = @_;
703              
704 0         0 my $device = $params{device};
705 0         0 my $snmp = $params{snmp};
706 0         0 my $logger = $params{logger};
707              
708             # colors
709 0         0 my $colors = $snmp->walk('.1.3.6.1.2.1.43.12.1.1.4.1');
710              
711             # consumable levels
712 0         0 my $color_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.3.1');
713 0         0 my $type_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.5.1');
714 0         0 my $descriptions = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.6.1');
715 0         0 my $unit_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.7.1');
716 0         0 my $max_levels = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.8.1');
717 0         0 my $current_levels = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.9.1');
718              
719 0         0 foreach my $consumable_id (sort keys %$descriptions) {
720 0         0 my $max = $max_levels->{$consumable_id};
721 0         0 my $current = $current_levels->{$consumable_id};
722 0 0 0     0 next unless defined $max and defined $current;
723              
724             # consumable identification
725 0         0 my $type_id = $type_ids->{$consumable_id};
726 0         0 my $color_id = $color_ids->{$consumable_id};
727              
728 0         0 my $type;
729 0 0       0 if ($type_id != 1) {
730 0         0 $type = $consumable_types{$type_id};
731             } else {
732             # fallback on description
733 0         0 my $description = $descriptions->{$consumable_id};
734 0 0       0 $type =
    0          
    0          
735             $description =~ /maintenance/i ? 'MAINTENANCEKIT' :
736             $description =~ /fuser/i ? 'FUSERKIT' :
737             $description =~ /transfer/i ? 'TRANSFERKIT' :
738             undef ;
739             }
740              
741 0 0       0 if (!$type) {
742 0 0       0 $logger->debug("unknown consumable type $type_id") if $logger;
743 0         0 next;
744             }
745              
746 0 0 0     0 if ($type eq 'TONER' || $type eq 'DRUM' || $type eq 'CARTRIDGE' || $type eq 'DEVELOPER') {
      0        
      0        
747 0         0 my $color;
748 0 0       0 if ($color_id) {
749 0         0 $color = _getCanonicalString($colors->{$color_id});
750 0 0       0 if (!$color) {
751 0 0       0 $logger->debug("invalid color ID $color_id") if $logger;
752 0         0 next;
753             }
754             } else {
755             # fallback on description
756 0         0 my $description = $descriptions->{$consumable_id};
757 0 0       0 $color =
    0          
    0          
    0          
758             $description =~ /cyan/i ? 'cyan' :
759             $description =~ /magenta/i ? 'magenta' :
760             $description =~ /(yellow|jaune)/i ? 'yellow' :
761             $description =~ /(black|noir)/i ? 'black' :
762             'black' ;
763             }
764 0         0 $type .= uc($color);
765             }
766              
767 0         0 my $value;
768 0 0       0 if ($current == -2) {
    0          
769             # A value of -2 means unknown
770 0         0 $value = undef;
771             } elsif ($current == -3) {
772             # A value of -3 means that the printer knows that there is some
773             # supply/remaining space, respectively.
774 0         0 $value = 'OK';
775             } else {
776 0 0       0 if ($max != -2) {
777 0         0 $value = _getPercentValue($max, $current);
778             } else {
779             # PrtMarkerSuppliesSupplyUnitTC in Printer MIB
780 0         0 my $unit_id = $unit_ids->{$consumable_id};
781 0 0       0 $value =
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
782             $unit_id == 19 ? $current :
783             $unit_id == 18 ? $current . 'items' :
784             $unit_id == 17 ? $current . 'm' :
785             $unit_id == 16 ? $current . 'feet' :
786             $unit_id == 15 ? ($current / 10) . 'ml' :
787             $unit_id == 13 ? ($current / 10) . 'g' :
788             $unit_id == 11 ? $current . 'hours' :
789             $unit_id == 8 ? $current . 'sheets' :
790             $unit_id == 7 ? $current . 'impressions' :
791             $unit_id == 4 ? ($current / 1000) . 'mm' :
792             $current . '?' ;
793             }
794             }
795              
796 0         0 $device->{CARTRIDGES}->{$type} = $value;
797             }
798              
799             # page counters
800 0         0 foreach my $key (keys %printer_pagecounters_variables) {
801 0         0 my $variable = $printer_pagecounters_variables{$key};
802 0         0 my $value;
803 0 0       0 if (ref $variable->{oid} eq 'ARRAY') {
804 0         0 foreach my $oid (@{$variable->{oid}}) {
  0         0  
805 0         0 $value = $snmp->get($oid);
806 0 0       0 last if $value;
807             }
808             } else {
809 0         0 my $oid = $variable->{oid};
810 0         0 $value = $snmp->get($oid);
811             }
812 0 0       0 next unless defined $value;
813 0 0       0 if (!_isInteger($value)) {
814 0 0       0 $logger->error("incorrect counter value $value, check $variable->{mapping} mapping") if $logger;
815 0         0 next;
816             }
817 0         0 $device->{PAGECOUNTERS}->{$key} = $value;
818             }
819             }
820              
821             sub _setNetworkingProperties {
822 0     0   0 my (%params) = @_;
823              
824 0         0 my $device = $params{device};
825 0         0 my $snmp = $params{snmp};
826 0         0 my $logger = $params{logger};
827              
828 0         0 my $ports = $device->{PORTS}->{PORT};
829              
830 0         0 _setVlans(
831             snmp => $snmp,
832             ports => $ports,
833             logger => $logger
834             );
835              
836 0         0 _setTrunkPorts(
837             snmp => $snmp,
838             ports => $ports,
839             logger => $logger
840             );
841              
842             _setConnectedDevices(
843             snmp => $snmp,
844             ports => $ports,
845             logger => $logger,
846             vendor => $device->{INFO}->{MANUFACTURER}
847 0         0 );
848              
849 0         0 _setKnownMacAddresses(
850             snmp => $snmp,
851             ports => $ports,
852             logger => $logger,
853             );
854              
855 0         0 _setAggregatePorts(
856             snmp => $snmp,
857             ports => $ports,
858             logger => $logger
859             );
860             }
861              
862             sub _getPercentValue {
863 0     0   0 my ($value1, $value2) = @_;
864              
865 0 0 0     0 return unless defined $value1 && _isInteger($value1);
866 0 0 0     0 return unless defined $value2 && _isInteger($value2);
867 0 0       0 return if $value1 == 0;
868              
869             return int(
870 0         0 ( 100 * $value2 ) / $value1
871             );
872             }
873              
874             sub _isInteger {
875 0     0   0 $_[0] =~ /^[+-]?\d+$/;
876             }
877              
878             sub _getCanonicalMacAddress {
879 13     13   3272 my ($value) = @_;
880              
881 13 100       47 return unless $value;
882              
883 9         7 my $result;
884             my @bytes;
885              
886             # packed value, convert from binary to hexadecimal
887 9 50       25 if ($value =~ m/\A [[:ascii:]] \Z/xms) {
888 0         0 $value = unpack 'H*', $value;
889             }
890              
891             # Check if it's a hex value
892 9 100       33 if ($value =~ /^(?:0x)?([0-9A-F]+)$/i) {
893 5         30 @bytes = unpack("(A2)*", $1);
894             } else {
895 4         11 @bytes = split(':', $value);
896             # return if bytes are not hex
897 4 100       27 return if grep(!/^[0-9A-F]{1,2}$/i, @bytes);
898             }
899              
900 8 100 100     42 if (scalar(@bytes) == 6) {
    100 66        
    100          
    50          
901             # it's a MAC
902             } elsif (scalar(@bytes) == 8 &&
903             (($bytes[0] eq '10' && $bytes[1] =~ /^0+/) # WWN 10:00:...
904             || $bytes[0] =~ /^2/)) { # WWN 2X:XX:...
905             } elsif (scalar(@bytes) < 6) {
906             # make a WWN. prepend "10" and zeroes as necessary
907 1         4 while (scalar(@bytes) < 7) { unshift @bytes, '00' }
  2         4  
908 1         2 unshift @bytes, '10';
909             } elsif (scalar(@bytes) > 6) {
910             # make a MAC. take 6 bytes from the right
911 1         4 @bytes = @bytes[-6 .. -1];
912             }
913              
914 8         14 $result = join ":", map { sprintf("%02x", hex($_)) } @bytes;
  54         85  
915              
916 8 50       16 return if $result eq '00:00:00:00:00:00';
917 8         35 return lc($result);
918             }
919              
920             sub _getCanonicalString {
921 6     6   7 my ($value) = @_;
922              
923 6         12 $value = hex2char($value);
924 6 100       9 return unless $value;
925              
926             # truncate after first null-character
927 4         3 $value =~ s/\000.*$//;
928              
929             # unquote string
930 4         18 $value =~ s/^\\?["']//;
931 4         11 $value =~ s/\\?["']$//;
932              
933 4 50       6 return unless $value;
934              
935 4         5 return $value;
936             }
937              
938             sub _getCanonicalSerialNumber {
939 0     0   0 my ($value) = @_;
940              
941 0         0 $value = hex2char($value);
942 0 0       0 return unless $value;
943              
944 0         0 $value =~ s/[[:^print:]]//g;
945 0         0 $value =~ s/^\s+//;
946 0         0 $value =~ s/\s+$//;
947 0         0 $value =~ s/\.{2,}//g;
948 0 0       0 return unless $value;
949              
950 0         0 return $value;
951             }
952              
953             sub _getCanonicalMemory {
954 0     0   0 my ($value) = @_;
955              
956 0 0       0 if ($value =~ /^(\d+) KBytes$/) {
957 0         0 return int($1 / 1024);
958             } else {
959 0         0 return int($value / 1024 / 1024);
960             }
961             }
962              
963             sub _getCanonicalConstant {
964 0     0   0 my ($value) = @_;
965              
966 0 0       0 return $value if _isInteger($value);
967 0 0       0 return $1 if $value =~ /\((\d+)\)$/;
968             }
969              
970             sub _getCanonicalCount {
971 0     0   0 my ($value) = @_;
972              
973 0 0       0 return _isInteger($value) ? $value : undef;
974             }
975              
976             sub _getElement {
977 6     6   832 my ($oid, $index) = @_;
978              
979 6         18 my @array = split(/\./, $oid);
980 6         18 return $array[$index];
981             }
982              
983             sub _getElements {
984 2     2   3 my ($oid, $first, $last) = @_;
985              
986 2         9 my @array = split(/\./, $oid);
987 2         15 return @array[$first .. $last];
988             }
989              
990             sub _setKnownMacAddresses {
991 4     4   19 my (%params) = @_;
992              
993 4         6 my $snmp = $params{snmp};
994 4         4 my $ports = $params{ports};
995 4         5 my $logger = $params{logger};
996              
997             # start with mac addresses seen on default VLAN
998 4         7 my $addresses = _getKnownMacAddresses(
999             snmp => $snmp,
1000             address2port => '.1.3.6.1.2.1.17.4.3.1.2', # dot1dTpFdbPort
1001             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
1002             );
1003              
1004 4 50       8 if ($addresses) {
1005 4         9 _addKnownMacAddresses(
1006             ports => $ports,
1007             logger => $logger,
1008             addresses => $addresses,
1009             );
1010             }
1011              
1012             # add additional mac addresses for other VLANs
1013 4         9 $addresses = _getKnownMacAddresses(
1014             snmp => $snmp,
1015             address2port => '.1.3.6.1.2.1.17.7.1.2.2.1.2', # dot1qTpFdbPort
1016             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
1017             );
1018              
1019 4 50       9 if ($addresses) {
1020 0         0 _addKnownMacAddresses(
1021             ports => $ports,
1022             logger => $logger,
1023             addresses => $addresses,
1024             );
1025             } else {
1026             # compute the list of vlans associated with at least one port
1027             # without CDP/LLDP information
1028 4         5 my @vlans;
1029 4         7 my %seen = ( 1 => 1 );
1030 4         9 foreach my $port (values %$ports) {
1031             next if
1032             exists $port->{CONNECTIONS} &&
1033             exists $port->{CONNECTIONS}->{CDP} &&
1034 4 50 33     19 $port->{CONNECTIONS}->{CDP};
      66        
1035 3 50       8 next unless exists $port->{VLANS};
1036             push @vlans,
1037 0         0 grep { !$seen{$_}++ }
1038 0         0 map { $_->{NUMBER} }
1039 0         0 @{$port->{VLANS}->{VLAN}};
  0         0  
1040             }
1041              
1042             # get additional associated mac addresses from those vlans
1043 4         7 my @mac_addresses = ();
1044 4         5 foreach my $vlan (@vlans) {
1045 0 0       0 $logger->debug("switching SNMP context to vlan $vlan") if $logger;
1046 0         0 $snmp->switch_vlan_context($vlan);
1047 0         0 my $mac_addresses = _getKnownMacAddresses(
1048             snmp => $snmp,
1049             address2port => '.1.3.6.1.2.1.17.4.3.1.2', # dot1dTpFdbPort
1050             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
1051             );
1052 0 0       0 next unless $mac_addresses;
1053              
1054 0         0 push @mac_addresses, $mac_addresses;
1055             }
1056 4 50       11 $snmp->reset_original_context() if @vlans;
1057              
1058             # Try deprecated OIDs if no additional mac addresse was found on vlans
1059 4 50       9 unless (@mac_addresses) {
1060 4         9 my $addresses = _getKnownMacAddressesDeprecatedOids(
1061             snmp => $snmp,
1062             address2mac => '.1.3.6.1.2.1.4.22.1.2', # ipNetToMediaPhysAddress
1063             address2interface => '.1.3.6.1.2.1.4.22.1.1' # ipNetToMediaIfIndex
1064             );
1065 4 50       9 push @mac_addresses, $addresses
1066             if ($addresses);
1067             }
1068              
1069             # Finally add found mac addresse
1070 4         10 foreach my $mac_addresses (@mac_addresses) {
1071 0         0 _addKnownMacAddresses(
1072             ports => $ports,
1073             logger => $logger,
1074             addresses => $mac_addresses,
1075             );
1076             }
1077             }
1078             }
1079              
1080             sub _addKnownMacAddresses {
1081 4     4   9 my (%params) = @_;
1082              
1083 4         5 my $ports = $params{ports};
1084 4         4 my $logger = $params{logger};
1085 4         4 my $mac_addresses = $params{addresses};
1086              
1087 4         9 foreach my $port_id (keys %$mac_addresses) {
1088             # safety check
1089 4 50       9 if (! exists $ports->{$port_id}) {
1090 0 0       0 $logger->error(
1091             "invalid interface ID $port_id while setting known mac " .
1092             "addresses, aborting"
1093             ) if $logger;
1094 0         0 next;
1095             }
1096              
1097 4         5 my $port = $ports->{$port_id};
1098              
1099             # connected device has already been identified through CDP/LLDP
1100             next if
1101             exists $port->{CONNECTIONS} &&
1102             exists $port->{CONNECTIONS}->{CDP} &&
1103 4 50 66     16 $port->{CONNECTIONS}->{CDP};
      33        
1104              
1105             # get at list of already associated addresses, if any
1106             # as well as the port own mac address, if known
1107 3         3 my @known;
1108 3 100       6 push @known, $port->{MAC} if $port->{MAC};
1109 0         0 push @known, @{$port->{CONNECTIONS}->{CONNECTION}->{MAC}} if
1110             exists $port->{CONNECTIONS} &&
1111             exists $port->{CONNECTIONS}->{CONNECTION} &&
1112 3 0 33     6 exists $port->{CONNECTIONS}->{CONNECTION}->{MAC};
      0        
1113              
1114             # filter out those addresses from the additional ones
1115 3         4 my %known = map { $_ => 1 } @known;
  1         6  
1116 3         3 my @adresses = grep { !$known{$_} } @{$mac_addresses->{$port_id}};
  5         13  
  3         4  
1117 3 50       6 next unless @adresses;
1118              
1119             # add remaining ones
1120 3         3 push @{$port->{CONNECTIONS}->{CONNECTION}->{MAC}}, @adresses;
  3         13  
1121             }
1122             }
1123              
1124             sub _getKnownMacAddresses {
1125 10     10   27 my (%params) = @_;
1126              
1127 10         12 my $snmp = $params{snmp};
1128              
1129 10         6 my $results;
1130 10         25 my $address2port = $snmp->walk($params{address2port});
1131 10         27 my $port2interface = $snmp->walk($params{port2interface});
1132              
1133             # dot1dTpFdbPort values matches the following scheme:
1134             # $prefix.a.b.c.d.e.f = $port
1135              
1136             # dot1qTpFdbPort values matches the following scheme:
1137             # $prefix.$vlan.a.b.c.d.e.f = $port
1138              
1139             # in both case, the last 6 elements of the OID constitutes
1140             # the mac address in decimal format
1141 10         9 foreach my $suffix (sort keys %{$address2port}) {
  10         27  
1142 10         11 my $port_id = $address2port->{$suffix};
1143 10         9 my $interface_id = $port2interface->{$port_id};
1144 10 50       17 next unless defined $interface_id;
1145              
1146 10         22 my @bytes = split(/\./, $suffix);
1147 10 50       19 shift @bytes if @bytes > 6;
1148              
1149 10         8 push @{$results->{$interface_id}},
  10         62  
1150             sprintf "%02x:%02x:%02x:%02x:%02x:%02x", @bytes;
1151             }
1152              
1153 10         32 return $results;
1154             }
1155              
1156             sub _getKnownMacAddressesDeprecatedOids {
1157 4     4   10 my (%params) = @_;
1158              
1159 4         5 my $snmp = $params{snmp};
1160            
1161 4         3 my $results;
1162 4         12 my $address2mac = $snmp->walk($params{address2mac});
1163 4         8 my $address2interface = $snmp->walk($params{address2interface});
1164              
1165 4         5 foreach my $suffix (sort keys %{$address2mac}) {
  4         7  
1166 0         0 my $interface_id = $address2interface->{$suffix};
1167 0 0       0 next unless defined $interface_id;
1168            
1169 0         0 push @{$results->{$interface_id}},
1170 0         0 _getCanonicalMacAddress($address2mac->{$suffix});
1171             }
1172            
1173 4         9 return $results;
1174             }
1175              
1176             sub _setConnectedDevices {
1177 0     0   0 my (%params) = @_;
1178              
1179 0         0 my $logger = $params{logger};
1180 0         0 my $ports = $params{ports};
1181              
1182 0         0 my $lldp_info = _getLLDPInfo(%params);
1183 0 0       0 if ($lldp_info) {
1184 0         0 foreach my $interface_id (keys %$lldp_info) {
1185             # safety check
1186 0 0       0 if (! exists $ports->{$interface_id}) {
1187 0 0       0 $logger->warning(
1188             "unknown interface $interface_id in LLDP info, ignoring"
1189             ) if $logger;
1190 0         0 next;
1191             }
1192              
1193 0         0 my $port = $ports->{$interface_id};
1194 0         0 my $lldp_connection = $lldp_info->{$interface_id};
1195              
1196             $port->{CONNECTIONS} = {
1197 0         0 CDP => 1,
1198             CONNECTION => $lldp_connection
1199             };
1200             }
1201             }
1202              
1203 0         0 my $cdp_info = _getCDPInfo(%params);
1204 0 0       0 if ($cdp_info) {
1205 0         0 foreach my $interface_id (keys %$cdp_info) {
1206             # safety check
1207 0 0       0 if (! exists $ports->{$interface_id}) {
1208 0 0       0 $logger->warning(
1209             "unknown interface $interface_id in CDP info, ignoring"
1210             ) if $logger;
1211 0         0 next;
1212             }
1213              
1214 0         0 my $port = $ports->{$interface_id};
1215 0         0 my $lldp_connection = $port->{CONNECTIONS}->{CONNECTION};
1216 0         0 my $cdp_connection = $cdp_info->{$interface_id};
1217              
1218 0 0       0 if ($lldp_connection) {
1219 0 0       0 if ($cdp_connection->{SYSDESCR} eq $lldp_connection->{SYSDESCR}) {
1220             # same device, everything OK
1221 0         0 foreach my $key (qw/IP MODEL/) {
1222 0         0 $lldp_connection->{$key} = $cdp_connection->{$key};
1223             }
1224             } else {
1225             # undecidable situation
1226 0         0 $logger->warning(
1227             "multiple neighbors found by LLDP and CDP for " .
1228             "interface $interface_id, ignoring"
1229             );
1230 0         0 delete $port->{CONNECTIONS};
1231             }
1232             } else {
1233             $port->{CONNECTIONS} = {
1234 0         0 CDP => 1,
1235             CONNECTION => $cdp_connection
1236             };
1237             }
1238             }
1239             }
1240              
1241 0         0 my $edp_info = _getEDPInfo(%params);
1242 0 0       0 if ($edp_info) {
1243 0         0 foreach my $interface_id (keys %$edp_info) {
1244             # safety check
1245 0 0       0 if (! exists $ports->{$interface_id}) {
1246 0 0       0 $logger->warning(
1247             "unknown interface $interface_id in EDP info, ignoring"
1248             ) if $logger;
1249 0         0 next;
1250             }
1251              
1252 0         0 my $port = $ports->{$interface_id};
1253 0         0 my $lldp_connection = $port->{CONNECTIONS}->{CONNECTION};
1254 0         0 my $edp_connection = $edp_info->{$interface_id};
1255              
1256 0 0       0 if ($lldp_connection) {
1257 0 0       0 if ($edp_connection->{SYSDESCR} eq $lldp_connection->{SYSDESCR}) {
1258             # same device, everything OK
1259 0         0 foreach my $key (qw/IP/) {
1260 0         0 $lldp_connection->{$key} = $edp_connection->{$key};
1261             }
1262             } else {
1263             # undecidable situation
1264 0         0 $logger->warning(
1265             "multiple neighbors found by LLDP and EDP for " .
1266             "interface $interface_id, ignoring"
1267             );
1268 0         0 delete $port->{CONNECTIONS};
1269             }
1270             } else {
1271             $port->{CONNECTIONS} = {
1272 0         0 CDP => 1,
1273             CONNECTION => $edp_connection
1274             };
1275             }
1276             }
1277             }
1278             }
1279              
1280             sub _getLLDPInfo {
1281 0     0   0 my (%params) = @_;
1282              
1283 0         0 my $snmp = $params{snmp};
1284              
1285 0         0 my $results;
1286 0         0 my $lldpRemChassisId = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.5');
1287 0         0 my $lldpRemPortId = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.7');
1288 0         0 my $lldpRemPortDesc = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.8');
1289 0         0 my $lldpRemSysName = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.9');
1290 0         0 my $lldpRemSysDesc = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.10');
1291              
1292             # port to interface mapping
1293 0   0     0 my $port2interface =
1294             $snmp->walk('.1.3.6.1.4.1.9.5.1.4.1.1.11.1') || # Cisco portIfIndex
1295             $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2'); # dot1dBasePortIfIndex
1296              
1297             # each lldp variable matches the following scheme:
1298             # $prefix.x.y.z = $value
1299             # whereas y is either a port or an interface id
1300              
1301 0         0 while (my ($suffix, $mac) = each %{$lldpRemChassisId}) {
  0         0  
1302 0         0 my $sysdescr = _getCanonicalString($lldpRemSysDesc->{$suffix});
1303 0 0       0 next unless $sysdescr;
1304              
1305 0         0 my $connection = {
1306             SYSMAC => lc(alt2canonical($mac)),
1307             SYSDESCR => $sysdescr
1308             };
1309              
1310             # portId is either a port number or a port mac address,
1311             # duplicating chassiId
1312 0         0 my $portId = $lldpRemPortId->{$suffix};
1313 0 0 0     0 if ($portId !~ /^0x/ or length($portId) != 14) {
1314 0         0 $connection->{IFNUMBER} = $portId;
1315             }
1316              
1317 0         0 my $ifdescr = _getCanonicalString($lldpRemPortDesc->{$suffix});
1318 0 0       0 $connection->{IFDESCR} = $ifdescr if $ifdescr;
1319              
1320 0         0 my $sysname = _getCanonicalString($lldpRemSysName->{$suffix});
1321 0 0       0 $connection->{SYSNAME} = $sysname if $sysname;
1322              
1323 0         0 my $id = _getElement($suffix, -2);
1324             my $interface_id =
1325             ! exists $port2interface->{$id} ? $id :
1326             $params{vendor} eq 'Juniper' ? $id :
1327 0 0       0 $port2interface->{$id};
    0          
1328              
1329 0         0 $results->{$interface_id} = $connection;
1330             }
1331              
1332 0         0 return $results;
1333             }
1334              
1335             sub _getCDPInfo {
1336 3     3   11 my (%params) = @_;
1337              
1338 3         3 my $snmp = $params{snmp};
1339 3         3 my $logger = $params{logger};
1340              
1341 3         4 my ($results, $blacklist);
1342 3         7 my $cdpCacheAddress = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.4');
1343 3         6 my $cdpCacheVersion = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.5');
1344 3         6 my $cdpCacheDeviceId = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.6');
1345 3         5 my $cdpCacheDevicePort = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.7');
1346 3         7 my $cdpCachePlatform = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.8');
1347              
1348             # each cdp variable matches the following scheme:
1349             # $prefix.x.y = $value
1350             # whereas x is the port number
1351              
1352 3         3 while (my ($suffix, $ip) = each %{$cdpCacheAddress}) {
  6         18  
1353 3         7 my $interface_id = _getElement($suffix, -2);
1354 3         8 $ip = hex2canonical($ip);
1355 3 50       8 next if $ip eq '0.0.0.0';
1356              
1357 3         6 my $sysdescr = _getCanonicalString($cdpCacheVersion->{$suffix});
1358 3         7 my $model = _getCanonicalString($cdpCachePlatform->{$suffix});
1359 3 100 100     14 next unless $sysdescr && $model;
1360              
1361 1         4 my $connection = {
1362             IP => $ip,
1363             SYSDESCR => $sysdescr,
1364             MODEL => $model,
1365             };
1366              
1367             # cdpCacheDevicePort is either a port number or a port description
1368 1         1 my $devicePort = $cdpCacheDevicePort->{$suffix};
1369 1 50       4 if ($devicePort =~ /^\d+$/) {
1370 0         0 $connection->{IFNUMBER} = $devicePort;
1371             } else {
1372 1         3 $connection->{IFDESCR} = $devicePort;
1373             }
1374              
1375             # cdpCacheDeviceId is either remote host name, either remote mac address
1376 1         1 my $deviceId = $cdpCacheDeviceId->{$suffix};
1377 1 50       3 if ($deviceId =~ /^0x/) {
1378 0 0       0 if (length($deviceId) == 14) {
1379             # let's assume it is a mac address if the length is 6 bytes
1380 0         0 $connection->{SYSMAC} = lc(alt2canonical($deviceId));
1381             } else {
1382             # otherwise it's an hex-encode hostname
1383 0         0 $connection->{SYSNAME} = _getCanonicalString($deviceId);
1384             }
1385             } else {
1386 1         1 $connection->{SYSNAME} = $deviceId;
1387             }
1388              
1389 1 50 33     7 if ($connection->{SYSNAME} &&
1390             $connection->{SYSNAME} =~ /^SIP([A-F0-9a-f]*)$/) {
1391 1         6 $connection->{MAC} = lc(alt2canonical("0x".$1));
1392             }
1393              
1394             # warning: multiple neighbors announcement for the same interface
1395             # usually means a non-CDP aware intermediate equipement
1396 1 50       3 if ($results->{$interface_id}) {
1397 0         0 $logger->warning(
1398             "multiple neighbors found by CDP for interface $interface_id," .
1399             " ignoring"
1400             );
1401 0         0 $blacklist->{$interface_id} = 1;
1402             } else {
1403 1         3 $results->{$interface_id} = $connection;
1404             }
1405             }
1406              
1407             # remove blacklisted results
1408 3         5 delete $results->{$_} foreach keys %$blacklist;
1409              
1410 3         10 return $results;
1411             }
1412              
1413             sub _getEDPInfo {
1414 0     0   0 my (%params) = @_;
1415              
1416 0         0 my $snmp = $params{snmp};
1417 0         0 my $logger = $params{logger};
1418              
1419 0         0 my ($results, $blacklist);
1420 0         0 my $edpNeighborVlanIpAddress = $snmp->walk('.1.3.6.1.4.1.1916.1.13.3.1.3');
1421 0         0 my $edpNeighborName = $snmp->walk('.1.3.6.1.4.1.1916.1.13.2.1.3');
1422 0         0 my $edpNeighborPort = $snmp->walk('.1.3.6.1.4.1.1916.1.13.2.1.6');
1423              
1424             # each entry from extremeEdpTable matches the following scheme:
1425             # $prefix.x.0.0.y1.y2.y3.y4.y5.y6 = $value
1426             # - x: the interface id
1427             # - y1.y2.y3.y4.y5.y6: the remote mac address
1428              
1429             # each entry from extremeEdpNeighborTable matches the following scheme:
1430             # $prefix.x.0.0.y1.y2.y3.y4.y5.y6.z1.z2...zz = $value
1431             # - x: the interface id,
1432             # - y1.y2.y3.y4.y5.y6: the remote mac address
1433             # - z1.z2...zz: the vlan name in ASCII
1434              
1435 0         0 while (my ($suffix, $ip) = each %{$edpNeighborVlanIpAddress}) {
  0         0  
1436 0 0       0 next if $ip eq '0.0.0.0';
1437              
1438 0         0 my $interface_id = _getElement($suffix, 0);
1439 0         0 my @mac_elements = _getElements($suffix, 3, 8);
1440 0         0 my $short_suffix = join('.', $interface_id, 0, 0, @mac_elements);
1441              
1442             my $connection = {
1443             IP => $ip,
1444             IFDESCR => $edpNeighborPort->{$short_suffix},
1445 0         0 SYSNAME => $edpNeighborName->{$short_suffix},
1446             SYSMAC => sprintf "%02x:%02x:%02x:%02x:%02x:%02x", @mac_elements
1447             };
1448              
1449             # warning: multiple neighbors announcement for the same interface
1450             # usually means a non-EDP aware intermediate equipement
1451 0 0       0 if ($results->{$interface_id}) {
1452 0         0 $logger->warning(
1453             "multiple neighbors found by EDP for interface $interface_id," .
1454             " ignoring"
1455             );
1456 0         0 $blacklist->{$interface_id} = 1;
1457             } else {
1458 0         0 $results->{$interface_id} = $connection;
1459             }
1460             }
1461              
1462             # remove blacklisted results
1463 0         0 delete $results->{$_} foreach keys %$blacklist;
1464              
1465 0         0 return $results;
1466             }
1467              
1468              
1469             sub _setVlans {
1470 0     0   0 my (%params) = @_;
1471              
1472             my $vlans = _getVlans(
1473             snmp => $params{snmp},
1474 0         0 );
1475 0 0       0 return unless $vlans;
1476              
1477 0         0 my $ports = $params{ports};
1478 0         0 my $logger = $params{logger};
1479              
1480 0         0 foreach my $port_id (keys %$vlans) {
1481             # safety check
1482 0 0       0 if (! exists $ports->{$port_id}) {
1483 0 0       0 $logger->error(
1484             "invalid interface ID $port_id while setting vlans, aborting"
1485             ) if $logger;
1486 0         0 last;
1487             }
1488 0         0 $ports->{$port_id}->{VLANS}->{VLAN} = $vlans->{$port_id};
1489             }
1490             }
1491              
1492             sub _getVlans {
1493 0     0   0 my (%params) = @_;
1494              
1495 0         0 my $snmp = $params{snmp};
1496              
1497 0         0 my $results;
1498 0         0 my $vtpVlanName = $snmp->walk('.1.3.6.1.4.1.9.9.46.1.3.1.1.4.1');
1499 0         0 my $vmPortStatus = $snmp->walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2');
1500              
1501             # each result matches either of the following schemes:
1502             # $prefix.$i.$j = $value, with $j as port id, and $value as vlan id
1503             # $prefix.$i = $value, with $i as port id, and $value as vlan id
1504             # work with Cisco and Juniper switches
1505 0 0 0     0 if($vtpVlanName and $vmPortStatus){
1506 0         0 foreach my $suffix (sort keys %{$vmPortStatus}) {
  0         0  
1507 0         0 my $port_id = _getElement($suffix, -1);
1508 0         0 my $vlan_id = $vmPortStatus->{$suffix};
1509 0         0 my $name = $vtpVlanName->{$vlan_id};
1510              
1511 0         0 push @{$results->{$port_id}}, {
  0         0  
1512             NUMBER => $vlan_id,
1513             NAME => $name
1514             };
1515             }
1516             }
1517              
1518             # For other switches, we use another method
1519 0         0 my $vlanId = $snmp->walk('.1.0.8802.1.1.2.1.5.32962.1.2.1.1.1');
1520 0 0       0 if($vlanId){
1521 0         0 while (my ($port, $vlan) = each %{$vlanId}) {
  0         0  
1522 0         0 push @{$results->{$port}}, {
  0         0  
1523             NUMBER => $vlan,
1524             NAME => "VLAN " . $vlan
1525             };
1526             }
1527             }
1528              
1529 0         0 return $results;
1530             }
1531              
1532             sub _setTrunkPorts {
1533 0     0   0 my (%params) = @_;
1534              
1535             my $trunk_ports = _getTrunkPorts(
1536             snmp => $params{snmp},
1537 0         0 );
1538 0 0       0 return unless $trunk_ports;
1539              
1540 0         0 my $ports = $params{ports};
1541 0         0 my $logger = $params{logger};
1542              
1543 0         0 foreach my $port_id (keys %$trunk_ports) {
1544             # safety check
1545 0 0       0 if (! exists $ports->{$port_id}) {
1546 0 0       0 $logger->error(
1547             "invalid interface ID $port_id while setting trunk flag, " .
1548             "aborting"
1549             ) if $logger;
1550 0         0 last;
1551             }
1552 0         0 $ports->{$port_id}->{TRUNK} = $trunk_ports->{$port_id};
1553             }
1554             }
1555              
1556             sub _getTrunkPorts {
1557 1     1   5 my (%params) = @_;
1558              
1559 1         2 my $snmp = $params{snmp};
1560              
1561 1         2 my $results;
1562              
1563             # cisco use vlanTrunkPortDynamicStatus, using the following schema:
1564             # prefix.x = value
1565             # x is the interface id
1566             # value is 1 for trunk, 2 for access
1567 1         3 my $vlanStatus = $snmp->walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.14');
1568 1 50       4 if ($vlanStatus) {
1569 1         1 while (my ($interface_id, $value) = each %{$vlanStatus}) {
  4         10  
1570 3 100       7 $results->{$interface_id} = $value == 1 ? 1 : 0;
1571             }
1572 1         4 return $results;
1573             }
1574              
1575             # juniper use jnxExVlanPortAccessMode, using the following schema:
1576             # prefix.x.y = value
1577             # x is the vlan id
1578             # y is the port id
1579             # value is 1 for access, 2 for trunk
1580 0           my $accessMode = $snmp->walk('.1.3.6.1.4.1.2636.3.40.1.5.1.7.1.5');
1581 0 0         if ($accessMode) {
1582 0           my $port2interface = $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2');
1583 0           while (my ($suffix, $value) = each %{$accessMode}) {
  0            
1584 0           my $port_id = _getElement($suffix, -1);
1585 0           my $interface_id = $port2interface->{$port_id};
1586 0 0         $results->{$interface_id} = $value == 2 ? 1 : 0;
1587             }
1588 0           return $results;
1589             }
1590              
1591              
1592             # others use lldpXdot1LocPortVlanId
1593             # prefix.x = value
1594             # x is either an interface or a port id
1595             # value is the vlan id, 0 for trunk
1596 0           my $vlanId = $snmp->walk('.1.0.8802.1.1.2.1.5.32962.1.2.1.1.1');
1597 0 0         if ($vlanId) {
1598 0           my $port2interface = $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2');
1599 0           while (my ($id, $value) = each %{$vlanId}) {
  0            
1600             my $interface_id =
1601             ! exists $port2interface->{$id} ? $id :
1602 0 0         $port2interface->{$id};
1603 0 0         $results->{$interface_id} = $value == 0 ? 1 : 0;
1604             }
1605 0           return $results;
1606             }
1607              
1608 0           return;
1609             }
1610              
1611             sub _setAggregatePorts {
1612 0     0     my (%params) = @_;
1613              
1614 0           my $ports = $params{ports};
1615 0           my $logger = $params{logger};
1616              
1617 0           my $lacp_info = _getLACPInfo(%params);
1618 0 0         if ($lacp_info) {
1619 0           foreach my $interface_id (keys %$lacp_info) {
1620             # safety check
1621 0 0         if (!$ports->{$interface_id}) {
1622 0 0         $logger->warning(
1623             "unknown interface $interface_id in LACP info, ignoring"
1624             ) if $logger;
1625 0           next;
1626             }
1627 0           $ports->{$interface_id}->{AGGREGATE}->{PORT} = $lacp_info->{$interface_id};
1628             }
1629             }
1630              
1631 0           my $pagp_info = _getPAGPInfo(%params);
1632 0 0         if ($pagp_info) {
1633 0           foreach my $interface_id (keys %$pagp_info) {
1634             # safety check
1635 0 0         if (!$ports->{$interface_id}) {
1636 0 0         $logger->error(
1637             "unknown interface $interface_id in PAGP info, ignoring"
1638             ) if $logger;
1639 0           next;
1640             }
1641 0           $ports->{$interface_id}->{AGGREGATE}->{PORT} = $pagp_info->{$interface_id};
1642             }
1643             }
1644             }
1645              
1646             sub _getLACPInfo {
1647 0     0     my (%params) = @_;
1648              
1649 0           my $snmp = $params{snmp};
1650              
1651 0           my $results;
1652 0           my $aggPortAttachedAggID = $snmp->walk('.1.2.840.10006.300.43.1.2.1.1.13');
1653              
1654 0           foreach my $interface_id (sort keys %$aggPortAttachedAggID) {
1655 0           my $aggregator_id = $aggPortAttachedAggID->{$interface_id};
1656 0 0         next if $aggregator_id == 0;
1657 0 0         next if $aggregator_id == $interface_id;
1658 0           push @{$results->{$aggregator_id}}, $interface_id;
  0            
1659             }
1660              
1661 0           return $results;
1662             }
1663              
1664             sub _getPAGPInfo {
1665 0     0     my (%params) = @_;
1666              
1667 0           my $snmp = $params{snmp};
1668              
1669 0           my $results;
1670 0           my $pagpPorts = $snmp->walk('.1.3.6.1.4.1.9.9.98.1.1.1.1.5');
1671              
1672 0           foreach my $port_id (sort keys %$pagpPorts) {
1673 0           my $portShortNum = $pagpPorts->{$port_id};
1674 0 0         next unless $portShortNum > 0;
1675 0           my $aggregatePort_id = $portShortNum + 5000;
1676 0           push @{$results->{$aggregatePort_id}}, $port_id;
  0            
1677             }
1678              
1679 0           return $results;
1680             }
1681              
1682             1;
1683             __END__