in reply to Allowed VLANs with SNMP

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)) ; } ;

Replies are listed 'Best First'.
Re^2: Allowed VLANs with SNMP
by spivey49 (Monk) on Oct 23, 2008 at 19:34 UTC
    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.
Re^2: Allowed VLANs with SNMP
by spivey49 (Monk) on Oct 23, 2008 at 20:31 UTC

    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.

        Thanks again. I don't think I'm fully grasping how vec works, even after reading the doc, or the routine using vec to map the bits to the vlans and creating the ranges.

        The routine formating numbers like 1,2,3 as 1-3 is great, but doesn't seem to work properly with the octet string example. Of course it won't work with Grandfather's example from what I can see since @ports just contains the VLANs allowed

        Trying to shoehorn the previous example into this one:

        sub xlat { my ($octets) = @_ ; return pack('B*', unpack('b*', $octets)) ; } ; my $test = '0x4000040000000200800000000000000000000002' .'0000000000c004000108008000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000'; my $bits; $bits = showbits($test); my @s = ranges($bits); print "Ranges: ", join(', ', @s); $bits = showbits(xlat($test)); @s = ranges($bits); print join(',',@s); $bits = showbits(xlat(xlat($test))); @s = ranges($bits); print join(',',@s); sub ranges{ my $test = shift; my $r = undef ; my @s = () ; for my $vn (1..length($test) * 8) { if (vec($test, $vn, 1)) { if (!defined($r)) { push @s, "$vn-" ; } ; $r = $vn ; } else { if (defined($r)) { $s[-1] .= "$r" ; $r = undef ; } ; } ; } ; return @s; } sub showbits { my ($octets) = @_ ; my $s = unpack('B*', $octets) ; $s =~ s/([01]{8})(?=[01])/$1/g ; ranges($s) ; } ;

        Should, at least in my misunderstanding, produce Ranges: 1,21,54,64,158,200-201,213,231,236,248.

        Instead it produces:

        Ranges: 1-1, 4-5, 8-8, 10-10, 12-13, 18-18, 20-21, 24-24, 28-291-1,4-5,8-8,10-10,12-13,18-18,20-21,28-291-1,4-5,8-8,10-10,12-13,18-18,20-21,24-24,28-29

        Using GrandFather's example

        my $ports = '0x4000040000000200800000000000000000000002' .'0000000000c004000108008000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000'; my $basePort = 0; my @ports; $ports = substr $ports, 2; while ($ports) { my $octet = hex substr $ports, 0, 2, ''; my $index = 0; while ($octet) { next unless $octet & 0x80; push @ports, $basePort + $index; } continue { ++$index; $octet = ($octet << 1) & 0xff; } $basePort += 8; } print join ',', @ports;

        produces: 1,21,54,64,158,200,201,213,231,236,248

        After hours of playing with both these examples I managed to confuse myself even more. Can you point out what I'm missing?

        Update:After some more tinkering I came up with the following kludge to format Grandfather's example, but something tells me there's a better way to do this. Still haven't figured out how to convert the string properly with vec.

        my $ports = '0x4000040000000200800000000000000000000002' .'0000000000c004000108008000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000000000000000000000000000000000' .'00000000000'; my $basePort = 0; my @ports; $ports = substr $ports, 2; while ($ports) { my $octet = hex substr $ports, 0, 2, ''; my $index = 0; while ($octet) { next unless $octet & 0x80; push @ports, $basePort + $index; } continue { ++$index; $octet = ($octet << 1) & 0xff; } $basePort += 8; } print join ',', @ports,"\n"; range_format(@ports); sub range_format{ my (@numbers,$i,$start,$end); @numbers = @_; for ($i = 0; $i < @numbers; ++$i){ my $prev = $numbers[$i-1]; my $next = $numbers[$i+1]; my $cur = $numbers[$i]; if (($cur+1 == $next) and ($cur-1!= $prev)){ $start = $cur; } if (($cur-1 == $prev) and ($cur+1 != $next)){ $end = $cur; } if ((defined $start) and (defined $end)){ print "$start-$end,"; $start = undef; $end = undef; }else{ unless((defined $start)||(defined $end)){print "$cur,";}} } }