Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- use strict;
- use Net::SNMP;
- use Data::Dumper;
- use 5.010;
- my $sw = $ARGV[0];
- my $switch = new switch($sw, 'public', '2c', 30, 6, 1);
- if( ! $switch ) {
- print STDERR "Could not create switch object for: $sw\n";
- return;
- }
- my @ports = $switch->GetPorts();
- my @vlans = $switch->GetVlans();
- my @macs = $switch->GetMacs();
- print STDERR "WARNING $sw read zero ports\n" unless scalar @ports;
- print STDERR "WARNING $sw read zero VLANs\n" unless scalar @vlans;
- print STDERR "WARNING $sw got zero MACs\n" unless scalar @macs;
- foreach my $vlan (@vlans) {
- print "VLAN: $vlan - ", $switch->VlanDesc($vlan), "\n";
- }
- foreach my $ifindex ( sort {$a<=>$b} @ports ) {
- my $port = $switch->GetPort($ifindex);
- my $cdpn = $port->cdpn();
- $cdpn =~ s/\(SAL..*\)//; # Damn Nexus switches
- next unless $port->type() == 6; # 6 = ethernet
- next if $port->name() eq '(other)'; # Bizzaro port, ignore
- next if $port->name() =~ /^Po[0-9]+/; # port-channel, ignore
- next if $port->name() =~ /^port-channel/; # ditto
- print "Name: ", $port->name(), " ";
- print "Mode: ", $port->mode(), " ";
- print "CDP: ", $port->cdpn(), " ";
- print "Voice: ", $port->voice(), " ";
- print "Speed: ", $port->speed(), " ";
- if( $port->pagp() ) {
- print "LAG: ", $switch->GetPort($port->pagp())->name(), " ";
- } else {
- print "LAG: ";
- }
- print "MACs: ", $switch->maccnt($ifindex), " ";
- print "VLAN: ", $port->vlan(), " ";
- print "\n";
- }
- exit 0;
- ###############################################################################
- #
- # Logger object. Writes formatted messages to STDOUT and to log file (if passed)
- #
- ###############################################################################
- package Logger;
- sub new {
- my $class = shift;
- my $fname = shift;
- my $self = {};
- $self->{fname} = $fname;
- print STDERR "Opening $fname\n";
- return undef unless $fname;
- if( $fname ) {
- if( ! open FD, ">$fname" ) {
- warn "Could not open log $fname. Error: $!\n";
- return undef;
- }
- $self->{logfd} = \*FD;
- }
- bless $self;
- return $self;
- }
- sub logit {
- my $self = shift;
- my $s = shift;
- return unless $self->{logfd};
- my $time = shift;
- my $caller = (caller(1))[3];
- $caller = "(MAIN)" unless $caller;
- my ($sc, $mn, $hr, $md, $mo, $yr, $wd, $yd, $isdst) = localtime();
- 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);
- print {$self->{logfd}} $txt;
- print STDERR $txt;
- }
- ################################################################################
- #
- # Switch object
- #
- ################################################################################
- package switch;
- sub new {
- my $class = shift;
- my $host = shift;
- my $community = shift || 'public';
- my $ver = shift || '2c';
- my $timeout = shift || 10;
- my $retries= shift || 3;
- my $debug = shift || 0;
- my $self = {};
- bless ($self, $class);
- $self->{host} = $host;
- $self->{community} = $community;
- $self->{ver} = $ver;
- $self->{timeout} = $timeout;
- $self->{vlans} = {}; # hash of VLAN names indexed by VLAN number
- $self->{ports} = {}; # hash of port obects indexed by ifindex number
- $self->{arp} = {}; # arp cache
- $self->{cam} = {}; # cam table
- $self->{maccnt} = {}; # MAC count per ifindex
- $self->{dot1x} = undef; # is switch configured for dot1x port control?
- $self->{log} = new Logger($self->{host} . "-debug.log") if $debug;
- # Open SNMP connection. Don't translate octet strings - Net::SNMP doesn't always get it right - we'll do it ourself
- ($self->{snmp}, my $err) = Net::SNMP->session( -hostname => $host, -version => $ver, -community => $community, -timeout => $timeout, -retries => $retries, -port => "161", -translate=>[-octetstring => 0]);
- if ( !defined($self->{snmp}) ) {
- print STDERR "$host: SNMP open error: $err\n";
- return undef;
- }
- # Generic information that both styles of switch might support
- # get the port names
- $self->{log}->logit("Getting port names") if $self->{log};
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.1", 1); # ifName
- foreach my $ifindex ( keys %{$res} ) { # entries will look like '234' => 'Gi6/39'
- my $ifname = $res->{$ifindex};
- $self->{ports}{$ifindex} = port->new($ifname, $self, $ifindex);
- $self->{maccnt}->{$ifindex} = 0;
- $self->{log}->logit("IfIndex: $ifindex Name: $ifname") if $self->{log};
- }
- # port descriptions
- $self->{log}->logit("Getting port descriptions") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.18", 1); # ifAlias
- foreach my $ifindex ( keys %{$res} ) { # entries will look like '236' => 'Uplink to E54APG06'
- my $desc = $res->{$ifindex};
- $self->{ports}->{$ifindex}->desc($desc);
- $self->{log}->logit("IfIndex: $ifindex Desc: $desc") if $self->{log};
- }
- # port types
- $self->{log}->logit("Getting port types") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.3", 1); # ifType. ethernet = 6, SVI = 53
- foreach my $ifindex ( keys %{$res} ) {
- my $type = $res->{$ifindex};
- $self->{ports}->{$ifindex}->type($type);
- $self->{log}->logit("IfIndex: $ifindex Type: $type") if $self->{log};
- }
- # CDP neighbor
- $self->{log}->logit("Getting CDP information") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.23.1.2.1.1.6", 2);
- foreach my $x ( keys %{$res} ) {
- my ($ifIndex, $junk) = split /\./, $x;
- $self->{ports}->{$ifIndex}->cdpn($res->{$x}) unless $res->{$x} =~ /[^[:print:]]/; # Store CDP info unless there is unprintable crap.
- }
- # get port status
- # ( 1=>'up', 2=>'down', 3=>'testing', 4=>'unknown', 5=>'dormant', 6=>'notPresent', 7=>'lowerLayerDown');
- $self->{log}->logit("Getting port status") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.8", 1);
- foreach my $ifindex ( keys %{$res} ) {
- my $mode = $res->{$ifindex};
- $self->{log}->logit("IfIndex: $ifindex Status: $mode") if $self->{log};
- $self->{ports}->{$ifindex}->status('up') if $mode == 1;
- $self->{ports}->{$ifindex}->status('down') if $mode == 2;
- $self->{ports}->{$ifindex}->status('testing') if $mode == 3;
- $self->{ports}->{$ifindex}->status('unknown') if $mode == 4;
- $self->{ports}->{$ifindex}->status('dormant') if $mode == 5;
- $self->{ports}->{$ifindex}->status('notPresent') if $mode == 6;
- $self->{ports}->{$ifindex}->status('lowerLayerDown') if $mode == 7;
- }
- # get port speed
- $self->{log}->logit("Getting port speed") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.15", 1); # IF-MIB::ifHighSpeed (units = megabits/sec)
- foreach my $ifindex ( keys %{$res} ) {
- my $speed = $res->{$ifindex};
- $self->{ports}->{$ifindex}->speed($speed);
- $self->{log}->logit("IfIndex: $ifindex Speed: $speed") if $self->{log};
- }
- # dot1x control (1 = forced unauthorized, 2 = auto, 3 = forced authorized
- $self->{log}->logit("Getting Dot1X control") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.0.8802.1.1.1.1.2.1.1.6", 1); # dot1xAuthAuthControlledPortControl
- foreach my $ifindex ( keys %{$res} ) {
- my $dot1x = $res->{$ifindex};
- $self->{log}->logit("IfIndex: $ifindex Dot1X: $dot1x") if $self->{log};
- $self->{ports}->{$ifindex}->dot1xcontrol('');
- $self->{ports}->{$ifindex}->dot1xcontrol('forced unauthorized') if $dot1x == 1;
- $self->{ports}->{$ifindex}->dot1xcontrol('auto') if $dot1x == 2;
- $self->{ports}->{$ifindex}->dot1xcontrol('forced authorized') if $dot1x == 3;
- }
- # Get MAC addresses associated with device
- $self->{log}->logit("Getting MAC addresses of device") if $self->{log};
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.6", 1);
- foreach my $key ( keys %{$res} ) {
- my $mac = unpack "H*", $res->{$key}; # We told Net::SNMP to not decode byte strings. Decode MAC address
- next if length($mac) != 12; # not a valid MAC address
- $self->{macs}->{$mac} = 1;
- }
- # Attempt to get the VLANs and descriptions by using the Cisco Catalyst/Nexus way. If that fails, revert to Q-BRIDGE
- # If that fails, punt and get VLANs
- if( $self->_LoadVlansCisco() ) { # Try Catalyst/Nexus
- $self->_LoadPortsCisco();
- my @vlans = $self->GetVlans();
- foreach my $vlan (@vlans) {
- $self->_LoadCam($vlan);
- }
- } elsif( $self->_LoadVlansQbridge() ) { # Try QBridge MIB
- $self->_LoadPortsQbridge();
- $self->_LoadCam();
- } else { #
- $self->_LoadPortsAruba(); # Try Aruba. LoadPortsAruba also gets the VLANs
- $self->_LoadCam();
- }
- return $self;
- }
- sub name {
- my $self = shift;
- my $p = shift;
- $self->{host} = $p if defined $p;
- return $self->{host};
- }
- sub maccnt {
- my $self = shift;
- my $ifindex = shift;
- return undef unless exists $self->{maccnt}->{$ifindex};
- return $self->{maccnt}->{$ifindex};
- }
- sub LoadArpCache {
- my $self = shift;
- # We told Net::SNMP to not encode binary strings, so the MAC addresses will be six byte binary blobs
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.3.1.1.2", 4);
- foreach my $ip ( keys %{$res} ) { # entries will look like '1.2.3.4' => '00112233445566'
- my $mac = $res->{$ip};
- $self->{arp}{$ip} = $mac;
- }
- # Convert binary to text characters
- foreach my $i (keys %{$self->{arp}}) {
- $self->{arp}->{$i} = unpack "H*", $self->{arp}->{$i};
- }
- return keys %{$self->{arp}};
- }
- sub ArpEntry {
- my $self = shift;
- my $ip = shift;
- return $self->{arp}->{$ip} if exists $self->{arp}->{$ip};
- return undef;
- }
- sub GetPorts {
- my $self = shift;
- return sort {$a<=>$b} keys %{$self->{ports}};
- }
- sub GetPort { # Given an ifindex value, return port object
- my $self = shift;
- my $p = shift;
- if( ! $self->{ports}->{$p} ) { # No port exists for this ifindex?
- $self->{ports}->{$p} = port->new($p); # create one - use the ifindex as the name
- }
- return $self->{ports}->{$p};
- }
- sub GetVlans {
- my $self = shift;
- return sort {$a<=>$b} keys %{$self->{vlans}};
- }
- sub VlanDesc { # for a given VLAN number, return the description
- my $self = shift;
- my $vlan = shift;
- my $p = shift;
- $self->{vlans}->{$vlan} = $p if defined $p;
- return $self->{vlans}->{$vlan};
- }
- # Is this switch dot1x enabled?
- sub Dot1x {
- my $self = shift;
- return $self->{dot1x} if defined $self->{dot1x};
- $self->{dot1x} = 0;
- my $oid = "1.0.8802.1.1.1.1.1.1.0";
- my $res = $self->{snmp}->get_request(-varbindlist => [ $oid ]);
- $self->{dot1x} = 1 if $res->{$oid} and $res->{$oid} == 1;
- return $self->{dot1x};
- }
- sub GetMacs {
- my $self = shift;
- return sort keys %{$self->{cam}};
- }
- sub MacToIfindex {
- my $self = shift;
- my $mac = shift;
- return $self->{cam}->{$mac};
- }
- ###############################################################################
- #
- # Q-BRIDGE MIB get vlans and vlan descriptions. Return count
- #
- ###############################################################################
- sub _LoadVlansQbridge {
- my $self = shift;
- $self->{log}->logit("Trying to get VLANS by walking dot1qVlanStaticName") if $self->{log};
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.3.1.1", 1); # dot1qVlanStaticName
- foreach my $vlan ( keys %{$res} ) {
- $self->{log}->logit("Found VLAN $vlan = $res->{$vlan}") if $self->{log};
- $self->{vlans}->{$vlan} = $res->{$vlan};
- }
- my $n = keys %{$self->{vlans}};
- $self->{log}->logit("Got $n VLANs" ) if $self->{log};
- return $n;
- }
- ###############################################################################
- #
- # Last resort version of get vlans and vlan descriptions. Return count
- #
- ###############################################################################
- sub _LoadVlansPunt {
- my $self = shift;
- $self->{log}->logit("Trying to get VLANS by finding interfaces defined as type VLAN") if $self->{log};
- foreach my $ifindex ( keys %{$self->{ports}} ) {
- my $port = $self->GetPort($ifindex);
- next unless $port->type() == 53; #propVirtual (VLAN interface)
- $self->{vlans}->{$ifindex} = $port->desc();
- $self->{log}->logit("Found VLAN " . $port->desc() ) if $self->{log};
- }
- my $n = keys %{$self->{vlans}};
- $self->{log}->logit("Got $n VLANs" ) if $self->{log};
- return $n;
- }
- ###############################################################################
- #
- # Cisco Catalyst/Nexus get vlans and vlan descriptions
- #
- ###############################################################################
- sub _LoadVlansCisco {
- my $self = shift;
- # Do it the Catalyst/Nexus way using vtp information.
- # Get a list of the VLANs present on this switch and create a VLAN object
- #$res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.2", 1); # vtpVlanState
- #
- #foreach my $vlan ( keys %{$res} ) {
- # next unless $res->{$vlan}; # VLAN is not operational
- # $self->{vlans}->{$vlan} = vlan->new(($self->{name}, $vlan)); # Create a VLAN object
- #}
- # get VLAN descriptions
- $self->{log}->logit("Trying to get VLANS by walking vtpVlanName") if $self->{log};
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.4.1", 1); # vtpVlanName
- foreach my $vlan ( keys %{$res} ) { # entries will look like '3' => '85175_wire_room_E'
- $self->{log}->logit("Found VLAN $vlan = $res->{$vlan}" ) if $self->{log};
- $self->{vlans}->{$vlan} = $res->{$vlan};
- }
- my $n = keys %{$self->{vlans}};
- $self->{log}->logit("Got $n VLANs" ) if $self->{log};
- return $n;
- }
- ###############################################################################
- #
- # Q-BRIDGE port config
- #
- ###############################################################################
- sub _LoadPortsQbridge {
- my $self = shift;
- $self->{log}->logit("Trying to get port mode by walking dot1qVlanCurrentEgressPorts") if $self->{log};
- # port mode (trunk or access)
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.2.1.4", 1); # dot1qVlanCurrentEgressPorts
- return undef unless scalar keys %{$res}; # Nothing returned, not QBridge
- # Returns a bitmap for each VLAN. Each bit in the bitmap corresponds to an ifIndex.
- # If a bit is set in the bitmap, the corresponding ifindex allows the VLAN to egress
- # In theory access ports should allow only one VLAN to egress.
- my %cnt; # How many VLANs are allowed egress on each port
- my @vlans; # Save the VLAN numbers as found
- foreach my $vlan ( keys %{$res} ) { # bit array of ports where VLAN is allowed egress
- push @vlans, $vlan;
- my $bitmap = $res->{$vlan};
- $self->{log}->logit("Bitmap length: " . length($bitmap)) if $self->{log};
- my @ifindexes;
- foreach my $offset (0..length($bitmap)) {
- my $byte = vec $bitmap, $offset, 8;
- push @ifindexes, $offset * 8 + 1 if $byte & 0b10000000;
- push @ifindexes, $offset * 8 + 2 if $byte & 0b01000000;
- push @ifindexes, $offset * 8 + 3 if $byte & 0b00100000;
- push @ifindexes, $offset * 8 + 4 if $byte & 0b00010000;
- push @ifindexes, $offset * 8 + 5 if $byte & 0b00001000;
- push @ifindexes, $offset * 8 + 6 if $byte & 0b00000100;
- push @ifindexes, $offset * 8 + 7 if $byte & 0b00000010;
- push @ifindexes, $offset * 8 + 8 if $byte & 0b00000001;
- }
- foreach my $ifindex (@ifindexes) {
- $self->{log}->logit("VLAN $vlan egresses Ifindex: $ifindex") if $self->{log};
- $cnt{$ifindex}++;
- }
- }
- foreach my $ifindex ( keys %{$self->{ports}} ) {
- $self->{log}->logit("ifindex: $ifindex VLAN count: $cnt{$ifindex}") if $self->{log};
- if( $cnt{$ifindex} > 1) {
- $self->{ports}->{$ifindex}->mode('Trunk');
- } else {
- $self->{ports}->{$ifindex}->mode('Access');
- }
- }
- # Returns a bitmap for each VLAN. Each bit in the bitmap corresponds to an ifIndex.
- # If a bit is set in the bitmap, the corresponding ifindex is untagged
- $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.7.1.4.2.1.5", 1); # dot1qVlanCurrentUntaggedPorts
- foreach my $vlan ( keys %{$res} ) { # bit array of ports where VLAN is allowed egress
- my $bits = $res->{$vlan};
- my @tmp = BitsToIfindex($bits);
- foreach my $ifindex (@tmp) {
- $self->{ports}->{$ifindex}->vlan($vlan);
- }
- }
- # Get any LAG interfaces that this port is a member of
- $res = util::tablehash($self->{snmp}, "1.2.840.10006.300.43.1.2.1.1.12", 1); # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID
- foreach my $ifindex ( keys %{$res} ) {
- my $lagindex = $res->{$ifindex};
- next if $lagindex == $ifindex; # This port is not a LAG
- $self->{log}->logit("ifindex: $ifindex is memger of LAG $lagindex") if $self->{log};
- $self->{ports}->{$ifindex}->pagp($lagindex); # the ifindex of the port channel this interface is a member of
- }
- $self->{log}->logit("Success. Mapped ports to " . scalar @vlans . " VLANs") if $self->{log};
- return 1;
- }
- sub _LoadPortsCisco {
- my $self = shift;
- # port mode (trunk or access) (only for Cisco Catalyst and Nexus)
- $self->{log}->logit("Trying to get port mode by walking vlanTrunkPortDynamicStatus") if $self->{log};
- my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.6.1.1.14", 1); # vlanTrunkPortDynamicStatus
- return 0 unless scalar keys %{$res};
- foreach my $ifindex ( keys %{$res} ) {
- my $mode = $res->{$ifindex};
- $self->{ports}->{$ifindex}->mode('Access') if $mode == 2;
- $self->{ports}->{$ifindex}->mode('Trunk') if $mode == 1;
- }
- # Get corresponding VLANs (Catalyst and Nexus only)
- $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.68.1.2.2.1.2", 1);
- foreach my $ifindex ( keys %{$res} ) {
- my $vlan = $res->{$ifindex};
- $self->{ports}->{$ifindex}->vlan($vlan);
- }
- # Get corresponding Voice VLANs
- $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.68.1.5.1.1.1", 1);
- foreach my $ifindex ( keys %{$res} ) {
- my $vlan = $res->{$ifindex};
- next if $vlan == 4096;
- $self->{ports}->{$ifindex}->voice($vlan);
- }
- # Get any PAGP ports that this port is a member of (Catalyst and Nexus only)
- $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.98.1.1.1.1.8", 1);
- foreach my $ifindex ( keys %{$res} ) {
- my $po = $res->{$ifindex};
- next if $po == $ifindex;
- $self->{ports}->{$ifindex}->pagp($po); # the ifindex of the port channel this interface is a member of
- if( exists $self->{ports}->{$po} ) {
- my $mode = $self->{ports}->{$po}->mode(); # yet more Cisco lunacy. Port-channel members will show mode as access
- $self->{ports}->{$ifindex}->mode($mode) # need to look at port-channel mode
- }
- }
- $self->{log}->logit("Success.") if $self->{log};
- return 1;
- }
- sub _LoadPortsAruba {
- my $self = shift;
- $self->{log}->logit("Trying to get port mode by walking Aruba arubaWiredPortVlanMemberMode") if $self->{log};
- 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
- return 0 unless scalar keys %{$res}; # Not Aruba
- my @ints;
- foreach my $ifindex ( keys %{$res} ) {
- push @ints, $ifindex;
- my $mode = $res->{$ifindex};
- $self->{log}->logit("Ifindex: $ifindex $mode") if $self->{log};
- $self->{ports}->{$ifindex}->mode('Access') if $mode == 2;
- $self->{ports}->{$ifindex}->mode('Trunk') if $mode == 1;
- }
- print STDERR "Getting VLAN bitmaps\n";
- foreach my $ifindex ( @ints ) {
- $self->{log}->logit("Getting VLAN bitmap for ifindex: $ifindex") if $self->{log};
- my $oid = "1.3.6.1.4.1.47196.4.1.1.3.18.1.1.1.1.3.$ifindex";
- my $res = $self->{snmp}->get_request(-varbindlist => [ $oid ]);
- my $bitmap = $res->{$oid} if $res->{$oid};
- $self->{log}->logit("Bitmap length: " . length($bitmap)) if $self->{log};
- my @vlans;
- foreach my $offset (0..511) {
- my $byte = vec $bitmap, $offset, 8;
- push @vlans, $offset * 8 + 1 if $byte & 0b10000000;
- push @vlans, $offset * 8 + 2 if $byte & 0b01000000;
- push @vlans, $offset * 8 + 3 if $byte & 0b00100000;
- push @vlans, $offset * 8 + 4 if $byte & 0b00010000;
- push @vlans, $offset * 8 + 5 if $byte & 0b00001000;
- push @vlans, $offset * 8 + 6 if $byte & 0b00000100;
- push @vlans, $offset * 8 + 7 if $byte & 0b00000010;
- push @vlans, $offset * 8 + 8 if $byte & 0b00000001;
- }
- $self->{log}->logit("VLANS: " . join " ", @vlans) if $self->{log};
- if( 1 == scalar @vlans ) { # Only one MAC?
- my $vlan = $vlans[0];
- $self->{ports}->{$ifindex}->vlan($vlan); # save it
- }
- foreach my $vlan (@vlans) { # Save the VLAN numbers.
- $self->{vlans}->{$vlan} = undef; # Don't know the VLAN name
- }
- }
- $self->{log}->logit("Success.") if $self->{log};
- return 1;
- }
- sub _LoadVlansAruba {
- my $self = shift;
- # Already done as best as can by by_LoaPortsAruba
- }
- # Grab the CAM table, translate bridge ports to ifindex, and translate MAC indexes to MAC addresses
- # For Q-BRIDGE MIB switches, you just call this without supplying a VLAN
- # For Catalyst and Nexus switches, you have to call once per VLAN
- sub _LoadCam {
- my $self = shift;
- my $vlan = shift;
- $self->{log}->logit("Loading CAM table for VLAN: $vlan") if $self->{log};
- my $snmp = $self->{snmp};
- if( $vlan ) {
- ($snmp, my $err) = Net::SNMP->session( -hostname => $self->{host}, -version => $self->{ver}, -community => $self->{community} . '@' . $vlan, -timeout => $self->{timeout}, -port => "161", -translate=>[-octetstring => 0]);
- return unless $snmp;
- }
- my $bpindex = util::tablehash($snmp, "1.3.6.1.2.1.17.1.4.1.2", 1); # dot1dBasePortIfIndex bridgeport to ifindex xref
- return unless scalar keys %{$bpindex};
- my $macindex = util::tablehash($snmp, "1.3.6.1.2.1.17.4.3.1.1", 6); # dot1dTpFdbAddress MAC index to MAC
- return unless scalar keys %{$macindex};
- # MAC addresses in this table are in binary because we told Net::SNMP not to translate. Translate them now
- foreach my $i (keys %{$macindex}) {
- $macindex->{$i} = unpack "H*", $macindex->{$i};
- }
- # Grab the CAM table for this VLAN
- my $res = util::tablehash($snmp, "1.3.6.1.2.1.17.4.3.1.2", 6); # dot1dTpFdbPort
- foreach my $maci ( keys %{$res} ) {
- next unless $macindex->{$maci}; # No corresponding MAC address for this MAC index
- my $mac = $macindex->{$maci};
- next if $self->{macs}->{$mac}; # ignore this MAC address if it belongs to the switch itself (stupid 2900XL switches)
- my $bp = $res->{$maci};
- next unless $bp; # No corresponding ifIndex for this bridgeport id
- my $ifindex = $bpindex->{$bp};
- $self->{cam}->{$mac} = $ifindex;
- $self->{maccnt}->{$ifindex}++;
- }
- return;
- }
- ##############################################################################
- #
- # Take a binary string of bits and return an array containing the bit numbers
- # that are set
- #
- ##############################################################################
- sub BitsToIfindex {
- my $bits = shift;
- my @ret;
- my $last = length($bits) - 1;
- foreach my $i (0..$last) {
- my $c = ord substr($bits, $i, 1);
- push @ret, $i * 8 + 1 if $c & 0b10000000;
- push @ret, $i * 8 + 2 if $c & 0b01000000;
- push @ret, $i * 8 + 3 if $c & 0b00100000;
- push @ret, $i * 8 + 4 if $c & 0b00010000;
- push @ret, $i * 8 + 5 if $c & 0b00001000;
- push @ret, $i * 8 + 6 if $c & 0b00000100;
- push @ret, $i * 8 + 7 if $c & 0b00000010;
- push @ret, $i * 8 + 8 if $c & 0b00000001;
- }
- return @ret;
- }
- package port;
- sub new {
- my $class = shift;
- my $name = shift;
- my $parent = shift;
- my $ifindex = shift;
- my $self = {};
- bless ($self, $class);
- $self->{name} = $name;
- $self->{desc} = '';
- $self->{mode} = '';
- $self->{cdpn} = '';
- $self->{status} = '';
- $self->{vlan} = '';
- $self->{pagp} = '';
- $self->{parent} = $parent;
- $self->{ifindex} = $ifindex;
- return $self;
- }
- sub parent {
- my $self = shift;
- my $p = shift;
- $self->{parent} = $p if defined $p;
- return $self->{parent};
- }
- sub maccnt {
- my $self = shift;
- return $self->{parent}->maccnt($self->{ifindex});
- }
- sub pagp {
- my $self = shift;
- my $p = shift;
- $self->{pagp} = $p if defined $p;
- return $self->{pagp};
- }
- sub status {
- my $self = shift;
- my $p = shift;
- $self->{status} = $p if defined $p;
- return $self->{status};
- }
- sub cdpn {
- my $self = shift;
- my $p = shift;
- $self->{cdpn} = $p if defined $p;
- return $self->{cdpn};
- }
- sub vlan {
- my $self = shift;
- my $p = shift;
- $self->{vlan} = $p if defined $p;
- return $self->{vlan};
- }
- sub mode {
- my $self = shift;
- my $p = shift;
- $self->{mode} = $p if defined $p;
- return $self->{mode};
- }
- sub desc {
- my $self = shift;
- my $p = shift;
- $self->{desc} = $p if defined $p;
- return $self->{desc};
- }
- sub type {
- my $self = shift;
- my $p = shift;
- $self->{type} = $p if defined $p;
- return $self->{type};
- }
- sub name {
- my $self = shift;
- my $p = shift;
- $self->{name} = $p if defined $p;
- return $self->{name};
- }
- sub dot1xcontrol {
- my $self = shift;
- my $p = shift;
- $self->{dot1xcontrol} = $p if defined $p;
- return $self->{dot1xcontrol} || '';
- }
- sub speed {
- my $self = shift;
- my $p = shift;
- $self->{speed} = $p if defined $p;
- return 0 + $self->{speed};
- }
- sub voice {
- my $self = shift;
- my $p = shift;
- $self->{voice} = $p if defined $p;
- return 0 + $self->{voice};
- }
- package util;
- ################################################################################
- #
- # Build a hash from an SNMP table. The key is created by taking the last $n
- # elements of the returned OID value.
- #
- ################################################################################
- sub tablehash {
- my $snmp = shift;
- my $oid = shift;
- my $n = shift;
- my $ret = ();
- my $res = $snmp->get_table(-baseoid => $oid, -maxrepetitions => 20 );
- # SNMP version 1 can't do bulk gets
- if( ! $res ) {
- $res = $snmp->get_table(-baseoid => $oid );
- }
- foreach my $x ( keys %{$res} ) {
- my @tmp = split /\./, $x;
- my $key = join ".", splice(@tmp, -1 * $n);
- $ret->{$key} = $res->{$x};
- }
- return $ret;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement