Advertisement
poodad

Untitled

Feb 22nd, 2023
281
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 28.98 KB | None | 0 0
  1. use strict;
  2. use Net::SNMP;
  3. use Data::Dumper;
  4. use 5.010;
  5.  
  6.  
  7. my $sw = $ARGV[0];
  8.  
  9. my $switch = new switch($sw, 'public', '2c', 30, 6, 1);
  10.  
  11. if( ! $switch ) {
  12. print STDERR "Could not create switch object for: $sw\n";
  13. return;
  14. }
  15.  
  16. my @ports = $switch->GetPorts();
  17. my @vlans = $switch->GetVlans();
  18. my @macs = $switch->GetMacs();
  19.  
  20. print STDERR "WARNING $sw read zero ports\n" unless scalar @ports;
  21. print STDERR "WARNING $sw read zero VLANs\n" unless scalar @vlans;
  22. print STDERR "WARNING $sw got zero MACs\n" unless scalar @macs;
  23.  
  24. foreach my $vlan (@vlans) {
  25. print "VLAN: $vlan - ", $switch->VlanDesc($vlan), "\n";
  26. }
  27.  
  28.  
  29. foreach my $ifindex ( sort {$a<=>$b} @ports ) {
  30.  
  31. my $port = $switch->GetPort($ifindex);
  32. my $cdpn = $port->cdpn();
  33. $cdpn =~ s/\(SAL..*\)//; # Damn Nexus switches
  34.  
  35. next unless $port->type() == 6; # 6 = ethernet
  36. next if $port->name() eq '(other)'; # Bizzaro port, ignore
  37. next if $port->name() =~ /^Po[0-9]+/; # port-channel, ignore
  38. next if $port->name() =~ /^port-channel/; # ditto
  39.  
  40. print "Name: ", $port->name(), " ";
  41. print "Mode: ", $port->mode(), " ";
  42. print "CDP: ", $port->cdpn(), " ";
  43. print "Voice: ", $port->voice(), " ";
  44. print "Speed: ", $port->speed(), " ";
  45. if( $port->pagp() ) {
  46. print "LAG: ", $switch->GetPort($port->pagp())->name(), " ";
  47. } else {
  48. print "LAG: ";
  49. }
  50. print "MACs: ", $switch->maccnt($ifindex), " ";
  51. print "VLAN: ", $port->vlan(), " ";
  52.  
  53.  
  54. print "\n";
  55. }
  56.  
  57. exit 0;
  58.  
  59.  
  60.  
  61.  
  62. ###############################################################################
  63. #
  64. # Logger object. Writes formatted messages to STDOUT and to log file (if passed)
  65. #
  66. ###############################################################################
  67.  
  68. package Logger;
  69.  
  70. sub new {
  71. my $class = shift;
  72. my $fname = shift;
  73.  
  74. my $self = {};
  75. $self->{fname} = $fname;
  76.  
  77. print STDERR "Opening $fname\n";
  78.  
  79. return undef unless $fname;
  80.  
  81. if( $fname ) {
  82. if( ! open FD, ">$fname" ) {
  83. warn "Could not open log $fname. Error: $!\n";
  84. return undef;
  85. }
  86. $self->{logfd} = \*FD;
  87. }
  88.  
  89. bless $self;
  90. return $self;
  91. }
  92.  
  93. sub logit {
  94. my $self = shift;
  95. my $s = shift;
  96.  
  97. return unless $self->{logfd};
  98.  
  99. my $time = shift;
  100.  
  101. my $caller = (caller(1))[3];
  102. $caller = "(MAIN)" unless $caller;
  103.  
  104.  
  105. my ($sc, $mn, $hr, $md, $mo, $yr, $wd, $yd, $isdst) = localtime();
  106. my $txt = sprintf( "%2.2d/%2.2d/%4.4d %2d:%2.2d:%2.2d %-35.35s\t%s\n", $mo+1, $md, $yr+1900, $hr, $mn, $sc, $caller, $s);
  107. print {$self->{logfd}} $txt;
  108. print STDERR $txt;
  109. }
  110.  
  111.  
  112.  
  113.  
  114. ################################################################################
  115. #
  116. # Switch object
  117. #
  118. ################################################################################
  119.  
  120. package switch;
  121.  
  122. sub new {
  123. my $class = shift;
  124. my $host = shift;
  125. my $community = shift || 'public';
  126. my $ver = shift || '2c';
  127. my $timeout = shift || 10;
  128. my $retries= shift || 3;
  129. my $debug = shift || 0;
  130.  
  131. my $self = {};
  132.  
  133. bless ($self, $class);
  134.  
  135. $self->{host} = $host;
  136. $self->{community} = $community;
  137. $self->{ver} = $ver;
  138. $self->{timeout} = $timeout;
  139.  
  140. $self->{vlans} = {}; # hash of VLAN names indexed by VLAN number
  141. $self->{ports} = {}; # hash of port obects indexed by ifindex number
  142. $self->{arp} = {}; # arp cache
  143. $self->{cam} = {}; # cam table
  144. $self->{maccnt} = {}; # MAC count per ifindex
  145.  
  146. $self->{dot1x} = undef; # is switch configured for dot1x port control?
  147.  
  148. $self->{log} = new Logger($self->{host} . "-debug.log") if $debug;
  149.  
  150. # Open SNMP connection. Don't translate octet strings - Net::SNMP doesn't always get it right - we'll do it ourself
  151.  
  152. ($self->{snmp}, my $err) = Net::SNMP->session( -hostname => $host, -version => $ver, -community => $community, -timeout => $timeout, -retries => $retries, -port => "161", -translate=>[-octetstring => 0]);
  153.  
  154. if ( !defined($self->{snmp}) ) {
  155. print STDERR "$host: SNMP open error: $err\n";
  156. return undef;
  157. }
  158.  
  159. # Generic information that both styles of switch might support
  160.  
  161. # get the port names
  162.  
  163. $self->{log}->logit("Getting port names") if $self->{log};
  164.  
  165. my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.1", 1); # ifName
  166.  
  167. foreach my $ifindex ( keys %{$res} ) { # entries will look like '234' => 'Gi6/39'
  168. my $ifname = $res->{$ifindex};
  169. $self->{ports}{$ifindex} = port->new($ifname, $self, $ifindex);
  170. $self->{maccnt}->{$ifindex} = 0;
  171. $self->{log}->logit("IfIndex: $ifindex Name: $ifname") if $self->{log};
  172. }
  173.  
  174. # port descriptions
  175.  
  176. $self->{log}->logit("Getting port descriptions") if $self->{log};
  177.  
  178. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.18", 1); # ifAlias
  179.  
  180. foreach my $ifindex ( keys %{$res} ) { # entries will look like '236' => 'Uplink to E54APG06'
  181. my $desc = $res->{$ifindex};
  182. $self->{ports}->{$ifindex}->desc($desc);
  183. $self->{log}->logit("IfIndex: $ifindex Desc: $desc") if $self->{log};
  184. }
  185.  
  186. # port types
  187.  
  188. $self->{log}->logit("Getting port types") if $self->{log};
  189.  
  190. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.3", 1); # ifType. ethernet = 6, SVI = 53
  191.  
  192. foreach my $ifindex ( keys %{$res} ) {
  193. my $type = $res->{$ifindex};
  194. $self->{ports}->{$ifindex}->type($type);
  195. $self->{log}->logit("IfIndex: $ifindex Type: $type") if $self->{log};
  196. }
  197.  
  198. # CDP neighbor
  199.  
  200. $self->{log}->logit("Getting CDP information") if $self->{log};
  201.  
  202. $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.23.1.2.1.1.6", 2);
  203.  
  204. foreach my $x ( keys %{$res} ) {
  205. my ($ifIndex, $junk) = split /\./, $x;
  206. $self->{ports}->{$ifIndex}->cdpn($res->{$x}) unless $res->{$x} =~ /[^[:print:]]/; # Store CDP info unless there is unprintable crap.
  207. }
  208.  
  209. # get port status
  210. # ( 1=>'up', 2=>'down', 3=>'testing', 4=>'unknown', 5=>'dormant', 6=>'notPresent', 7=>'lowerLayerDown');
  211.  
  212. $self->{log}->logit("Getting port status") if $self->{log};
  213.  
  214. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.8", 1);
  215.  
  216. foreach my $ifindex ( keys %{$res} ) {
  217. my $mode = $res->{$ifindex};
  218. $self->{log}->logit("IfIndex: $ifindex Status: $mode") if $self->{log};
  219. $self->{ports}->{$ifindex}->status('up') if $mode == 1;
  220. $self->{ports}->{$ifindex}->status('down') if $mode == 2;
  221. $self->{ports}->{$ifindex}->status('testing') if $mode == 3;
  222. $self->{ports}->{$ifindex}->status('unknown') if $mode == 4;
  223. $self->{ports}->{$ifindex}->status('dormant') if $mode == 5;
  224. $self->{ports}->{$ifindex}->status('notPresent') if $mode == 6;
  225. $self->{ports}->{$ifindex}->status('lowerLayerDown') if $mode == 7;
  226. }
  227.  
  228. # get port speed
  229.  
  230. $self->{log}->logit("Getting port speed") if $self->{log};
  231.  
  232. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.15", 1); # IF-MIB::ifHighSpeed (units = megabits/sec)
  233.  
  234. foreach my $ifindex ( keys %{$res} ) {
  235. my $speed = $res->{$ifindex};
  236. $self->{ports}->{$ifindex}->speed($speed);
  237. $self->{log}->logit("IfIndex: $ifindex Speed: $speed") if $self->{log};
  238. }
  239.  
  240.  
  241. # dot1x control (1 = forced unauthorized, 2 = auto, 3 = forced authorized
  242.  
  243. $self->{log}->logit("Getting Dot1X control") if $self->{log};
  244.  
  245. $res = util::tablehash($self->{snmp}, "1.0.8802.1.1.1.1.2.1.1.6", 1); # dot1xAuthAuthControlledPortControl
  246.  
  247. foreach my $ifindex ( keys %{$res} ) {
  248. my $dot1x = $res->{$ifindex};
  249. $self->{log}->logit("IfIndex: $ifindex Dot1X: $dot1x") if $self->{log};
  250. $self->{ports}->{$ifindex}->dot1xcontrol('');
  251. $self->{ports}->{$ifindex}->dot1xcontrol('forced unauthorized') if $dot1x == 1;
  252. $self->{ports}->{$ifindex}->dot1xcontrol('auto') if $dot1x == 2;
  253. $self->{ports}->{$ifindex}->dot1xcontrol('forced authorized') if $dot1x == 3;
  254. }
  255.  
  256. # Get MAC addresses associated with device
  257.  
  258. $self->{log}->logit("Getting MAC addresses of device") if $self->{log};
  259.  
  260. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.6", 1);
  261.  
  262. foreach my $key ( keys %{$res} ) {
  263. my $mac = unpack "H*", $res->{$key}; # We told Net::SNMP to not decode byte strings. Decode MAC address
  264. next if length($mac) != 12; # not a valid MAC address
  265. $self->{macs}->{$mac} = 1;
  266. }
  267.  
  268. # Attempt to get the VLANs and descriptions by using the Cisco Catalyst/Nexus way. If that fails, revert to Q-BRIDGE
  269. # If that fails, punt and get VLANs
  270.  
  271.  
  272. if( $self->_LoadVlansCisco() ) { # Try Catalyst/Nexus
  273. $self->_LoadPortsCisco();
  274. my @vlans = $self->GetVlans();
  275. foreach my $vlan (@vlans) {
  276. $self->_LoadCam($vlan);
  277. }
  278. } elsif( $self->_LoadVlansQbridge() ) { # Try QBridge MIB
  279. $self->_LoadPortsQbridge();
  280. $self->_LoadCam();
  281. } else { #
  282. $self->_LoadPortsAruba(); # Try Aruba. LoadPortsAruba also gets the VLANs
  283. $self->_LoadCam();
  284. }
  285.  
  286. return $self;
  287. }
  288.  
  289.  
  290. sub name {
  291. my $self = shift;
  292. my $p = shift;
  293.  
  294. $self->{host} = $p if defined $p;
  295. return $self->{host};
  296. }
  297.  
  298. sub maccnt {
  299. my $self = shift;
  300. my $ifindex = shift;
  301. return undef unless exists $self->{maccnt}->{$ifindex};
  302. return $self->{maccnt}->{$ifindex};
  303. }
  304.  
  305. sub LoadArpCache {
  306. my $self = shift;
  307.  
  308.  
  309. # We told Net::SNMP to not encode binary strings, so the MAC addresses will be six byte binary blobs
  310.  
  311. my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.3.1.1.2", 4);
  312.  
  313. foreach my $ip ( keys %{$res} ) { # entries will look like '1.2.3.4' => '00112233445566'
  314. my $mac = $res->{$ip};
  315. $self->{arp}{$ip} = $mac;
  316. }
  317.  
  318. # Convert binary to text characters
  319.  
  320. foreach my $i (keys %{$self->{arp}}) {
  321. $self->{arp}->{$i} = unpack "H*", $self->{arp}->{$i};
  322. }
  323.  
  324. return keys %{$self->{arp}};
  325. }
  326.  
  327.  
  328. sub ArpEntry {
  329. my $self = shift;
  330. my $ip = shift;
  331.  
  332. return $self->{arp}->{$ip} if exists $self->{arp}->{$ip};
  333. return undef;
  334.  
  335. }
  336.  
  337. sub GetPorts {
  338. my $self = shift;
  339.  
  340. return sort {$a<=>$b} keys %{$self->{ports}};
  341. }
  342.  
  343.  
  344. sub GetPort { # Given an ifindex value, return port object
  345. my $self = shift;
  346. my $p = shift;
  347.  
  348. if( ! $self->{ports}->{$p} ) { # No port exists for this ifindex?
  349. $self->{ports}->{$p} = port->new($p); # create one - use the ifindex as the name
  350. }
  351.  
  352. return $self->{ports}->{$p};
  353. }
  354.  
  355. sub GetVlans {
  356. my $self = shift;
  357.  
  358. return sort {$a<=>$b} keys %{$self->{vlans}};
  359. }
  360.  
  361.  
  362. sub VlanDesc { # for a given VLAN number, return the description
  363. my $self = shift;
  364. my $vlan = shift;
  365. my $p = shift;
  366.  
  367. $self->{vlans}->{$vlan} = $p if defined $p;
  368. return $self->{vlans}->{$vlan};
  369. }
  370.  
  371. # Is this switch dot1x enabled?
  372.  
  373. sub Dot1x {
  374. my $self = shift;
  375.  
  376. return $self->{dot1x} if defined $self->{dot1x};
  377.  
  378. $self->{dot1x} = 0;
  379.  
  380. my $oid = "1.0.8802.1.1.1.1.1.1.0";
  381. my $res = $self->{snmp}->get_request(-varbindlist => [ $oid ]);
  382. $self->{dot1x} = 1 if $res->{$oid} and $res->{$oid} == 1;
  383. return $self->{dot1x};
  384. }
  385.  
  386. sub GetMacs {
  387. my $self = shift;
  388.  
  389. return sort keys %{$self->{cam}};
  390. }
  391.  
  392. sub MacToIfindex {
  393. my $self = shift;
  394. my $mac = shift;
  395.  
  396. return $self->{cam}->{$mac};
  397. }
  398.  
  399.  
  400. ###############################################################################
  401. #
  402. # Q-BRIDGE MIB get vlans and vlan descriptions. Return count
  403. #
  404. ###############################################################################
  405.  
  406.  
  407. sub _LoadVlansQbridge {
  408. my $self = shift;
  409.  
  410. $self->{log}->logit("Trying to get VLANS by walking dot1qVlanStaticName") if $self->{log};
  411.  
  412. my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.3.1.1", 1); # dot1qVlanStaticName
  413. foreach my $vlan ( keys %{$res} ) {
  414. $self->{log}->logit("Found VLAN $vlan = $res->{$vlan}") if $self->{log};
  415. $self->{vlans}->{$vlan} = $res->{$vlan};
  416. }
  417.  
  418. my $n = keys %{$self->{vlans}};
  419. $self->{log}->logit("Got $n VLANs" ) if $self->{log};
  420.  
  421. return $n;
  422. }
  423.  
  424. ###############################################################################
  425. #
  426. # Last resort version of get vlans and vlan descriptions. Return count
  427. #
  428. ###############################################################################
  429.  
  430.  
  431. sub _LoadVlansPunt {
  432. my $self = shift;
  433.  
  434. $self->{log}->logit("Trying to get VLANS by finding interfaces defined as type VLAN") if $self->{log};
  435.  
  436.  
  437. foreach my $ifindex ( keys %{$self->{ports}} ) {
  438. my $port = $self->GetPort($ifindex);
  439. next unless $port->type() == 53; #propVirtual (VLAN interface)
  440. $self->{vlans}->{$ifindex} = $port->desc();
  441. $self->{log}->logit("Found VLAN " . $port->desc() ) if $self->{log};
  442. }
  443.  
  444. my $n = keys %{$self->{vlans}};
  445. $self->{log}->logit("Got $n VLANs" ) if $self->{log};
  446. return $n;
  447. }
  448.  
  449.  
  450.  
  451.  
  452. ###############################################################################
  453. #
  454. # Cisco Catalyst/Nexus get vlans and vlan descriptions
  455. #
  456. ###############################################################################
  457.  
  458. sub _LoadVlansCisco {
  459. my $self = shift;
  460.  
  461. # Do it the Catalyst/Nexus way using vtp information.
  462.  
  463. # Get a list of the VLANs present on this switch and create a VLAN object
  464.  
  465. #$res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.2", 1); # vtpVlanState
  466. #
  467. #foreach my $vlan ( keys %{$res} ) {
  468. # next unless $res->{$vlan}; # VLAN is not operational
  469. # $self->{vlans}->{$vlan} = vlan->new(($self->{name}, $vlan)); # Create a VLAN object
  470. #}
  471.  
  472. # get VLAN descriptions
  473.  
  474. $self->{log}->logit("Trying to get VLANS by walking vtpVlanName") if $self->{log};
  475.  
  476. my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.4.1", 1); # vtpVlanName
  477. foreach my $vlan ( keys %{$res} ) { # entries will look like '3' => '85175_wire_room_E'
  478. $self->{log}->logit("Found VLAN $vlan = $res->{$vlan}" ) if $self->{log};
  479. $self->{vlans}->{$vlan} = $res->{$vlan};
  480. }
  481.  
  482. my $n = keys %{$self->{vlans}};
  483. $self->{log}->logit("Got $n VLANs" ) if $self->{log};
  484. return $n;
  485. }
  486.  
  487. ###############################################################################
  488. #
  489. # Q-BRIDGE port config
  490. #
  491. ###############################################################################
  492.  
  493. sub _LoadPortsQbridge {
  494. my $self = shift;
  495.  
  496. $self->{log}->logit("Trying to get port mode by walking dot1qVlanCurrentEgressPorts") if $self->{log};
  497.  
  498. # port mode (trunk or access)
  499.  
  500. my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.2.1.4", 1); # dot1qVlanCurrentEgressPorts
  501. return undef unless scalar keys %{$res}; # Nothing returned, not QBridge
  502.  
  503. # Returns a bitmap for each VLAN. Each bit in the bitmap corresponds to an ifIndex.
  504. # If a bit is set in the bitmap, the corresponding ifindex allows the VLAN to egress
  505. # In theory access ports should allow only one VLAN to egress.
  506.  
  507. my %cnt; # How many VLANs are allowed egress on each port
  508. my @vlans; # Save the VLAN numbers as found
  509.  
  510. foreach my $vlan ( keys %{$res} ) { # bit array of ports where VLAN is allowed egress
  511. push @vlans, $vlan;
  512. my $bitmap = $res->{$vlan};
  513.  
  514. $self->{log}->logit("Bitmap length: " . length($bitmap)) if $self->{log};
  515.  
  516. my @ifindexes;
  517. foreach my $offset (0..length($bitmap)) {
  518. my $byte = vec $bitmap, $offset, 8;
  519. push @ifindexes, $offset * 8 + 1 if $byte & 0b10000000;
  520. push @ifindexes, $offset * 8 + 2 if $byte & 0b01000000;
  521. push @ifindexes, $offset * 8 + 3 if $byte & 0b00100000;
  522. push @ifindexes, $offset * 8 + 4 if $byte & 0b00010000;
  523. push @ifindexes, $offset * 8 + 5 if $byte & 0b00001000;
  524. push @ifindexes, $offset * 8 + 6 if $byte & 0b00000100;
  525. push @ifindexes, $offset * 8 + 7 if $byte & 0b00000010;
  526. push @ifindexes, $offset * 8 + 8 if $byte & 0b00000001;
  527. }
  528.  
  529. foreach my $ifindex (@ifindexes) {
  530. $self->{log}->logit("VLAN $vlan egresses Ifindex: $ifindex") if $self->{log};
  531. $cnt{$ifindex}++;
  532. }
  533. }
  534.  
  535. foreach my $ifindex ( keys %{$self->{ports}} ) {
  536. $self->{log}->logit("ifindex: $ifindex VLAN count: $cnt{$ifindex}") if $self->{log};
  537.  
  538. if( $cnt{$ifindex} > 1) {
  539. $self->{ports}->{$ifindex}->mode('Trunk');
  540. } else {
  541. $self->{ports}->{$ifindex}->mode('Access');
  542. }
  543. }
  544.  
  545.  
  546. # Returns a bitmap for each VLAN. Each bit in the bitmap corresponds to an ifIndex.
  547. # If a bit is set in the bitmap, the corresponding ifindex is untagged
  548.  
  549.  
  550. $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.2.1.5", 1); # dot1qVlanCurrentUntaggedPorts
  551.  
  552. foreach my $vlan ( keys %{$res} ) { # bit array of ports where VLAN is allowed egress
  553. my $bits = $res->{$vlan};
  554. my @tmp = BitsToIfindex($bits);
  555. foreach my $ifindex (@tmp) {
  556. $self->{ports}->{$ifindex}->vlan($vlan);
  557. }
  558. }
  559.  
  560. # Get any LAG interfaces that this port is a member of
  561.  
  562. $res = util::tablehash($self->{snmp}, "1.2.840.10006.300.43.1.2.1.1.12", 1); # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID
  563.  
  564. foreach my $ifindex ( keys %{$res} ) {
  565. my $lagindex = $res->{$ifindex};
  566. next if $lagindex == $ifindex; # This port is not a LAG
  567. $self->{log}->logit("ifindex: $ifindex is memger of LAG $lagindex") if $self->{log};
  568. $self->{ports}->{$ifindex}->pagp($lagindex); # the ifindex of the port channel this interface is a member of
  569. }
  570.  
  571. $self->{log}->logit("Success. Mapped ports to " . scalar @vlans . " VLANs") if $self->{log};
  572.  
  573. return 1;
  574. }
  575.  
  576. sub _LoadPortsCisco {
  577. my $self = shift;
  578.  
  579. # port mode (trunk or access) (only for Cisco Catalyst and Nexus)
  580.  
  581. $self->{log}->logit("Trying to get port mode by walking vlanTrunkPortDynamicStatus") if $self->{log};
  582.  
  583. my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.6.1.1.14", 1); # vlanTrunkPortDynamicStatus
  584.  
  585. return 0 unless scalar keys %{$res};
  586.  
  587. foreach my $ifindex ( keys %{$res} ) {
  588. my $mode = $res->{$ifindex};
  589. $self->{ports}->{$ifindex}->mode('Access') if $mode == 2;
  590. $self->{ports}->{$ifindex}->mode('Trunk') if $mode == 1;
  591. }
  592.  
  593. # Get corresponding VLANs (Catalyst and Nexus only)
  594.  
  595. $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.68.1.2.2.1.2", 1);
  596.  
  597. foreach my $ifindex ( keys %{$res} ) {
  598. my $vlan = $res->{$ifindex};
  599. $self->{ports}->{$ifindex}->vlan($vlan);
  600. }
  601.  
  602. # Get corresponding Voice VLANs
  603.  
  604. $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.68.1.5.1.1.1", 1);
  605.  
  606. foreach my $ifindex ( keys %{$res} ) {
  607. my $vlan = $res->{$ifindex};
  608. next if $vlan == 4096;
  609. $self->{ports}->{$ifindex}->voice($vlan);
  610. }
  611.  
  612.  
  613. # Get any PAGP ports that this port is a member of (Catalyst and Nexus only)
  614.  
  615. $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.98.1.1.1.1.8", 1);
  616.  
  617. foreach my $ifindex ( keys %{$res} ) {
  618. my $po = $res->{$ifindex};
  619. next if $po == $ifindex;
  620.  
  621. $self->{ports}->{$ifindex}->pagp($po); # the ifindex of the port channel this interface is a member of
  622.  
  623. if( exists $self->{ports}->{$po} ) {
  624. my $mode = $self->{ports}->{$po}->mode(); # yet more Cisco lunacy. Port-channel members will show mode as access
  625. $self->{ports}->{$ifindex}->mode($mode) # need to look at port-channel mode
  626. }
  627.  
  628. }
  629.  
  630. $self->{log}->logit("Success.") if $self->{log};
  631.  
  632. return 1;
  633. }
  634.  
  635. sub _LoadPortsAruba {
  636. my $self = shift;
  637.  
  638.  
  639. $self->{log}->logit("Trying to get port mode by walking Aruba arubaWiredPortVlanMemberMode") if $self->{log};
  640.  
  641. my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.47196.4.1.1.3.18.1.1.1.1.2", 1); # arubaWiredPortVlanMemberMode
  642.  
  643. return 0 unless scalar keys %{$res}; # Not Aruba
  644.  
  645. my @ints;
  646.  
  647. foreach my $ifindex ( keys %{$res} ) {
  648. push @ints, $ifindex;
  649. my $mode = $res->{$ifindex};
  650. $self->{log}->logit("Ifindex: $ifindex $mode") if $self->{log};
  651. $self->{ports}->{$ifindex}->mode('Access') if $mode == 2;
  652. $self->{ports}->{$ifindex}->mode('Trunk') if $mode == 1;
  653. }
  654.  
  655. print STDERR "Getting VLAN bitmaps\n";
  656.  
  657. foreach my $ifindex ( @ints ) {
  658. $self->{log}->logit("Getting VLAN bitmap for ifindex: $ifindex") if $self->{log};
  659.  
  660. my $oid = "1.3.6.1.4.1.47196.4.1.1.3.18.1.1.1.1.3.$ifindex";
  661. my $res = $self->{snmp}->get_request(-varbindlist => [ $oid ]);
  662. my $bitmap = $res->{$oid} if $res->{$oid};
  663.  
  664. $self->{log}->logit("Bitmap length: " . length($bitmap)) if $self->{log};
  665.  
  666. my @vlans;
  667. foreach my $offset (0..511) {
  668. my $byte = vec $bitmap, $offset, 8;
  669. push @vlans, $offset * 8 + 1 if $byte & 0b10000000;
  670. push @vlans, $offset * 8 + 2 if $byte & 0b01000000;
  671. push @vlans, $offset * 8 + 3 if $byte & 0b00100000;
  672. push @vlans, $offset * 8 + 4 if $byte & 0b00010000;
  673. push @vlans, $offset * 8 + 5 if $byte & 0b00001000;
  674. push @vlans, $offset * 8 + 6 if $byte & 0b00000100;
  675. push @vlans, $offset * 8 + 7 if $byte & 0b00000010;
  676. push @vlans, $offset * 8 + 8 if $byte & 0b00000001;
  677. }
  678.  
  679. $self->{log}->logit("VLANS: " . join " ", @vlans) if $self->{log};
  680.  
  681. if( 1 == scalar @vlans ) { # Only one MAC?
  682. my $vlan = $vlans[0];
  683. $self->{ports}->{$ifindex}->vlan($vlan); # save it
  684. }
  685.  
  686. foreach my $vlan (@vlans) { # Save the VLAN numbers.
  687. $self->{vlans}->{$vlan} = undef; # Don't know the VLAN name
  688. }
  689.  
  690. }
  691.  
  692. $self->{log}->logit("Success.") if $self->{log};
  693.  
  694. return 1;
  695. }
  696.  
  697. sub _LoadVlansAruba {
  698. my $self = shift;
  699.  
  700. # Already done as best as can by by_LoaPortsAruba
  701.  
  702. }
  703.  
  704.  
  705. # Grab the CAM table, translate bridge ports to ifindex, and translate MAC indexes to MAC addresses
  706. # For Q-BRIDGE MIB switches, you just call this without supplying a VLAN
  707.  
  708. # For Catalyst and Nexus switches, you have to call once per VLAN
  709.  
  710. sub _LoadCam {
  711. my $self = shift;
  712. my $vlan = shift;
  713.  
  714. $self->{log}->logit("Loading CAM table for VLAN: $vlan") if $self->{log};
  715.  
  716. my $snmp = $self->{snmp};
  717.  
  718. if( $vlan ) {
  719. ($snmp, my $err) = Net::SNMP->session( -hostname => $self->{host}, -version => $self->{ver}, -community => $self->{community} . '@' . $vlan, -timeout => $self->{timeout}, -port => "161", -translate=>[-octetstring => 0]);
  720. return unless $snmp;
  721. }
  722.  
  723. my $bpindex = util::tablehash($snmp, "1.3.6.1.2.1.17.1.4.1.2", 1); # dot1dBasePortIfIndex bridgeport to ifindex xref
  724. return unless scalar keys %{$bpindex};
  725.  
  726.  
  727. my $macindex = util::tablehash($snmp, "1.3.6.1.2.1.17.4.3.1.1", 6); # dot1dTpFdbAddress MAC index to MAC
  728. return unless scalar keys %{$macindex};
  729.  
  730. # MAC addresses in this table are in binary because we told Net::SNMP not to translate. Translate them now
  731.  
  732. foreach my $i (keys %{$macindex}) {
  733. $macindex->{$i} = unpack "H*", $macindex->{$i};
  734. }
  735.  
  736. # Grab the CAM table for this VLAN
  737.  
  738. my $res = util::tablehash($snmp, "1.3.6.1.2.1.17.4.3.1.2", 6); # dot1dTpFdbPort
  739. foreach my $maci ( keys %{$res} ) {
  740. next unless $macindex->{$maci}; # No corresponding MAC address for this MAC index
  741.  
  742. my $mac = $macindex->{$maci};
  743. next if $self->{macs}->{$mac}; # ignore this MAC address if it belongs to the switch itself (stupid 2900XL switches)
  744.  
  745. my $bp = $res->{$maci};
  746.  
  747. next unless $bp; # No corresponding ifIndex for this bridgeport id
  748. my $ifindex = $bpindex->{$bp};
  749. $self->{cam}->{$mac} = $ifindex;
  750. $self->{maccnt}->{$ifindex}++;
  751. }
  752.  
  753. return;
  754. }
  755.  
  756. ##############################################################################
  757. #
  758. # Take a binary string of bits and return an array containing the bit numbers
  759. # that are set
  760. #
  761. ##############################################################################
  762.  
  763. sub BitsToIfindex {
  764. my $bits = shift;
  765.  
  766. my @ret;
  767.  
  768. my $last = length($bits) - 1;
  769. foreach my $i (0..$last) {
  770. my $c = ord substr($bits, $i, 1);
  771. push @ret, $i * 8 + 1 if $c & 0b10000000;
  772. push @ret, $i * 8 + 2 if $c & 0b01000000;
  773. push @ret, $i * 8 + 3 if $c & 0b00100000;
  774. push @ret, $i * 8 + 4 if $c & 0b00010000;
  775. push @ret, $i * 8 + 5 if $c & 0b00001000;
  776. push @ret, $i * 8 + 6 if $c & 0b00000100;
  777. push @ret, $i * 8 + 7 if $c & 0b00000010;
  778. push @ret, $i * 8 + 8 if $c & 0b00000001;
  779. }
  780.  
  781. return @ret;
  782.  
  783. }
  784.  
  785.  
  786. package port;
  787.  
  788. sub new {
  789. my $class = shift;
  790. my $name = shift;
  791. my $parent = shift;
  792. my $ifindex = shift;
  793.  
  794. my $self = {};
  795.  
  796. bless ($self, $class);
  797.  
  798. $self->{name} = $name;
  799. $self->{desc} = '';
  800. $self->{mode} = '';
  801. $self->{cdpn} = '';
  802. $self->{status} = '';
  803. $self->{vlan} = '';
  804. $self->{pagp} = '';
  805. $self->{parent} = $parent;
  806. $self->{ifindex} = $ifindex;
  807.  
  808. return $self;
  809. }
  810.  
  811. sub parent {
  812. my $self = shift;
  813. my $p = shift;
  814.  
  815. $self->{parent} = $p if defined $p;
  816. return $self->{parent};
  817. }
  818.  
  819. sub maccnt {
  820. my $self = shift;
  821. return $self->{parent}->maccnt($self->{ifindex});
  822. }
  823.  
  824. sub pagp {
  825. my $self = shift;
  826. my $p = shift;
  827.  
  828. $self->{pagp} = $p if defined $p;
  829. return $self->{pagp};
  830. }
  831.  
  832. sub status {
  833. my $self = shift;
  834. my $p = shift;
  835.  
  836. $self->{status} = $p if defined $p;
  837. return $self->{status};
  838. }
  839.  
  840.  
  841. sub cdpn {
  842. my $self = shift;
  843. my $p = shift;
  844.  
  845. $self->{cdpn} = $p if defined $p;
  846. return $self->{cdpn};
  847. }
  848.  
  849. sub vlan {
  850. my $self = shift;
  851. my $p = shift;
  852.  
  853. $self->{vlan} = $p if defined $p;
  854. return $self->{vlan};
  855. }
  856.  
  857. sub mode {
  858. my $self = shift;
  859. my $p = shift;
  860.  
  861. $self->{mode} = $p if defined $p;
  862. return $self->{mode};
  863. }
  864.  
  865.  
  866. sub desc {
  867. my $self = shift;
  868. my $p = shift;
  869.  
  870. $self->{desc} = $p if defined $p;
  871. return $self->{desc};
  872. }
  873.  
  874. sub type {
  875. my $self = shift;
  876. my $p = shift;
  877.  
  878. $self->{type} = $p if defined $p;
  879. return $self->{type};
  880. }
  881.  
  882.  
  883. sub name {
  884. my $self = shift;
  885. my $p = shift;
  886.  
  887. $self->{name} = $p if defined $p;
  888. return $self->{name};
  889. }
  890.  
  891. sub dot1xcontrol {
  892. my $self = shift;
  893. my $p = shift;
  894.  
  895. $self->{dot1xcontrol} = $p if defined $p;
  896. return $self->{dot1xcontrol} || '';
  897. }
  898.  
  899. sub speed {
  900. my $self = shift;
  901. my $p = shift;
  902.  
  903. $self->{speed} = $p if defined $p;
  904. return 0 + $self->{speed};
  905. }
  906.  
  907. sub voice {
  908. my $self = shift;
  909. my $p = shift;
  910.  
  911. $self->{voice} = $p if defined $p;
  912. return 0 + $self->{voice};
  913. }
  914.  
  915.  
  916. package util;
  917.  
  918. ################################################################################
  919. #
  920. # Build a hash from an SNMP table. The key is created by taking the last $n
  921. # elements of the returned OID value.
  922. #
  923. ################################################################################
  924.  
  925. sub tablehash {
  926. my $snmp = shift;
  927. my $oid = shift;
  928. my $n = shift;
  929.  
  930. my $ret = ();
  931.  
  932. my $res = $snmp->get_table(-baseoid => $oid, -maxrepetitions => 20 );
  933.  
  934. # SNMP version 1 can't do bulk gets
  935.  
  936. if( ! $res ) {
  937. $res = $snmp->get_table(-baseoid => $oid );
  938. }
  939.  
  940. foreach my $x ( keys %{$res} ) {
  941. my @tmp = split /\./, $x;
  942. my $key = join ".", splice(@tmp, -1 * $n);
  943. $ret->{$key} = $res->{$x};
  944. }
  945.  
  946. return $ret;
  947. }
  948.  
  949.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement