Section 11.10. Switch Port Control


11.10. Switch Port Control

Sometimes it can be handy to turn up or down a switch port. For example, you may want to play a prank on an annoying co-worker. Other times, it may be a case of disabling a host that is infected with a virus and spewing packets all over the network. Whatever the case may be, it can be helpful to have something like this in your toolbox.

Most people attach a serial cable from a laptop to the management port on the switch to configure it or manage it. To manage a switch via SNMP, you generally have to create a VLAN (which may encompass all the ports on the switch). This VLAN is configured with an IP address, which allows for SNMP access and control. The actual ability to manage a port comes via the Bridge MIB (RFC 1493). Most if not all switch vendors implement this MIB. Many vendors also have their own MIB that may enhance or extend the Bridge MIB, but we will focus on the RFC version to keep things generic.

To successfully manage a switch port, you have to know the following bits of information:

  • IP address of host on port

  • MAC address of host on port

  • Switch port number

The key to managing your switch ports is keeping track of which hosts are on which switch ports. Tobias Oetiker, creator of MRTG, created a Perl script called Cammer (http://people.ee.ethz.ch/~oetiker/webtools/mrtg/pub/contrib/cammer). Cammer displays which MAC addresses are on a switch, along with IP address information. It does this by querying the Address Resolution Protocol (ARP) table on a router and then matching this up with what's on your switch. Here's a sample run:

     $ ./cammer.pl public@switch public@router     Fa0/9         1  00:11:43:17:06:8d  192.168.0.48     dhcp48.domain     Fa0/3         1  00:60:f5:08:4e:3c  192.168.0.1      router.domain     Fa0/8         1  00:60:47:40:fd:14  192.168.0.130    dhcp130.domain 

When running Cammer, you must pass it the community string for your switch and router. The first column is the interface name from the switch. The second column is the VLAN number, followed by the MAC address, IP address, and DNS name of the IP address (if available). Unfortunately, this script is Cisco-specific, but modifying it to operate with other vendors is doable.

For our purposes, we modified the Cammer source a little bit. The output now looks like this:

     $ ./cammer2.pl public@switch public@router     192.168.0.148              1 (ifIndex = 10, ifName = Fa0/9)  00:11:43:17:06:8d 192.168.0.48              dhcp48.domain              1 (ifIndex = 4, ifName = Fa0/3)  00:60:f5:08:4e:3c 192.168.0.1              router.domain              1 (ifIndex = 9, ifName = Fa0/8)  00:60:47:40:fd:14 192.168.0.130              dhcp130.domain 

The display now shows the ifIndex in addition to the ifName. The ifIndex is used later in the script that does the actual port control. The output from running diff on the original Cammer version and the one we modified follows:

     51d50     <     62a62,65     > my %OID_TO_NONARRAY = ( 'dot1dBasePortIfIndex' =>  "1.3.6.1.2.1.17.1.4.1.2",);     >     >     >     129a133,134     >     my %macsToIfIndex;     >     my %macsToIfName;     132a138     >         #my $sws = SNMPv2c_Session->open ($opt{sw},$opt{swco}.'@'.$vlan,161)     141c147,152     <          $port{$vlan}{$mac}=$port;     ---     >                my $iid = snmpget($opt{swco}.'@'.$opt{sw},"$OID_TO_     NONARRAY{'dot1dBasePortIfIndex'}.$port");     >                my $ifname = snmpget($opt{swco}.'@'.$opt{sw},"ifName.$iid");     >                #my $ifname = snmpget($opt{swco}.'@'.$opt{sw},"$OID_TO_     NONARRAY{'ifName'}.$iid");     >                $macsToIfIndex{$mac} = $iid;     >                $macsToIfName{$mac} = $ifname;     >                 $port{$vlan}{$mac}=$port;     145c156,158     <           sub {  my($port,$if) = pretty(@_);     ---     >          sub {     >          my($port,$if) = pretty(@_);     >          print "$port,$if\n";     166c179     <        push @{$output{$name}}, sprintf "%4s  %-17s  %-15s  %s     %s",$truevlan,$mac,$ip[0],$host[0],$quest;     ---     >        push @{$output{$name}},     sprintf "%4s (ifIndex = %s, ifName = %s)  %-17s %-15s  %s     %s",$truevlan,$macsToIfIndex{$mac},$macsToIfName{$mac},$mac,$ip[0],$host[0],     $quest if($ip[0] ne "");     168a182     >     print "\n$opt{sw}\n";     249d262     < 

The switch control Perl script that follows uses a few MIB objects from the Bridge MIB:

     #!/usr/bin/perl     use SNMP;     use Getopt::Long;     GetOptions("mac=s"  => \$gMac,                "index=s" => \$gIndex,                "action=s" => \$gAction,                );     ($gMac,$gAction,$gIndex) = verifyInput($gMac,$gAction,$gIndex);     &SNMP::initMib( );     &SNMP::loadModules(qw/BRIDGE-MIB/);     my $host = "192.168.0.148";     my $roComm = "public";     my $rwComm = "private";     $roSession = new SNMP::Session(DestHost => $host, Community => $roComm,                                     UseSprintValue => 1, Version=>2);     die "session creation error: $SNMP::Session::ErrorStr" unless        (defined $roSession);     $rwSession = new SNMP::Session(DestHost => $host, Community => $rwComm,                                     UseSprintValue => 1, Version=>2);     die "session creation error: $SNMP::Session::ErrorStr" unless        (defined $rwSession);     findMac( );     sub findMac {        my($discover) = @_;        $vars = new SNMP::VarList(['dot1dTpFdbAddress'], ['dot1dTpFdbPort']);        # get first row        my ($mac, $port) = $roSession->getnext($vars);        die $roSession->{ErrorStr} if ($roSession->{ErrorStr});        while (!$roSession->{ErrorStr} and $$vars[0]->tag eq "dot1dTpFdbAddress"              || $$vars[0]->tag eq "dot1dBasePortIfIndex"){           my @tmac = $mac =~ m/(\w{1,2}) (\w{1,2}) (\w{1,2}) (\w{1,2}) (\w{1,2}) (\     w{1,2})/g;           $mac = sanitizeMac(sprintf("%s:%s:%s:%s:%s:%s",@tmac));           if($gMac eq $mac){              # We found it              my $ifnum = $roSession->get("dot1dBasePortIfIndex\.$port");              if($ifnum eq $gIndex){                 doAction($gAction,$ifnum);              }else{                 print "$mac has moved to ifIndex $ifnum\n";              }              last;           }           # keep going           ($mac, $port) = $roSession->getnext($vars);        }     }     sub doAction{        my ($action,$ifnum) = @_;        my $ifname = $roSession->get("ifDescr\.$ifnum");        if($action eq "up"){           print "Turning $ifname $action (ifNum is $ifnum)..\n";           $rwSession->set([["ifAdminStatus", $ifnum, 1, "INTEGER"]]);        }elsif($action eq "down"){           print "Turning $ifname $action (ifNum is $ifnum)...\n";           $rwSession->set([["ifAdminStatus", $ifnum, 2, "INTEGER"]]);        }        if($rwSession->{ErrorStr}){           print "An error occurred during processing: $rwSession->{ErrorStr}\n";        }     }     sub sanitizeMac{        my($mac) = @_;        my @tmac = split(/:/,$mac);        foreach my $byte (0..$#tmac){           $tmac[$byte] =~ s/^0//g;           $tmac[$byte] = lc($tmac[$byte]);        }        $mac = sprintf("%s:%s:%s:%s:%s:%s",@tmac);        return $mac;     }     sub verifyInput{        my($mac,$action,$index) = @_;        if(($mac eq "" && $action eq "" && $index eq "")) {           usage( );           exit;        }        if($action eq ""){           usage( );           exit;        }        $mac = sanitizeMac($mac);        $action = lc($action);        if($action ne "up" && $action ne "down"){           usage( );           exit;        }        return ($mac,$action,$index);     }     sub usage{        print "Usage:\t$0 --mac=0:f:0:d:55:a --index=10 --action=up\n";        print "\tSpecify a MAC adddress and the index in the interfaces MIB tree     where this port lives on the switch. Action can be EITHER \"up\" OR \"down\"\n";     } 

Here are some key points about this script:

  • The MAC address that is passed to the script is normalized to a common format.

  • The findMac( ) routine makes use of the dot1dTpFdbAddress, dot1dTpFdbPort, and dot1dBasePortIfIndex OIDs. Here are their MIB definitions:

         dot1dTpFdbAddress OBJECT-TYPE                   SYNTAX  MacAddress                   ACCESS  read-only                   STATUS  mandatory                   DESCRIPTION                           "A unicast MAC address for which the bridge has                           forwarding and/or filtering information."                   REFERENCE                           "IEEE 802.1D-1990: Section 3.9.1, 3.9.2"                   ::= { dot1dTpFdbEntry 1 }     dot1dTpFdbPort OBJECT-TYPE                   SYNTAX  INTEGER                   ACCESS  read-only                   STATUS  mandatory                   DESCRIPTION                           "Either the value '0', or the port number of the                           port on which a frame having a source address                           equal to the value of the corresponding instance                           of dot1dTpFdbAddress has been seen.  A value of                           '0' indicates that the port number has not been                           learned but that the bridge does have some                           forwarding/filtering information about this                           address (e.g., in the dot1dStaticTable).                           Implementors are encouraged to assign the port                           value to this object whenever it is learned even                           for addresses for which the corresponding value of                           dot1dTpFdbStatus is not learned(3)."                   ::= { dot1dTpFdbEntry 2 }     dot1dBasePortIfIndex OBJECT-TYPE                   SYNTAX  INTEGER                   ACCESS  read-only                   STATUS  mandatory                   DESCRIPTION                           "The value of the instance of the ifIndex object,                           defined in MIB-II, for the interface corresponding                           to this port."                   ::= { dot1dBasePortEntry 2 } 

    dot1dTpFdbAddress gathers all the MAC addresses on the switch for which it has forwarding information. dot1dTpFdbPort gets the corresponding port number and dot1dBasePortIfIndex is the port to ifIndex mapping. findMac( ) keeps going until it finds a MAC address that matches the one specified on the command line. Once found, it looks to see if the dot1dBasePortIfIndex value matches the one specified by the user. If it does, it performs the action. If it doesn't, it displays a message and exits.

  • The doAction( ) method performs the actual port control operation. It uses the ifAdminStatus OID to set the port to up or down.

Here are some sample runs of the script:

     $ ./swcontrol.pl --mac=00:11:43:17:06:8d --index=10 --action=up     Turning FastEthernet0/9 up (ifNum is 10)..     $ ./swcontrol.pl --mac=00:11:43:17:06:8d --index=10 --action=down     Turning FastEthernet0/9 down (ifNum is 10)...     $ ./swcontrol.pl --mac=00:11:43:17:06:8d --index=11 --action=up     0:11:43:17:6:8d has moved to ifIndex 10     $ 

The last run shows output when the wrong index is used on the command line. This index check is used as a safety since ifIndex values can shift or move at any time. When this happens, you may need to rerun Cammer to update your mappings.

Finally, here are some ways to enhance this script:

  • Cammer needs to be run on a somewhat regular basis, since hosts can move (especially laptops).

  • Cammer could be extended to store its results in a database. The switch control script could then be made to read from the database to get the proper index information.




Essential SNMP
Essential SNMP, Second Edition
ISBN: 0596008406
EAN: 2147483647
Year: 2003
Pages: 165

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net