File Coverage

blib/lib/FusionInventory/Agent/Tools/Hardware.pm
Criterion Covered Total %
statement 21 622 3.3
branch 0 378 0.0
condition 0 81 0.0
subroutine 7 43 16.2
pod 2 2 100.0
total 30 1126 2.6


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