Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Allowed VLANs with SNMP

by spivey49 (Monk)
on Oct 23, 2008 at 17:33 UTC ( #719096=perlquestion: print w/replies, xml ) Need Help??

spivey49 has asked for the wisdom of the Perl Monks concerning the following question:

I'm modifying Cisco SNMP CDP Poll by fingers to grab the allowed VLANs on trunk ports using SNMP. The OID I'm using is 1.3.6.1.4.1.9.5.1.9.3.1.5. I can get the information, but I'm not entirely sure how to translate it.

The output for Get_SNMP_Info("1.3.6.1.4.1.9.5.1.9.3.1.5".$unique_oid) is

0xfffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffe

which should translate into VLANs 0-1023 being allowed I believe, given the description of the OID below and a review of the switch's VLAN table from a term session.

Cisco's description of the OID is as follows:

An indication of which Virtual LANs are allowed on this Inter-Switch Link. This is an octet string value with bits set to indicate allowed VLANs. It can be interpreted as a sum of f(x) as x goes from 0 to 1023, where f(x) = 0 for VLAN x not allowed and f(x) = exp(2, x) for VLAN x allowed.

Any thoughts how to write this formula in Perl?

Update: I did find this WSH script on The Moose and Squirrel Files

Fortunately large number arithmetic is not required to reveal the allowed vlans. Each pair of hexadecimal digits is converted to a binary string and concatenated to form the binary representation of the number. Each bit that is set corresponds to a vlan. The string is reversed using the strreverse() function and then searched for the character 1′ using the instr() function. The index value returned from instr is the vlan number + 1. The vlan is obtained by subtracting 1 from the returned index and the search repeated until no more matches are found in the string. More detail is available by referring to the enum_AllowedVLAN() and supporting functions in listing 1

Listing 1:
const vlanPortIslVlansAllowed = " .1.3.6.1.4.1.9.5.1.9.3.1.5.1.1 " const SNMPGETCMD2 = "f:\usr\bin\snmpget.exe -Ov -v 2c -c " '********************************************************************* +*** 'FUNCTION: + * ' enum_AllowedVLAN(strAgent) + * 'Purpose: + * ' enumerate the vlans configured on the switch. + * ' + * 'Inputs: + * ' strAgent: management IP address of the switch + * ' + * 'Returns: + * ' Array with each element containing a vlan number + * ' + * 'Calls: + * ' SNMPGETCMD2 - constant defining the path to an external + * ' program and options used to perform an snmp get operation. + * ' fmtBinary - function to left pad a binary number with zeros + * ' ToBinary - function to convert an integer to a binary string + * ' + * 'Comments: + * ' CISCO-STACK-MIB is cisco specific. + * ' Reference Cisco SNMP Object Navigator viewed at + * ' http://tools.cisco.com/Support/SNMP/do/BrowseOID.do? + * ' objectInput=vlanPortIslVlansAllowed&translate=Translate& + * ' submitValue=SUBMIT&submitClicked=true + * ' on 16/11/2006 + * '********************************************************************* +*** function enum_AllowedVLAN(strAgent) dim WshShell, oExec dim re 'as regexp dim matches dim match, submatch dim tempstr, stroutput, index, vlans Set WshShell = CreateObject("WScript.Shell") Set oExec = WshShell.Exec(SNMPGETCMD2 & SNMPREAD & " " & _ strAgent & " " & vlanPortIslVlansAllowed) Do while Not oExec.StdOut.AtEndOfStream stroutput = oExec.StdOut.readall Loop Do While oExec.Status <> 1 WScript.Sleep 100 Loop tempstr = "" set re = new regexp re.global = True re.multiline = True 'Pattern to capture the hex digits representing the allowed vlans. re.pattern = "[0-9a-fA-F]{2}" vlans = "" if instr(1,stroutput, "Hex-STRING:") > 0 then set matches = re.execute(stroutput) for each match in matches tempstr = tempstr & fmtBinary(ToBinary(cint("&H" & match)), 8) next tempstr = strreverse(tempstr) index = 1 do index = instr(index, tempstr, "1") if index <> 0 then vlans = vlans & " " & index - 1 index = index + 1 end if loop until index = 0 end if enum_AllowedVLAN = split(trim(vlans)) end function '********************************************************************* +*** 'FUNCTION: + * ' fmtBinary(strNumber, intLength) + * 'PURPOSE: + * ' function to left pad a binary number with zeros + * ' + * 'INPUTS: + * ' strNumber: binary number to left pad with zeros + * ' intLength: The desired bit length of the binary number + * ' + * 'RETURNS: + * ' string containing the binary representation of the input. + * ' + * 'CALLS: + * ' Nothing + * ' + * 'COMMENTS: + * '********************************************************************* +*** function fmtBinary(strNumber, intLength) fmtBinary = string(intLength - len(strNumber), "0") & strNumber end function '********************************************************************* +*** 'FUNCTION: + * ' ToBinary(intNumber) + * ' + * 'PURPOSE: + * ' convert an integer number to binary. + * ' + * 'Inputs: + * ' intNumber: Number to convert to binary + * ' + * 'Returns: + * ' string containing the binary representation of the input. + * ' + * 'Calls: + * ' Nothing + * ' + * 'Comments: + * ' note the use of \ (integer division operator) rather than / + * '********************************************************************* +*** function ToBinary(intNumber) if intNumber > 0 then ToBinary = ToBinary(intNumber\2) & intNumber mod 2 end if end function

Update: Thanks for all the help! I ended up using a different OID. Cisco doesn't support the Stack MIBs on all devices. Here's the script with the allowed VLANs.

use strict; use Net::SNMP; use Cisco::CopyConfig; my($error,$session,$seed_oid,$oid_root,$community,$hostname,$seed_ip,$ +found, $htype,$tftpsvr,$line,$key,$value,$sysname,$ssn); my ($osysname,$ochassis,$ossn,$oifdesc,$ovlanallowed);#OID constants my(%done,%devices,%ssndone, %shapes); my(@todo, @lines, @hostinfo); #The sysObjectID OID returns a vendor system identifier OID #Read OIDs and descriptions from file to translate the OIDs open (FH, "c:/CiscoSysObjIDs.csv"); while (<FH>){ chomp($_); @lines = split ",", $_; if ($lines[0]){ $devices{$lines[0]} = $lines[1]; } } close FH; #Define OID constants $osysname = "1.3.6.1.2.1.1.5.0"; $ochassis = "1.3.6.1.2.1.1.2.0"; $ossn = "1.3.6.1.2.1.47.1.1.1.1.11.1"; $oifdesc = "1.3.6.1.2.1.2.2.1.2"; #Vlan allowed OID will show only the VLANs not pruned. #Administratively allowed VLANs is 1.3.6.1.4.1.9.5.1.9.3.1.5 $ovlanallowed = "1.3.6.1.4.1.9.9.46.1.6.1.1.4"; $done{"0.0.0.0"}=1; #We need a starting point, get an IP from command line die "usage: $0 seedip" unless ( @ARGV == 1 ); $seed_ip = $ARGV[0]; die "usage: $0 seedip" unless ($seed_ip =~ m{\d+\.\d+\.\d+\.\d+}); $tftpsvr = "xxx.xxx.xxx.xxx"; @todo=($seed_ip); #List of possible targets $oid_root = "1.3.6.1.4.1.9.9.23.1.2.1.1"; $seed_oid = ("$oid_root".".3"); print "Host Name,Host IP,Host Type,Local Interface,Neighbor IP,Neighbo +r Name,Remote Interface,Neighbor Type,Vlans Allowed to Neighbor\n"; while(@todo){ #Grab a target and go to work $hostname= shift(@todo); $community = "comm1"; unless(exists $done{$hostname}){ #Make sure we haven't done this +one yet $done{$hostname}=1; #Remember that we checked this IP + #Open SNMP session ($session,$error) = Net::SNMP->session(Hostname => $hostname, +Community => $community); return unless($session); $sysname = Get_SNMP_Info($osysname); unless($sysname){ $community = "comm2"; Net::SNMP->session(Hostname => $hostn +ame, Community => $community); $sysname = Get_SNMP_Info($osysname); } unless($sysname){ $community = "comm3"; Net::SNMP->session(Hostname => $hostn +ame, Community => $community); $sysname = Get_SNMP_Info($osysname); } unless($sysname){$sysname = "Unknown device name";} $htype =(Get_SNMP_Info($ochassis)); $htype =~ s/1\.3\.6\.1\.4\.1\.9\.1\.//; if ($htype){ for my $key ( keys %devices ) { if ($htype eq $key) { $htype = $devices{$key} ;last; } } } else{$htype = "Unkown device type";} $ssn = Get_SNMP_Info($ossn); unless ($ssn){$ssn = "UknownSSN$found.$sysname.$hostna +me"};#Some devices do not support the entPhysical Table $ssndone{$ssn}=1; #Remember that we checked this ssn unless(exists $done{$ssn}){ #Make sure we haven't don +e this one yet print "$sysname,$hostname,$htype\n"; $found++; Get_Config($ +hostname,$community,$tftpsvr,$sysname.".cfg"); get_oids($seed_oid);#Get the SNMP info for thi +s target } $session->close; } } print $found."devices found\n"; #---------------------------------------------------------- #This sub finds out how many neighbors the target has #and determines what oids we need to use to get the info that #we want, then calls other subs to get that info #---------------------------------------------------------- sub get_oids{ my($starting_oid , $new_oid , $unique_oid , $result , $crap, $inde +x,$if_oid); my($ip , $name , $port, $type); $starting_oid = $_[0]; $new_oid = $starting_oid ; while(Net::SNMP::oid_context_match($starting_oid,$new_oid)){ $result = $session->get_next_request(($new_oid)); return unless (defined $result); ($new_oid , $crap) = %$result; if (Net::SNMP::oid_context_match($starting_oid,$new_oid)){ $unique_oid = $new_oid; $unique_oid =~ s/$starting_oid//g; $ip = (Convert_IP(Get_SNMP_Info("$oid_root".".4"."$unique_ +oid"))); $name = (Get_SNMP_Info("$oid_root".".6"."$unique_oid")); $port = (Get_SNMP_Info("$oid_root".".7"."$unique_oid")); $type = (Get_SNMP_Info("$oid_root".".8"."$unique_oid")); $if_oid = $unique_oid; $if_oid =~ s/\.\d+$//; my $ifdescr = Get_SNMP_Info($oifdesc.$if_oid); my $vlanallowed = Get_SNMP_Info($ovlanallowed.$if_oid); if ($vlanallowed){ $vlanallowed = Vlans_Allowed_Format($vlanallowed);} else{$vlanallowed = "Unavailable";} unless (($type=~/phone/i)||($type=~/server/i)||($type=~/at +a/i)){@todo=(@todo,$ip)}; #Ignore phones, servers, and ATA devices unless (($type=~/phone/i)||($type=~/server/i)||($type=~/at +a/i)){print ",,,$ifdescr,$ip,$name,$port,$type,\"$vlanallowed\"\n"}; get_oids($new_oid); } } } sub Convert_IP{ #This sub converts a hex IP to standard xxx.xxx.xxx.xx +x format my($ip , $result , $crap); my($hex1 , $hex2 , $hex3 , $hex4); my($oct1 , $oct2 , $oct3 , $oct4); my($hex_ip) = $_[0] ; if ((substr($hex_ip,0,1) eq "")|| ($hex_ip !~ /0x/)){ $ip = "0.0.0.0"; } else{ $hex_ip =~ s/0x//g; $hex1 = (substr $hex_ip,0,2); $hex2 = (substr $hex_ip,2,2); $hex3 = (substr $hex_ip,4,2); $hex4 = (substr $hex_ip,6,2); $oct1 = hex($hex1); $oct2 = hex($hex2); $oct3 = hex($hex3); $oct4 = hex($hex4); $ip = "$oct1\.$oct2\.$oct3\.$oct4"; } return $ip; } sub Get_SNMP_Info{ #This sub gets the value of an oid my($crap , $value , $result); my($oid) = $_[0]; $result = $session->get_request("$oid"); return unless (defined $result); ($crap , $value) = %$result; return $value; } sub Get_Config{ my ($hostip, $community, $tftp, $cfg_file, $copy); $hostip = shift; $community = shift; $tftp = shift; $cfg_file = shift; unless ($cfg_file) {$cfg_file = $hostip}; $copy = Cisco::CopyConfig->new(Host => $hostip, Comm => $community, Tmout => 15, Retry => 2); $copy->copy($tftp, $cfg_file); if ($copy->error){print "Couldn't get config for $hostip : ".$copy +->error."\n"}; } sub Convert_Octet_String{ my $octet_str = shift; my $basePort = 0; my ($index,$octet); my @octet; $octet_str = substr $octet_str, 2; while ($octet_str) { my $octet = hex substr $octet_str, 0, 2, ''; my $index = 0; while ($octet) { next unless $octet & 0x80; push @octet, $basePort + $index; } continue { ++$index; $octet = ($octet << 1) & 0xff; } $basePort += 8; } return @octet; } sub Vlans_Allowed_Format{ my ($vlans,$octets,$bits,$return); $vlans = shift; $octets = From_Hex($vlans); $bits = Reverse_Bit_Order($octets); $return = Range_Format($bits); if($return eq "1-1023"){ $return = "1-4094";#Assume the extended VLANs are allo +wed as well return $return; } return $return; } sub From_Hex { my ($hex) = @_ ; $hex =~ s/^0x//i ; return pack('H*', $hex) ; } sub Reverse_Bit_Order { my ($octets) = @_ ; return pack('B*', unpack('b*', $octets)) ; } sub Range_Format{ my $bits = shift; my $r = undef; my @s = (); for my $vn (1..length($bits) * 8) { if (vec($bits, $vn, 1)) { if (!defined($r)) { push @s, "$vn" ; $r = 0 ; +} else { $r = $vn ; } ; } else { if (defined($r)) { if ($r) { $s[-1] .= "-$r" ; } ; $r = undef ; } ; } } return join(', ',@s); }

Replies are listed 'Best First'.
Re: Allowed VLANs with SNMP
by almut (Canon) on Oct 23, 2008 at 18:30 UTC

    If I'm understanding you correctly (not entirely sure), you could use pack/unpack to convert your hex string "0xffff...fffe" into an ASCII "bit"-string, i.e. where each byte of the string is either the character "1" or "0". You would then simply use substr to test whether the char at a specific index is "1", which would mean the VLAN with that number/index is allowed.  Something like this (simplified):

    my $h = "ff00f0fe"; # just 32 bits here, but any other length possibl +e my $b = unpack("B*", pack("H*", $h)); print "$b\n"; # 11111111000000001111000011111110 my $idx = 5; my $allowed = substr($b, $idx-1, 1) eq "1";

    Note that you need to remove the leading "0x" from the string.

      Thanks, I think that's exactly what I need.

      my $h = "ff00f0fe"; # just 32 bits here, but any other length possibl +e $h =~ s/0x//g; my $b = unpack("B*", pack("H*", $h)); print "$b\n"; # 11111111000000001111000011111110 foreach $idx (1..1023){ my $allowed = substr($b, $idx-1, 1) eq "1"; if ($allowed){print "$idx is allowed\n";} else{print "$idx is not allowed\n";} }
Re: Allowed VLANs with SNMP
by gone2015 (Deacon) on Oct 23, 2008 at 19:09 UTC
    An indication of which Virtual LANs are allowed on this Inter-Switch Link. This is an octet string value with bits set to indicate allowed VLANs. It can be interpreted as a sum of f(x) as x goes from 0 to 1023, where f(x) = 0 for VLAN x not allowed and f(x) = exp(2, x) for VLAN x allowed.

    Some genius wrote that documentation, presumably thinking it was wonderfully precise, without specifying the order of bits in the 'octet string'

    If we make the flying assumption that the '0xfff...fe' is a 1024 bit number, and not an 'octet string' at all, then we can go to and from a Perl bit-vector string, and use vec to access it, as in the code below.

    The result is:

      Ranges: 1..1023
    
    which looks plausible. But a value with a few more '0' bits in it would be a stronger test.

    use strict ; use warnings ; #1234567890123456789012345678901234567890123456789012345 +678901234 my $vlans = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffff' .'fffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffff' .'fffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffff' .'fffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffe' ; my $vlan_vec = to_vec($vlans) ; # Convert long hex integer to bit-vect +or # VLAN numbers are mapped to/from $vlan_vec by vec($vlan_vec, 1, $vn) # This scans the bit-vector looking for ranges of VLAN numbers. # # Note that we can read one bit beyond the given range, and get 0 my $r = undef ; my @s = () ; for my $vn (0..length($vlan_vec) * 8) { if (vec($vlan_vec, $vn, 1)) { if (!defined($r)) { push @s, "$vn.." ; } ; $r = $vn ; } else { if (defined($r)) { $s[-1] .= "$r" ; $r = undef ; } ; } ; } ; print "Ranges: ", join(', ', @s), "\n" ; # Convert long hex integer to bit-vector. # # Combination of 'h*' and reverse gives bits in right order in the bit +-vector. sub to_vec { my ($hex) = @_ ; $hex =~ s/^0x// ; return pack('h*', scalar reverse $hex) ; } ; # Convert bit-vector to long integer. Again 'h*' and reverse sorts ou +t bit order. sub from_vec { my ($vec) = @_ ; return '0x'. scalar reverse(unpack('h*', $vec)) ; } ;

      What would be the approach if the string were an octet string? I came across more genius documentation for a different OID:

      A string of octets containing one bit per VLAN in the management domain on this trunk port. The first octet corresponds to VLANs with VlanIndex values of 0 through 7; the second octet to VLANs 8 through 15; etc. The most significant bit of each octet corresponds to the lowest value VlanIndex in that octet. If the bit corresponding to a VLAN is set to '1', then the local system is enabled for sending and receiving frames on that VLAN; if the bit is set to '0', then the system is disabled from sending and receiving frames on that VLAN.

      GrandFather has a great example in a post, Re: Convert SNMP Octet String to Array, to convert the octet string, but I don't think I understand how the ranges were created from the bit number. Using GrandFather's example I can convert the string properly, but I can't figure out how to show the VLANs in the range format (1-10,15,20-30, etc).

        A string of octets containing one bit per VLAN in the management domain on this trunk port. The first octet corresponds to VLANs with VlanIndex values of 0 through 7; the second octet to VLANs 8 through 15; etc. The most significant bit of each octet corresponds to the lowest value VlanIndex in that octet.

        Well that's pretty clear. It's also the exact reverse of the previous form.

        Perl's bit-vector is similar, except that the bits are in the opposite order in each byte. So you can translate between this format and a Perl bit-vector so:

        sub xlat { my ($octets) = @_ ; return pack('B*', unpack('b*', $octets)) ; } ;
        The translation reverses itself, and as you can see unpacks the bytes in one bit order and promptly repacks in the other. This shows the effect:
        my $test = "\xC5\x11\x01\x80\x5A" ; print showbits($test), "\n" ; print showbits(xlat($test)), "\n" ; print showbits(xlat(xlat($test))), "\n" ; sub showbits { my ($octets) = @_ ; my $s = unpack('B*', $octets) ; $s =~ s/([01]{8})(?=[01])/$1:/g ; return $s ; } ;
        giving:
          11000101:00010001:00000001:10000000:01011010
          10100011:10001000:10000000:00000001:01011010
          11000101:00010001:00000001:10000000:01011010
        
        which appears to be what's required.

      Very nice! That takes care of the formating issue I was just contemplating as well, how to show the ranges rather than the individual vlans allowed. Works with 0 values as well. Comparing the script results to switch configurations seems to prove the number is a 1024 bit number too.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://719096]
Approved by mr_mischief
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2019-12-15 13:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?