File Coverage

blib/lib/FusionInventory/Agent/Tools/Hardware.pm
Criterion Covered Total %
statement 238 624 38.1
branch 87 384 22.6
condition 17 78 21.7
subroutine 21 40 52.5
pod 2 2 100.0
total 365 1128 32.3


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Tools::Hardware;
2              
3 2     2   9450291 use strict;
  2         11  
  2         81  
4 2     2   17 use warnings;
  2         4  
  2         124  
5 2     2   10 use base 'Exporter';
  2         72  
  2         310  
6              
7 2     2   10 use English qw(-no_match_vars);
  2         4  
  2         29  
8              
9 2     2   2139 use FusionInventory::Agent::Tools;
  2         6  
  2         378  
10 2     2   1176 use FusionInventory::Agent::Tools::Network;
  2         7  
  2         17257  
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 3     3 1 1512 my (%params) = @_;
263              
264 3         7 my $snmp = $params{snmp};
265 3         6 my $datadir = $params{datadir};
266 3         5 my $logger = $params{logger};
267              
268 3         4 my $device;
269              
270             # manufacturer, type and model identification attempt, using sysObjectID
271 3         11 my $sysobjectid = $snmp->get('.1.3.6.1.2.1.1.2.0');
272 3 100       9 if ($sysobjectid) {
273 2         7 my $match = _getSysObjectIDInfo(
274             id => $sysobjectid,
275             datadir => $datadir,
276             logger => $logger
277             );
278 2 100       10 $device->{TYPE} = $match->{type} if $match->{type};
279 2 50       7 $device->{MODEL} = $match->{model} if $match->{model};
280             $device->{MANUFACTURER} = $match->{manufacturer}
281 2 100       9 if $match->{manufacturer};
282             }
283              
284             # vendor and type identification attempt, using sysDescr
285 3         15 my $sysdescr = $snmp->get('.1.3.6.1.2.1.1.1.0');
286 3 50       9 if ($sysdescr) {
287              
288             # first word
289 3         14 my ($first_word) = $sysdescr =~ /(\S+)/;
290 3         9 my $result = $sysdescr_first_word{lc($first_word)};
291              
292 3 50       8 if ($result) {
293 0 0       0 $device->{VENDOR} = $result->{vendor} if $result->{vendor};
294 0 0       0 $device->{TYPE} = $result->{type} if $result->{type};
295             }
296              
297             # whole sysdescr value
298 3         8 foreach my $rule (@sysdescr_rules) {
299 9 50       41 next unless $sysdescr =~ $rule->{match};
300 0 0       0 $device->{VENDOR} = $rule->{vendor} if $rule->{vendor};
301 0 0       0 $device->{TYPE} = $rule->{type} if $rule->{type};
302 0         0 last;
303             }
304 3         12 $device->{DESCRIPTION} = $sysdescr;
305             }
306              
307             # fallback type identification attempt, using type-specific OID presence
308 3 100       9 if (!exists $device->{TYPE}) {
309 2 50 33     7 if (
310             $snmp->get('.1.3.6.1.2.1.43.11.1.1.6.1.1') ||
311             $snmp->get('.1.3.6.1.2.1.25.3.2.1.3.1')
312             ) {
313 0         0 $device->{TYPE} = 'PRINTER'
314             }
315             }
316              
317             # fallback model identification attempt, using type-specific OID value
318 3 50       8 if (!exists $device->{MODEL}) {
319 3 50 66     21 my $model = exists $device->{TYPE} && $device->{TYPE} eq 'PRINTER' ?
320             $snmp->get('.1.3.6.1.2.1.25.3.2.1.3.1') :
321             $snmp->get('.1.3.6.1.2.1.47.1.1.1.1.13.1') ;
322 3 50       37 $device->{MODEL} = $model if $model;
323             }
324              
325             # fallback manufacturer identification attempt, using type-agnostic OID
326 3 100       11 if (!exists $device->{MANUFACTURER}) {
327 2         6 my $manufacturer = $snmp->get('.1.3.6.1.2.1.43.8.2.1.14.1.1');
328 2 50       6 $device->{MANUFACTURER} = $manufacturer if $manufacturer;
329             }
330              
331             # fallback vendor, using manufacturer
332 3 100 33     16 if (!exists $device->{VENDOR} && exists $device->{MANUFACTURER}) {
333 1         4 $device->{VENDOR} = $device->{MANUFACTURER};
334             }
335              
336             # remaining informations
337 3         13 foreach my $key (keys %base_variables) {
338 21         34 my $variable = $base_variables{$key};
339              
340 21         22 my $raw_value;
341 21 100       102 if (ref $variable->{oid} eq 'ARRAY') {
342 3         4 foreach my $oid (@{$variable->{oid}}) {
  3         9  
343 6         16 $raw_value = $snmp->get($oid);
344 6 50       14 last if defined $raw_value;
345             }
346             } else {
347 18         50 $raw_value = $snmp->get($variable->{oid});
348             }
349 21 50       49 next unless defined $raw_value;
350              
351 0         0 my $type = $variable->{type};
352 0 0       0 my $value =
    0          
    0          
353             $type eq 'memory' ? _getCanonicalMemory($raw_value) :
354             $type eq 'string' ? _getCanonicalString($raw_value) :
355             $type eq 'count' ? _getCanonicalCount($raw_value) :
356             $raw_value;
357              
358 0 0       0 $device->{$key} = $value if defined $value;
359             }
360              
361 3         12 my $mac = _getMacAddress($snmp);
362 3 50       10 $device->{MAC} = $mac if $mac;
363              
364 3         13 my $serial = _getSerial($snmp, $device->{TYPE});
365 3 50       7 $device->{SERIAL} = $serial if $serial;
366              
367 3         12 my $firmware = _getFirmware($snmp, $device->{TYPE});
368 3 50       8 $device->{FIRMWARE} = $firmware if $firmware;
369              
370 3         10 my $results = $snmp->walk('.1.3.6.1.2.1.4.20.1.1');
371             $device->{IPS}->{IP} = [
372 3 50       8 sort values %{$results}
  0         0  
373             ] if $results;
374              
375 3         12 return $device;
376             }
377              
378             sub _getSysObjectIDInfo {
379 2     2   8 my (%params) = @_;
380              
381 2 50       6 return unless $params{id};
382              
383 2 50       10 _loadSysObjectIDDatabase(%params) if !%sysobjectid;
384              
385 2         6 my $logger = $params{logger};
386 2         14 my $prefix = qr/(?:
387             SNMPv2-SMI::enterprises |
388             iso\.3\.6\.1\.4\.1 |
389             \.1\.3\.6\.1\.4\.1
390             )/x;
391             my ($manufacturer_id, $device_id) =
392 2         98 $params{id} =~ /^ $prefix \. (\d+) (?:\. ([\d.]+))? $/x;
393              
394 2 50       11 if (!$manufacturer_id) {
395 0 0       0 $logger->debug("invalid sysobjectID $params{id}: no manufacturer ID")
396             if $logger;
397 0         0 return;
398             }
399              
400 2 50       6 if (!$device_id) {
401 0 0       0 $logger->debug("invalid sysobjectID $params{id}: no device ID")
402             if $logger;
403             }
404              
405 2         4 my $match;
406              
407             # attempt full match first
408 2 50       7 if ($device_id) {
409 2         6 $match = $sysobjectid{$manufacturer_id . '.' . $device_id};
410 2 50       7 if ($match) {
411 0 0       0 $logger->debug(
412             "full match for sysobjectID $params{id} in database"
413             ) if $logger;
414 0         0 return $match;
415             }
416             }
417              
418             # fallback to partial match
419 2         5 $match = $sysobjectid{$manufacturer_id};
420 2 100       9 if ($match) {
421 1 50       4 $logger->debug(
422             "partial match for sysobjectID $params{id} in database: ".
423             "unknown device ID"
424             ) if $logger;
425 1         6 return $match;
426             }
427              
428             # no match
429             $logger->debug(
430 1 50       7 "no match for sysobjectID $params{id} in database: " .
431             "unknown manufacturer ID"
432             ) if $logger;
433 1         6 return;
434             }
435              
436             sub _loadSysObjectIDDatabase {
437 2     2   6 my (%params) = @_;
438              
439 2 100       7 return unless $params{datadir};
440              
441 1         7 my $handle = getFileHandle(file => "$params{datadir}/sysobject.ids");
442 1 50       4 return unless $handle;
443              
444 1         29 while (my $line = <$handle>) {
445 3595 100       7310 next if $line =~ /^#/;
446 3581 100       7849 next if $line =~ /^$/;
447 3517         3908 chomp $line;
448 3517         9603 my ($id, $manufacturer, $type, $model) = split(/\t/, $line);
449 3517         19936 $sysobjectid{$id} = {
450             manufacturer => $manufacturer,
451             type => $type,
452             model => $model
453             };
454             }
455              
456 1         70 close $handle;
457             }
458              
459             sub _getSerial {
460 3     3   7 my ($snmp, $type) = @_;
461              
462             # Entity-MIB::entPhysicalSerialNum
463 3         19 my $entPhysicalSerialNum = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.11');
464 3 50       8 return _getCanonicalSerialNumber($entPhysicalSerialNum)
465             if $entPhysicalSerialNum;
466              
467             # Printer-MIB::prtGeneralSerialNumber
468 3         9 my $prtGeneralSerialNumber = $snmp->get_first('.1.3.6.1.2.1.43.5.1.1.17');
469 3 50       9 return _getCanonicalSerialNumber($prtGeneralSerialNumber)
470             if $prtGeneralSerialNumber;
471              
472             # vendor specific OIDs
473 3         12 my @oids = (
474             '.1.3.6.1.4.1.2636.3.1.3.0', # Juniper-MIB
475             '.1.3.6.1.4.1.248.14.1.1.9.1.10.1', # Hirschman MIB
476             '.1.3.6.1.4.1.253.8.53.3.2.1.3.1', # Xerox-MIB
477             '.1.3.6.1.4.1.367.3.2.1.2.1.4.0', # Ricoh-MIB
478             '.1.3.6.1.4.1.641.2.1.2.1.6.1', # Lexmark-MIB
479             '.1.3.6.1.4.1.1602.1.2.1.4.0', # Canon-MIB
480             '.1.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.1.0', # Brother-MIB
481             '.1.3.6.1.4.1.318.1.1.4.1.5.0', # MasterSwitch-MIB
482             '.1.3.6.1.4.1.6027.3.8.1.1.5.0', # F10-C-SERIES-CHASSIS-MIB
483             '.1.3.6.1.4.1.6027.3.10.1.2.2.1.12.1', # FORCE10-SMI
484             );
485 3         6 foreach my $oid (@oids) {
486 30         74 my $value = $snmp->get($oid);
487 30 50       66 next unless $value;
488 0         0 return _getCanonicalSerialNumber($value);
489             }
490              
491 3         9 return;
492             }
493              
494             sub _getFirmware {
495 3     3   6 my ($snmp, $type) = @_;
496              
497 3         8 my $entPhysicalSoftwareRev = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.10');
498 3 50       7 return $entPhysicalSoftwareRev if $entPhysicalSoftwareRev;
499              
500 3         10 my $entPhysicalFirmwareRev = $snmp->get_first('.1.3.6.1.2.1.47.1.1.1.1.9');
501 3 50       10 return $entPhysicalFirmwareRev if $entPhysicalFirmwareRev;
502              
503 3         8 my $ios_version = $snmp->get('.1.3.6.1.4.1.9.9.25.1.1.1.2.5');
504 3 50       7 return $ios_version if $ios_version;
505              
506 3         9 my $firmware = $snmp->get('.1.3.6.1.4.1.248.14.1.1.2.0');
507 3 50       7 return $firmware if $firmware;
508              
509 3         6 return;
510             }
511              
512             sub _getMacAddress {
513 3     3   4 my ($snmp) = @_;
514              
515             # use BRIDGE-MIB::dot1dBaseBridgeAddress if available
516 3         5 my $address_oid = ".1.3.6.1.2.1.17.1.1.0";
517 3         10 my $address = _getCanonicalMacAddress($snmp->get($address_oid));
518              
519 3 50 33     9 return $address if $address && $address =~ /^$mac_address_pattern$/;
520              
521             # fallback on ports addresses (IF-MIB::ifPhysAddress) if unique
522 3         5 my $addresses_oid = ".1.3.6.1.2.1.2.2.1.6";
523 3         14 my $addresses = $snmp->walk($addresses_oid);
524             my @addresses =
525             uniq
526 0         0 grep { $_ ne '00:00:00:00:00:00' }
527 0         0 grep { $_ }
528 0         0 map { _getCanonicalMacAddress($_) }
529 3         7 values %{$addresses};
  3         18  
530              
531 3 50 33     11 return $addresses[0] if @addresses && @addresses == 1;
532              
533 3         8 return;
534             }
535              
536             sub getDeviceFullInfo {
537 0     0 1 0 my (%params) = @_;
538              
539 0         0 my $snmp = $params{snmp};
540 0         0 my $logger = $params{logger};
541              
542             # first, let's retrieve basic device informations
543 0         0 my $info = getDeviceInfo(%params);
544 0 0       0 return unless $info;
545              
546             # description is defined as DESCRIPTION for discovery
547             # and COMMENTS for inventory
548 0 0       0 if (exists $info->{DESCRIPTION}) {
549 0         0 $info->{COMMENTS} = $info->{DESCRIPTION};
550 0         0 delete $info->{DESCRIPTION};
551             }
552              
553             # host name is defined as SNMPHOSTNAME for discovery
554             # and NAME for inventory
555 0 0       0 if (exists $info->{SNMPHOSTNAME}) {
556 0         0 $info->{NAME} = $info->{SNMPHOSTNAME};
557 0         0 delete $info->{SNMPHOSTNAME};
558             }
559              
560             # device ID is set from the server request
561 0         0 $info->{ID} = $params{id};
562              
563             # device TYPE is set either:
564             # - from the server request,
565             # - from initial identification
566 0   0     0 $info->{TYPE} = $params{type} || $info->{TYPE};
567              
568             # second, use results to build the object
569 0         0 my $device = { INFO => $info };
570              
571 0         0 _setGenericProperties(
572             device => $device,
573             snmp => $snmp,
574             logger => $logger
575             );
576              
577             _setPrinterProperties(
578             device => $device,
579             snmp => $snmp,
580             logger => $logger,
581             datadir => $params{datadir}
582 0 0 0     0 ) if $info->{TYPE} && $info->{TYPE} eq 'PRINTER';
583              
584             _setNetworkingProperties(
585             device => $device,
586             snmp => $snmp,
587             logger => $logger,
588             datadir => $params{datadir}
589 0 0 0     0 ) if $info->{TYPE} && $info->{TYPE} eq 'NETWORKING';
590              
591             # convert ports hashref to an arrayref, sorted by interface number
592 0         0 my $ports = $device->{PORTS}->{PORT};
593 0 0 0     0 if ($ports && %$ports) {
594             $device->{PORTS}->{PORT} = [
595 0         0 map { $ports->{$_} }
596 0         0 sort { $a <=> $b }
597 0         0 keys %{$ports}
  0         0  
598             ];
599             } else {
600 0         0 delete $device->{PORTS};
601             }
602              
603 0         0 return $device;
604             }
605              
606             sub _setGenericProperties {
607 0     0   0 my (%params) = @_;
608              
609 0         0 my $device = $params{device};
610 0         0 my $snmp = $params{snmp};
611 0         0 my $logger = $params{logger};
612              
613             # ports is a sparse hash of network ports, indexed by interface identifier
614             # (ifIndex, or IFNUMBER in agent output)
615 0         0 my $ports;
616              
617 0         0 foreach my $key (keys %interface_variables) {
618 0         0 my $variable = $interface_variables{$key};
619 0 0       0 next unless $variable->{oid};
620              
621 0         0 my $results;
622 0 0       0 if (ref $variable->{oid} eq 'ARRAY') {
623 0         0 foreach my $oid (@{$variable->{oid}}) {
  0         0  
624 0         0 $results = $snmp->walk($oid);
625 0 0       0 last if $results;
626             }
627             } else {
628 0         0 $results = $snmp->walk($variable->{oid});
629             }
630 0 0       0 next unless $results;
631              
632 0         0 my $type = $variable->{type};
633             # each result matches the following scheme:
634             # $prefix.$i = $value, with $i as port id
635 0         0 while (my ($suffix, $raw_value) = each %{$results}) {
  0         0  
636 0 0       0 my $value =
    0          
    0          
    0          
637             $type eq 'mac' ? _getCanonicalMacAddress($raw_value) :
638             $type eq 'constant' ? _getCanonicalConstant($raw_value) :
639             $type eq 'string' ? _getCanonicalString($raw_value) :
640             $type eq 'count' ? _getCanonicalCount($raw_value) :
641             $raw_value;
642 0 0       0 $ports->{$suffix}->{$key} = $value if defined $value;
643             }
644             }
645              
646 0         0 my $highspeed_results = $snmp->walk('.1.3.6.1.2.1.31.1.1.1.15');
647 0         0 my $speed_results = $snmp->walk('.1.3.6.1.2.1.2.2.1.5');
648             # ifSpeed is expressed in b/s, and available for all interfaces
649             # HighSpeed is expressed in Mb/s, available for fast interfaces only
650 0         0 while (my ($suffix, $speed_value) = each %{$speed_results}) {
  0         0  
651 0         0 my $highspeed_value = $highspeed_results->{$suffix};
652 0 0       0 $ports->{$suffix}->{IFSPEED} = $highspeed_value ?
653             $highspeed_value * 1000 * 1000 : $speed_value;
654             }
655              
656 0         0 my $results = $snmp->walk('.1.3.6.1.2.1.4.20.1.2');
657             # each result matches the following scheme:
658             # $prefix.$i.$j.$k.$l = $value
659             # with $i.$j.$k.$l as IP address, and $value as port id
660 0         0 foreach my $suffix (sort keys %{$results}) {
  0         0  
661 0         0 my $value = $results->{$suffix};
662 0 0       0 next unless $value;
663             # safety checks
664 0 0       0 if (! exists $ports->{$value}) {
665 0 0       0 $logger->warning(
666             "unknown interface $value for IP address $suffix, ignoring"
667             ) if $logger;
668 0         0 next;
669             }
670 0 0       0 if ($suffix !~ /^$ip_address_pattern$/) {
671 0 0       0 $logger->error("invalid IP address $suffix") if $logger;
672 0         0 next;
673             }
674 0         0 $ports->{$value}->{IP} = $suffix;
675 0         0 push @{$ports->{$value}->{IPS}->{IP}}, $suffix;
  0         0  
676             }
677              
678 0         0 $device->{PORTS}->{PORT} = $ports;
679             }
680              
681             sub _setPrinterProperties {
682 0     0   0 my (%params) = @_;
683              
684 0         0 my $device = $params{device};
685 0         0 my $snmp = $params{snmp};
686 0         0 my $logger = $params{logger};
687              
688             # colors
689 0         0 my $colors = $snmp->walk('.1.3.6.1.2.1.43.12.1.1.4.1');
690              
691             # consumable levels
692 0         0 my $color_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.3.1');
693 0         0 my $type_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.5.1');
694 0         0 my $descriptions = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.6.1');
695 0         0 my $unit_ids = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.7.1');
696 0         0 my $max_levels = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.8.1');
697 0         0 my $current_levels = $snmp->walk('.1.3.6.1.2.1.43.11.1.1.9.1');
698              
699 0         0 foreach my $consumable_id (sort keys %$descriptions) {
700 0         0 my $max = $max_levels->{$consumable_id};
701 0         0 my $current = $current_levels->{$consumable_id};
702 0 0 0     0 next unless defined $max and defined $current;
703              
704             # consumable identification
705 0         0 my $type_id = $type_ids->{$consumable_id};
706 0         0 my $color_id = $color_ids->{$consumable_id};
707              
708 0         0 my $type;
709 0 0       0 if ($type_id != 1) {
710 0         0 $type = $consumable_types{$type_id};
711             } else {
712             # fallback on description
713 0         0 my $description = $descriptions->{$consumable_id};
714 0 0       0 $type =
    0          
    0          
715             $description =~ /maintenance/i ? 'MAINTENANCEKIT' :
716             $description =~ /fuser/i ? 'FUSERKIT' :
717             $description =~ /transfer/i ? 'TRANSFERKIT' :
718             undef ;
719             }
720              
721 0 0       0 if (!$type) {
722 0 0       0 $logger->debug("unknown consumable type $type_id") if $logger;
723 0         0 next;
724             }
725              
726 0 0 0     0 if ($type eq 'TONER' || $type eq 'DRUM' || $type eq 'CARTRIDGE' || $type eq 'DEVELOPER') {
      0        
      0        
727 0         0 my $color;
728 0 0       0 if ($color_id) {
729 0         0 $color = _getCanonicalString($colors->{$color_id});
730 0 0       0 if (!$color) {
731 0 0       0 $logger->debug("invalid color ID $color_id") if $logger;
732 0         0 next;
733             }
734             } else {
735             # fallback on description
736 0         0 my $description = $descriptions->{$consumable_id};
737 0 0       0 $color =
    0          
    0          
    0          
738             $description =~ /cyan/i ? 'cyan' :
739             $description =~ /magenta/i ? 'magenta' :
740             $description =~ /(yellow|jaune)/i ? 'yellow' :
741             $description =~ /(black|noir)/i ? 'black' :
742             'black' ;
743             }
744 0         0 $type .= uc($color);
745             }
746              
747 0         0 my $value;
748 0 0       0 if ($current == -2) {
    0          
749             # A value of -2 means unknown
750 0         0 $value = undef;
751             } elsif ($current == -3) {
752             # A value of -3 means that the printer knows that there is some
753             # supply/remaining space, respectively.
754 0         0 $value = 'OK';
755             } else {
756 0 0       0 if ($max != -2) {
757 0         0 $value = _getPercentValue($max, $current);
758             } else {
759             # PrtMarkerSuppliesSupplyUnitTC in Printer MIB
760 0         0 my $unit_id = $unit_ids->{$consumable_id};
761 0 0       0 $value =
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
762             $unit_id == 19 ? $current :
763             $unit_id == 18 ? $current . 'items' :
764             $unit_id == 17 ? $current . 'm' :
765             $unit_id == 16 ? $current . 'feet' :
766             $unit_id == 15 ? ($current / 10) . 'ml' :
767             $unit_id == 13 ? ($current / 10) . 'g' :
768             $unit_id == 11 ? $current . 'hours' :
769             $unit_id == 8 ? $current . 'sheets' :
770             $unit_id == 7 ? $current . 'impressions' :
771             $unit_id == 4 ? ($current / 1000) . 'mm' :
772             $current . '?' ;
773             }
774             }
775              
776 0         0 $device->{CARTRIDGES}->{$type} = $value;
777             }
778              
779             # page counters
780 0         0 foreach my $key (keys %printer_pagecounters_variables) {
781 0         0 my $variable = $printer_pagecounters_variables{$key};
782 0         0 my $value;
783 0 0       0 if (ref $variable->{oid} eq 'ARRAY') {
784 0         0 foreach my $oid (@{$variable->{oid}}) {
  0         0  
785 0         0 $value = $snmp->get($oid);
786 0 0       0 last if $value;
787             }
788             } else {
789 0         0 my $oid = $variable->{oid};
790 0         0 $value = $snmp->get($oid);
791             }
792 0 0       0 next unless defined $value;
793 0 0       0 if (!_isInteger($value)) {
794 0 0       0 $logger->error("incorrect counter value $value, check $variable->{mapping} mapping") if $logger;
795 0         0 next;
796             }
797 0         0 $device->{PAGECOUNTERS}->{$key} = $value;
798             }
799             }
800              
801             sub _setNetworkingProperties {
802 0     0   0 my (%params) = @_;
803              
804 0         0 my $device = $params{device};
805 0         0 my $snmp = $params{snmp};
806 0         0 my $logger = $params{logger};
807              
808 0         0 my $ports = $device->{PORTS}->{PORT};
809              
810 0         0 _setVlans(
811             snmp => $snmp,
812             ports => $ports,
813             logger => $logger
814             );
815              
816 0         0 _setTrunkPorts(
817             snmp => $snmp,
818             ports => $ports,
819             logger => $logger
820             );
821              
822             _setConnectedDevices(
823             snmp => $snmp,
824             ports => $ports,
825             logger => $logger,
826             vendor => $device->{INFO}->{MANUFACTURER}
827 0         0 );
828              
829 0         0 _setKnownMacAddresses(
830             snmp => $snmp,
831             ports => $ports,
832             logger => $logger,
833             );
834              
835 0         0 _setAggregatePorts(
836             snmp => $snmp,
837             ports => $ports,
838             logger => $logger
839             );
840             }
841              
842             sub _getPercentValue {
843 0     0   0 my ($value1, $value2) = @_;
844              
845 0 0 0     0 return unless defined $value1 && _isInteger($value1);
846 0 0 0     0 return unless defined $value2 && _isInteger($value2);
847 0 0       0 return if $value1 == 0;
848              
849             return int(
850 0         0 ( 100 * $value2 ) / $value1
851             );
852             }
853              
854             sub _isInteger {
855 0     0   0 $_[0] =~ /^[+-]?\d+$/;
856             }
857              
858             sub _getCanonicalMacAddress {
859 6     6   998 my ($value) = @_;
860              
861 6 100       19 return unless $value;
862              
863 3         3 my $result;
864 3 100       21 if ($value =~ /$mac_address_pattern/) {
    50          
865             # this was stored as a string, it just has to be normalized
866             $result = sprintf
867             "%02x:%02x:%02x:%02x:%02x:%02x",
868 1         4 map { hex($_) } split(':', $value);
  6         16  
869             } elsif ($value =~ /^\d{2}:\d{2}:\d{2}:\d{2}:\d{2}$/) {
870             # WWN format
871 0         0 $result = '10:00:00:' . $value;
872             } else {
873             # this was stored as an hex-string
874             # 0xD205A86C26D5 or 0x6001D205A86C26D5
875 2 50       11 if ($value =~ /^0x[0-9A-F]{0,4}([0-9A-F]{12})$/i) {
876             # value translated by Net::SNMP
877 2         11 $result = alt2canonical('0x'.$1);
878             } else {
879             # packed value, onvert from binary to hexadecimal
880 0         0 $result = unpack 'H*', $value;
881             }
882             }
883              
884 3 50       12 return if $result eq '00:00:00:00:00:00';
885 3         16 return lc($result);
886             }
887              
888             sub _getCanonicalString {
889 6     6   9 my ($value) = @_;
890              
891 6         17 $value = hex2char($value);
892 6 100       15 return unless $value;
893              
894             # truncate after first null-character
895 4         5 $value =~ s/\000.*$//;
896              
897             # unquote string
898 4         9 $value =~ s/^\\?["']//;
899 4         6 $value =~ s/\\?["']$//;
900              
901 4 50       19 return unless $value;
902              
903 4         9 return $value;
904             }
905              
906             sub _getCanonicalSerialNumber {
907 0     0   0 my ($value) = @_;
908              
909 0         0 $value = hex2char($value);
910 0 0       0 return unless $value;
911              
912 0         0 $value =~ s/[[:^print:]]//g;
913 0         0 $value =~ s/^\s+//;
914 0         0 $value =~ s/\s+$//;
915 0         0 $value =~ s/\.{2,}//g;
916 0 0       0 return unless $value;
917              
918 0         0 return $value;
919             }
920              
921             sub _getCanonicalMemory {
922 0     0   0 my ($value) = @_;
923              
924 0 0       0 if ($value =~ /^(\d+) KBytes$/) {
925 0         0 return int($1 / 1024);
926             } else {
927 0         0 return int($value / 1024 / 1024);
928             }
929             }
930              
931             sub _getCanonicalConstant {
932 0     0   0 my ($value) = @_;
933              
934 0 0       0 return $value if _isInteger($value);
935 0 0       0 return $1 if $value =~ /\((\d+)\)$/;
936             }
937              
938             sub _getCanonicalCount {
939 0     0   0 my ($value) = @_;
940              
941 0 0       0 return _isInteger($value) ? $value : undef;
942             }
943              
944             sub _getElement {
945 6     6   1210 my ($oid, $index) = @_;
946              
947 6         25 my @array = split(/\./, $oid);
948 6         25 return $array[$index];
949             }
950              
951             sub _getElements {
952 2     2   5 my ($oid, $first, $last) = @_;
953              
954 2         10 my @array = split(/\./, $oid);
955 2         19 return @array[$first .. $last];
956             }
957              
958             sub _setKnownMacAddresses {
959 4     4   20 my (%params) = @_;
960              
961 4         10 my $snmp = $params{snmp};
962 4         5 my $ports = $params{ports};
963 4         6 my $logger = $params{logger};
964              
965             # start with mac addresses seen on default VLAN
966 4         9 my $addresses = _getKnownMacAddresses(
967             snmp => $snmp,
968             address2port => '.1.3.6.1.2.1.17.4.3.1.2', # dot1dTpFdbPort
969             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
970             );
971              
972 4 50       11 if ($addresses) {
973 4         10 _addKnownMacAddresses(
974             ports => $ports,
975             logger => $logger,
976             addresses => $addresses,
977             );
978             }
979              
980             # add additional mac addresses for other VLANs
981 4         9 $addresses = _getKnownMacAddresses(
982             snmp => $snmp,
983             address2port => '.1.3.6.1.2.1.17.7.1.2.2.1.2', # dot1qTpFdbPort
984             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
985             );
986              
987 4 50       14 if ($addresses) {
988 0         0 _addKnownMacAddresses(
989             ports => $ports,
990             logger => $logger,
991             addresses => $addresses,
992             );
993             } else {
994             # compute the list of vlans associated with at least one port
995             # without CDP/LLDP information
996 4         6 my @vlans;
997 4         10 my %seen = ( 1 => 1 );
998 4         10 foreach my $port (values %$ports) {
999             next if
1000             exists $port->{CONNECTIONS} &&
1001             exists $port->{CONNECTIONS}->{CDP} &&
1002 4 50 33     24 $port->{CONNECTIONS}->{CDP};
      66        
1003 3 50       8 next unless exists $port->{VLANS};
1004             push @vlans,
1005 0         0 grep { !$seen{$_}++ }
1006 0         0 map { $_->{NUMBER} }
1007 0         0 @{$port->{VLANS}->{VLAN}};
  0         0  
1008             }
1009              
1010             # get additional associated mac addresses from those vlans
1011 4         7 foreach my $vlan (@vlans) {
1012 0 0       0 $logger->debug("switching SNMP context to vlan $vlan") if $logger;
1013 0         0 $snmp->switch_vlan_context($vlan);
1014 0         0 my $mac_addresses = _getKnownMacAddresses(
1015             snmp => $snmp,
1016             address2port => '.1.3.6.1.2.1.17.4.3.1.2', # dot1dTpFdbPort
1017             port2interface => '.1.3.6.1.2.1.17.1.4.1.2', # dot1dBasePortIfIndex
1018             );
1019 0 0       0 next unless $mac_addresses;
1020              
1021 0         0 _addKnownMacAddresses(
1022             ports => $ports,
1023             logger => $logger,
1024             addresses => $mac_addresses,
1025             );
1026             }
1027 4 50       18 $snmp->reset_original_context() if @vlans;
1028             }
1029              
1030             }
1031              
1032             sub _addKnownMacAddresses {
1033 4     4   13 my (%params) = @_;
1034              
1035 4         6 my $ports = $params{ports};
1036 4         28 my $logger = $params{logger};
1037 4         5 my $mac_addresses = $params{addresses};
1038              
1039 4         11 foreach my $port_id (keys %$mac_addresses) {
1040             # safety check
1041 4 50       10 if (! exists $ports->{$port_id}) {
1042 0 0       0 $logger->error(
1043             "invalid interface ID $port_id while setting known mac " .
1044             "addresses, aborting"
1045             ) if $logger;
1046 0         0 next;
1047             }
1048              
1049 4         7 my $port = $ports->{$port_id};
1050              
1051             # connected device has already been identified through CDP/LLDP
1052             next if
1053             exists $port->{CONNECTIONS} &&
1054             exists $port->{CONNECTIONS}->{CDP} &&
1055 4 50 66     19 $port->{CONNECTIONS}->{CDP};
      33        
1056              
1057             # get at list of already associated addresses, if any
1058             # as well as the port own mac address, if known
1059 3         4 my @known;
1060 3 100       8 push @known, $port->{MAC} if $port->{MAC};
1061 0         0 push @known, @{$port->{CONNECTIONS}->{CONNECTION}->{MAC}} if
1062             exists $port->{CONNECTIONS} &&
1063             exists $port->{CONNECTIONS}->{CONNECTION} &&
1064 3 0 33     8 exists $port->{CONNECTIONS}->{CONNECTION}->{MAC};
      0        
1065              
1066             # filter out those addresses from the additional ones
1067 3         11 my %known = map { $_ => 1 } @known;
  1         5  
1068 3         3 my @adresses = grep { !$known{$_} } @{$mac_addresses->{$port_id}};
  5         15  
  3         7  
1069 3 50       10 next unless @adresses;
1070              
1071             # add remaining ones
1072 3         4 push @{$port->{CONNECTIONS}->{CONNECTION}->{MAC}}, @adresses;
  3         19  
1073             }
1074             }
1075              
1076             sub _getKnownMacAddresses {
1077 10     10   38 my (%params) = @_;
1078              
1079 10         18 my $snmp = $params{snmp};
1080              
1081 10         10 my $results;
1082 10         31 my $address2port = $snmp->walk($params{address2port});
1083 10         33 my $port2interface = $snmp->walk($params{port2interface});
1084              
1085             # dot1dTpFdbPort values matches the following scheme:
1086             # $prefix.a.b.c.d.e.f = $port
1087              
1088             # dot1qTpFdbPort values matches the following scheme:
1089             # $prefix.$vlan.a.b.c.d.e.f = $port
1090              
1091             # in both case, the last 6 elements of the OID constitutes
1092             # the mac address in decimal format
1093 10         14 foreach my $suffix (sort keys %{$address2port}) {
  10         31  
1094 10         18 my $port_id = $address2port->{$suffix};
1095 10         17 my $interface_id = $port2interface->{$port_id};
1096 10 50       26 next unless defined $interface_id;
1097              
1098 10         35 my @bytes = split(/\./, $suffix);
1099 10 50       23 shift @bytes if @bytes > 6;
1100              
1101 10         13 push @{$results->{$interface_id}},
  10         80  
1102             sprintf "%02x:%02x:%02x:%02x:%02x:%02x", @bytes;
1103             }
1104              
1105 10         38 return $results;
1106             }
1107              
1108             sub _setConnectedDevices {
1109 0     0   0 my (%params) = @_;
1110              
1111 0         0 my $logger = $params{logger};
1112 0         0 my $ports = $params{ports};
1113              
1114 0         0 my $lldp_info = _getLLDPInfo(%params);
1115 0 0       0 if ($lldp_info) {
1116 0         0 foreach my $interface_id (keys %$lldp_info) {
1117             # safety check
1118 0 0       0 if (! exists $ports->{$interface_id}) {
1119 0 0       0 $logger->warning(
1120             "unknown interface $interface_id in LLDP info, ignoring"
1121             ) if $logger;
1122 0         0 next;
1123             }
1124              
1125 0         0 my $port = $ports->{$interface_id};
1126 0         0 my $lldp_connection = $lldp_info->{$interface_id};
1127              
1128             $port->{CONNECTIONS} = {
1129 0         0 CDP => 1,
1130             CONNECTION => $lldp_connection
1131             };
1132             }
1133             }
1134              
1135 0         0 my $cdp_info = _getCDPInfo(%params);
1136 0 0       0 if ($cdp_info) {
1137 0         0 foreach my $interface_id (keys %$cdp_info) {
1138             # safety check
1139 0 0       0 if (! exists $ports->{$interface_id}) {
1140 0 0       0 $logger->warning(
1141             "unknown interface $interface_id in CDP info, ignoring"
1142             ) if $logger;
1143 0         0 next;
1144             }
1145              
1146 0         0 my $port = $ports->{$interface_id};
1147 0         0 my $lldp_connection = $port->{CONNECTIONS}->{CONNECTION};
1148 0         0 my $cdp_connection = $cdp_info->{$interface_id};
1149              
1150 0 0       0 if ($lldp_connection) {
1151 0 0       0 if ($cdp_connection->{SYSDESCR} eq $lldp_connection->{SYSDESCR}) {
1152             # same device, everything OK
1153 0         0 foreach my $key (qw/IP MODEL/) {
1154 0         0 $lldp_connection->{$key} = $cdp_connection->{$key};
1155             }
1156             } else {
1157             # undecidable situation
1158 0         0 $logger->warning(
1159             "multiple neighbors found by LLDP and CDP for " .
1160             "interface $interface_id, ignoring"
1161             );
1162 0         0 delete $port->{CONNECTIONS};
1163             }
1164             } else {
1165             $port->{CONNECTIONS} = {
1166 0         0 CDP => 1,
1167             CONNECTION => $cdp_connection
1168             };
1169             }
1170             }
1171             }
1172              
1173 0         0 my $edp_info = _getEDPInfo(%params);
1174 0 0       0 if ($edp_info) {
1175 0         0 foreach my $interface_id (keys %$edp_info) {
1176             # safety check
1177 0 0       0 if (! exists $ports->{$interface_id}) {
1178 0 0       0 $logger->warning(
1179             "unknown interface $interface_id in EDP info, ignoring"
1180             ) if $logger;
1181 0         0 next;
1182             }
1183              
1184 0         0 my $port = $ports->{$interface_id};
1185 0         0 my $lldp_connection = $port->{CONNECTIONS}->{CONNECTION};
1186 0         0 my $edp_connection = $edp_info->{$interface_id};
1187              
1188 0 0       0 if ($lldp_connection) {
1189 0 0       0 if ($edp_connection->{SYSDESCR} eq $lldp_connection->{SYSDESCR}) {
1190             # same device, everything OK
1191 0         0 foreach my $key (qw/IP/) {
1192 0         0 $lldp_connection->{$key} = $edp_connection->{$key};
1193             }
1194             } else {
1195             # undecidable situation
1196 0         0 $logger->warning(
1197             "multiple neighbors found by LLDP and EDP for " .
1198             "interface $interface_id, ignoring"
1199             );
1200 0         0 delete $port->{CONNECTIONS};
1201             }
1202             } else {
1203             $port->{CONNECTIONS} = {
1204 0         0 CDP => 1,
1205             CONNECTION => $edp_connection
1206             };
1207             }
1208             }
1209             }
1210             }
1211              
1212             sub _getLLDPInfo {
1213 0     0   0 my (%params) = @_;
1214              
1215 0         0 my $snmp = $params{snmp};
1216              
1217 0         0 my $results;
1218 0         0 my $lldpRemChassisId = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.5');
1219 0         0 my $lldpRemPortId = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.7');
1220 0         0 my $lldpRemPortDesc = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.8');
1221 0         0 my $lldpRemSysName = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.9');
1222 0         0 my $lldpRemSysDesc = $snmp->walk('.1.0.8802.1.1.2.1.4.1.1.10');
1223              
1224             # port to interface mapping
1225 0   0     0 my $port2interface =
1226             $snmp->walk('.1.3.6.1.4.1.9.5.1.4.1.1.11.1') || # Cisco portIfIndex
1227             $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2'); # dot1dBasePortIfIndex
1228              
1229             # each lldp variable matches the following scheme:
1230             # $prefix.x.y.z = $value
1231             # whereas y is either a port or an interface id
1232              
1233 0         0 while (my ($suffix, $mac) = each %{$lldpRemChassisId}) {
  0         0  
1234 0         0 my $sysdescr = _getCanonicalString($lldpRemSysDesc->{$suffix});
1235 0 0       0 next unless $sysdescr;
1236              
1237 0         0 my $connection = {
1238             SYSMAC => lc(alt2canonical($mac)),
1239             SYSDESCR => $sysdescr
1240             };
1241              
1242             # portId is either a port number or a port mac address,
1243             # duplicating chassiId
1244 0         0 my $portId = $lldpRemPortId->{$suffix};
1245 0 0 0     0 if ($portId !~ /^0x/ or length($portId) != 14) {
1246 0         0 $connection->{IFNUMBER} = $portId;
1247             }
1248              
1249 0         0 my $ifdescr = _getCanonicalString($lldpRemPortDesc->{$suffix});
1250 0 0       0 $connection->{IFDESCR} = $ifdescr if $ifdescr;
1251              
1252 0         0 my $sysname = _getCanonicalString($lldpRemSysName->{$suffix});
1253 0 0       0 $connection->{SYSNAME} = $sysname if $sysname;
1254              
1255 0         0 my $id = _getElement($suffix, -2);
1256             my $interface_id =
1257             ! exists $port2interface->{$id} ? $id :
1258             $params{vendor} eq 'Juniper' ? $id :
1259 0 0       0 $port2interface->{$id};
    0          
1260              
1261 0         0 $results->{$interface_id} = $connection;
1262             }
1263              
1264 0         0 return $results;
1265             }
1266              
1267             sub _getCDPInfo {
1268 3     3   15 my (%params) = @_;
1269              
1270 3         5 my $snmp = $params{snmp};
1271 3         7 my $logger = $params{logger};
1272              
1273 3         4 my ($results, $blacklist);
1274 3         9 my $cdpCacheAddress = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.4');
1275 3         11 my $cdpCacheVersion = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.5');
1276 3         10 my $cdpCacheDeviceId = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.6');
1277 3         10 my $cdpCacheDevicePort = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.7');
1278 3         9 my $cdpCachePlatform = $snmp->walk('.1.3.6.1.4.1.9.9.23.1.2.1.1.8');
1279              
1280             # each cdp variable matches the following scheme:
1281             # $prefix.x.y = $value
1282             # whereas x is the port number
1283              
1284 3         5 while (my ($suffix, $ip) = each %{$cdpCacheAddress}) {
  6         23  
1285 3         8 my $interface_id = _getElement($suffix, -2);
1286 3         12 $ip = hex2canonical($ip);
1287 3 50       9 next if $ip eq '0.0.0.0';
1288              
1289 3         8 my $sysdescr = _getCanonicalString($cdpCacheVersion->{$suffix});
1290 3         8 my $model = _getCanonicalString($cdpCachePlatform->{$suffix});
1291 3 100 100     18 next unless $sysdescr && $model;
1292              
1293 1         5 my $connection = {
1294             IP => $ip,
1295             SYSDESCR => $sysdescr,
1296             MODEL => $model,
1297             };
1298              
1299             # cdpCacheDevicePort is either a port number or a port description
1300 1         2 my $devicePort = $cdpCacheDevicePort->{$suffix};
1301 1 50       5 if ($devicePort =~ /^\d+$/) {
1302 0         0 $connection->{IFNUMBER} = $devicePort;
1303             } else {
1304 1         3 $connection->{IFDESCR} = $devicePort;
1305             }
1306              
1307             # cdpCacheDeviceId is either remote host name, either remote mac address
1308 1         2 my $deviceId = $cdpCacheDeviceId->{$suffix};
1309 1 50       3 if ($deviceId =~ /^0x/) {
1310 0 0       0 if (length($deviceId) == 14) {
1311             # let's assume it is a mac address if the length is 6 bytes
1312 0         0 $connection->{SYSMAC} = lc(alt2canonical($deviceId));
1313             } else {
1314             # otherwise it's an hex-encode hostname
1315 0         0 $connection->{SYSNAME} = _getCanonicalString($deviceId);
1316             }
1317             } else {
1318 1         2 $connection->{SYSNAME} = $deviceId;
1319             }
1320              
1321 1 50 33     9 if ($connection->{SYSNAME} &&
1322             $connection->{SYSNAME} =~ /^SIP([A-F0-9a-f]*)$/) {
1323 1         6 $connection->{MAC} = lc(alt2canonical("0x".$1));
1324             }
1325              
1326             # warning: multiple neighbors announcement for the same interface
1327             # usually means a non-CDP aware intermediate equipement
1328 1 50       4 if ($results->{$interface_id}) {
1329 0         0 $logger->warning(
1330             "multiple neighbors found by CDP for interface $interface_id," .
1331             " ignoring"
1332             );
1333 0         0 $blacklist->{$interface_id} = 1;
1334             } else {
1335 1         4 $results->{$interface_id} = $connection;
1336             }
1337             }
1338              
1339             # remove blacklisted results
1340 3         8 delete $results->{$_} foreach keys %$blacklist;
1341              
1342 3         16 return $results;
1343             }
1344              
1345             sub _getEDPInfo {
1346 0     0   0 my (%params) = @_;
1347              
1348 0         0 my $snmp = $params{snmp};
1349 0         0 my $logger = $params{logger};
1350              
1351 0         0 my ($results, $blacklist);
1352 0         0 my $edpNeighborVlanIpAddress = $snmp->walk('.1.3.6.1.4.1.1916.1.13.3.1.3');
1353 0         0 my $edpNeighborName = $snmp->walk('.1.3.6.1.4.1.1916.1.13.2.1.3');
1354 0         0 my $edpNeighborPort = $snmp->walk('.1.3.6.1.4.1.1916.1.13.2.1.6');
1355              
1356             # each entry from extremeEdpTable matches the following scheme:
1357             # $prefix.x.0.0.y1.y2.y3.y4.y5.y6 = $value
1358             # - x: the interface id
1359             # - y1.y2.y3.y4.y5.y6: the remote mac address
1360              
1361             # each entry from extremeEdpNeighborTable matches the following scheme:
1362             # $prefix.x.0.0.y1.y2.y3.y4.y5.y6.z1.z2...zz = $value
1363             # - x: the interface id,
1364             # - y1.y2.y3.y4.y5.y6: the remote mac address
1365             # - z1.z2...zz: the vlan name in ASCII
1366              
1367 0         0 while (my ($suffix, $ip) = each %{$edpNeighborVlanIpAddress}) {
  0         0  
1368 0 0       0 next if $ip eq '0.0.0.0';
1369              
1370 0         0 my $interface_id = _getElement($suffix, 0);
1371 0         0 my @mac_elements = _getElements($suffix, 3, 8);
1372 0         0 my $short_suffix = join('.', $interface_id, 0, 0, @mac_elements);
1373              
1374             my $connection = {
1375             IP => $ip,
1376             IFDESCR => $edpNeighborPort->{$short_suffix},
1377 0         0 SYSNAME => $edpNeighborName->{$short_suffix},
1378             SYSMAC => sprintf "%02x:%02x:%02x:%02x:%02x:%02x", @mac_elements
1379             };
1380              
1381             # warning: multiple neighbors announcement for the same interface
1382             # usually means a non-EDP aware intermediate equipement
1383 0 0       0 if ($results->{$interface_id}) {
1384 0         0 $logger->warning(
1385             "multiple neighbors found by EDP for interface $interface_id," .
1386             " ignoring"
1387             );
1388 0         0 $blacklist->{$interface_id} = 1;
1389             } else {
1390 0         0 $results->{$interface_id} = $connection;
1391             }
1392             }
1393              
1394             # remove blacklisted results
1395 0         0 delete $results->{$_} foreach keys %$blacklist;
1396              
1397 0         0 return $results;
1398             }
1399              
1400              
1401             sub _setVlans {
1402 0     0   0 my (%params) = @_;
1403              
1404             my $vlans = _getVlans(
1405             snmp => $params{snmp},
1406 0         0 );
1407 0 0       0 return unless $vlans;
1408              
1409 0         0 my $ports = $params{ports};
1410 0         0 my $logger = $params{logger};
1411              
1412 0         0 foreach my $port_id (keys %$vlans) {
1413             # safety check
1414 0 0       0 if (! exists $ports->{$port_id}) {
1415 0 0       0 $logger->error(
1416             "invalid interface ID $port_id while setting vlans, aborting"
1417             ) if $logger;
1418 0         0 last;
1419             }
1420 0         0 $ports->{$port_id}->{VLANS}->{VLAN} = $vlans->{$port_id};
1421             }
1422             }
1423              
1424             sub _getVlans {
1425 0     0   0 my (%params) = @_;
1426              
1427 0         0 my $snmp = $params{snmp};
1428              
1429 0         0 my $results;
1430 0         0 my $vtpVlanName = $snmp->walk('.1.3.6.1.4.1.9.9.46.1.3.1.1.4.1');
1431 0         0 my $vmPortStatus = $snmp->walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2');
1432              
1433             # each result matches either of the following schemes:
1434             # $prefix.$i.$j = $value, with $j as port id, and $value as vlan id
1435             # $prefix.$i = $value, with $i as port id, and $value as vlan id
1436             # work with Cisco and Juniper switches
1437 0 0 0     0 if($vtpVlanName and $vmPortStatus){
1438 0         0 foreach my $suffix (sort keys %{$vmPortStatus}) {
  0         0  
1439 0         0 my $port_id = _getElement($suffix, -1);
1440 0         0 my $vlan_id = $vmPortStatus->{$suffix};
1441 0         0 my $name = $vtpVlanName->{$vlan_id};
1442              
1443 0         0 push @{$results->{$port_id}}, {
  0         0  
1444             NUMBER => $vlan_id,
1445             NAME => $name
1446             };
1447             }
1448             }
1449              
1450             # For other switches, we use another method
1451 0         0 my $vlanId = $snmp->walk('.1.0.8802.1.1.2.1.5.32962.1.2.1.1.1');
1452 0 0       0 if($vlanId){
1453 0         0 while (my ($port, $vlan) = each %{$vlanId}) {
  0         0  
1454 0         0 push @{$results->{$port}}, {
  0         0  
1455             NUMBER => $vlan,
1456             NAME => "VLAN " . $vlan
1457             };
1458             }
1459             }
1460              
1461 0         0 return $results;
1462             }
1463              
1464             sub _setTrunkPorts {
1465 0     0   0 my (%params) = @_;
1466              
1467             my $trunk_ports = _getTrunkPorts(
1468             snmp => $params{snmp},
1469 0         0 );
1470 0 0       0 return unless $trunk_ports;
1471              
1472 0         0 my $ports = $params{ports};
1473 0         0 my $logger = $params{logger};
1474              
1475 0         0 foreach my $port_id (keys %$trunk_ports) {
1476             # safety check
1477 0 0       0 if (! exists $ports->{$port_id}) {
1478 0 0       0 $logger->error(
1479             "invalid interface ID $port_id while setting trunk flag, " .
1480             "aborting"
1481             ) if $logger;
1482 0         0 last;
1483             }
1484 0         0 $ports->{$port_id}->{TRUNK} = $trunk_ports->{$port_id};
1485             }
1486             }
1487              
1488             sub _getTrunkPorts {
1489 1     1   7 my (%params) = @_;
1490              
1491 1         3 my $snmp = $params{snmp};
1492              
1493 1         2 my $results;
1494              
1495             # cisco use vlanTrunkPortDynamicStatus, using the following schema:
1496             # prefix.x = value
1497             # x is the interface id
1498             # value is 1 for trunk, 2 for access
1499 1         5 my $vlanStatus = $snmp->walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.14');
1500 1 50       5 if ($vlanStatus) {
1501 1         2 while (my ($interface_id, $value) = each %{$vlanStatus}) {
  4         14  
1502 3 100       10 $results->{$interface_id} = $value == 1 ? 1 : 0;
1503             }
1504 1         6 return $results;
1505             }
1506              
1507             # juniper use jnxExVlanPortAccessMode, using the following schema:
1508             # prefix.x.y = value
1509             # x is the vlan id
1510             # y is the port id
1511             # value is 1 for access, 2 for trunk
1512 0           my $accessMode = $snmp->walk('.1.3.6.1.4.1.2636.3.40.1.5.1.7.1.5');
1513 0 0         if ($accessMode) {
1514 0           my $port2interface = $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2');
1515 0           while (my ($suffix, $value) = each %{$accessMode}) {
  0            
1516 0           my $port_id = _getElement($suffix, -1);
1517 0           my $interface_id = $port2interface->{$port_id};
1518 0 0         $results->{$interface_id} = $value == 2 ? 1 : 0;
1519             }
1520 0           return $results;
1521             }
1522              
1523              
1524             # others use lldpXdot1LocPortVlanId
1525             # prefix.x = value
1526             # x is either an interface or a port id
1527             # value is the vlan id, 0 for trunk
1528 0           my $vlanId = $snmp->walk('.1.0.8802.1.1.2.1.5.32962.1.2.1.1.1');
1529 0 0         if ($vlanId) {
1530 0           my $port2interface = $snmp->walk('.1.3.6.1.2.1.17.1.4.1.2');
1531 0           while (my ($id, $value) = each %{$vlanId}) {
  0            
1532             my $interface_id =
1533             ! exists $port2interface->{$id} ? $id :
1534 0 0         $port2interface->{$id};
1535 0 0         $results->{$interface_id} = $value == 0 ? 1 : 0;
1536             }
1537 0           return $results;
1538             }
1539              
1540 0           return;
1541             }
1542              
1543             sub _setAggregatePorts {
1544 0     0     my (%params) = @_;
1545              
1546 0           my $ports = $params{ports};
1547 0           my $logger = $params{logger};
1548              
1549 0           my $lacp_info = _getLACPInfo(%params);
1550 0 0         if ($lacp_info) {
1551 0           foreach my $interface_id (keys %$lacp_info) {
1552             # safety check
1553 0 0         if (!$ports->{$interface_id}) {
1554 0 0         $logger->warning(
1555             "unknown interface $interface_id in LACP info, ignoring"
1556             ) if $logger;
1557 0           next;
1558             }
1559 0           $ports->{$interface_id}->{AGGREGATE}->{PORT} = $lacp_info->{$interface_id};
1560             }
1561             }
1562              
1563 0           my $pagp_info = _getPAGPInfo(%params);
1564 0 0         if ($pagp_info) {
1565 0           foreach my $interface_id (keys %$pagp_info) {
1566             # safety check
1567 0 0         if (!$ports->{$interface_id}) {
1568 0 0         $logger->error(
1569             "unknown interface $interface_id in PAGP info, ignoring"
1570             ) if $logger;
1571 0           next;
1572             }
1573 0           $ports->{$interface_id}->{AGGREGATE}->{PORT} = $pagp_info->{$interface_id};
1574             }
1575             }
1576             }
1577              
1578             sub _getLACPInfo {
1579 0     0     my (%params) = @_;
1580              
1581 0           my $snmp = $params{snmp};
1582              
1583 0           my $results;
1584 0           my $aggPortAttachedAggID = $snmp->walk('.1.2.840.10006.300.43.1.2.1.1.13');
1585              
1586 0           foreach my $interface_id (sort keys %$aggPortAttachedAggID) {
1587 0           my $aggregator_id = $aggPortAttachedAggID->{$interface_id};
1588 0 0         next if $aggregator_id == 0;
1589 0 0         next if $aggregator_id == $interface_id;
1590 0           push @{$results->{$aggregator_id}}, $interface_id;
  0            
1591             }
1592              
1593 0           return $results;
1594             }
1595              
1596             sub _getPAGPInfo {
1597 0     0     my (%params) = @_;
1598              
1599 0           my $snmp = $params{snmp};
1600              
1601 0           my $results;
1602 0           my $pagpPorts = $snmp->walk('.1.3.6.1.4.1.9.9.98.1.1.1.1.5');
1603              
1604 0           foreach my $port_id (sort keys %$pagpPorts) {
1605 0           my $portShortNum = $pagpPorts->{$port_id};
1606 0 0         next unless $portShortNum > 0;
1607 0           my $aggregatePort_id = $portShortNum + 5000;
1608 0           push @{$results->{$aggregatePort_id}}, $port_id;
  0            
1609             }
1610              
1611 0           return $results;
1612             }
1613              
1614             1;
1615             __END__