#!/usr/bin/perl # # Listen for Cisco Discovery Protocol (CDP) packets # and print out key values such as switch, port, vlan, and duplex. # # This script depends on either "snoop" (Solaris) or # "tcpdump" (AIX, and others). Both of those programs generally # must be run as root. # # It has been tested on Solaris 10 and AIX 5.3. # # Andy Welter # Version 1.1 # July 2007 # Support timeout values while waiting on the cdp packet. # Version 1.0 # December 2006 # Initial Version. # # $usage="cdpinfo -i [-t timeoutvalue] [-v]\n-i use the enX device name for the interface to watch\n-t timeout value in seconds. Don't wait for a cdp packet longer than this. default is 60 seconds. zero means no limit.\n-v verbose output\n"; use Getopt::Std; if ( getopts ('i:t:v') == 0) { print "$usage"; exit 1; }; $idev=$opt_i; if ($opt_i) { $iface="-i $opt_i"; }; $verbose=$opt_v; $timeout=$opt_t; # # convert string data to hex characters. # sub hexprint { my ($string)=@_; my $hex=""; my $ii,$len; $len=length ($string); $ii=0; @bytes=unpack "C*",$string; foreach $byte (@bytes) { $hex=$hex . sprintf "%02x ",$byte; $ii++; }; return $hex; }; # # Parse TCP dump output to acquire a CDP packet sub tcpdump { my ($cmd)=@_; # tcpdump omits the first 14 bytes of a packet in the hex dump # so put some filler in the packet string my $packet="01234567890123"; open (GETPACKET, "$cmd") || die "cannot open $cmd\n"; while ( $_ = ) { chomp; # # look a line that starts with white space, followed by at least # 2 hex characters if (m/^\s+([\da-fA-F]+ )/) { s/^\s+//; @data=split /\s+/,$_,8; foreach $bytes (@data) { $verbose && print "$bytes "; $packet=$packet . pack "H4", $bytes; }; $verbose && print "\n"; } ; }; close GETPACKET; return $packet; }; # # Parse "snoop" output for the packet sub snoop { my ($cmd)=@_; my $packet=""; open (GETPACKET, "$cmd") || die "cannot open $cmd\n"; while ( $_ = ) { chomp; print "-- $_\n"; if (/^\s+\d+:/) { s/^\s+//; @data=split /\s+/,$_,10; shift @data; pop @data; foreach $bytes (@data) { $packet=$packet . pack "H4", $bytes; }; }; }; close GETPACKET; return $packet; }; # # Parse the acquired CDP packet for key values. # sub decodePacket { my ($packet)=@_; my $plen,$string,$ii,$flength,$switchName,$switchPort,$ftype,$vlan,$duplex; # decode the packet # ethernet layout: # 0-7 8 byte preamble # 8-13 6 byte dest mac addr # 14-19 6 byte source mac addr # 20-21 2 byte type field # 22-23 2 byte check sum # 24-25 2 byte ??? # 26-27 2 byte first CDP data field # 28-29 2 byte field length (including field type and length) # 30-- Variable data. # 4 byte CRC field. # # Field type indicators # Device-ID => 0x01 # Version-String => 0x05 # Platform => 0x06 # Address => 0x02 # Port-ID => 0x03 # Capability => 0x04 # VTP-Domain => 0x09 # VLAN-ID => 0x0a # Duplex => 0x0b # AVVID-Trust => 0x12 # AVVID-CoS => 0x13); $verbose && printf "packet len=%d\n",length($packet); # # The CDP packet data starts at offset 26 $ii=26; $plen=length ($packet); while ( $ii < $plen-4) { $ftype=unpack "S", substr ($packet, $ii, 2); $flength=unpack "S", substr ($packet, $ii+2, 2); if ( $ftype == 1 ) { $switchName=substr ($packet,$ii+4,$flength-4); } elsif ( $ftype == 3 ) { $switchPort=substr ($packet,$ii+4,$flength-4); } elsif ( $ftype == 10 ) { $vlan=unpack "s",substr ($packet,$ii+4,$flength-4); } elsif ( $ftype == 11 ) { $duplex=unpack "c",substr ($packet,$ii+4,$flength-4); }; $string=substr ($packet,$ii+4,$flength-4); $fvalue=hexprint ($string); $string=~s/\W/./g; $verbose && printf "\noffset=%d, type 0x%04x, length 0x%04x\nHex Value:\n%s\nASCII value:\n%s\n\n", $ii,$ftype, $flength-4,$fvalue,$string; if ($flength == 0 ) { $ii=$plen; }; $ii=$ii+$flength; }; return sprintf "\"%s\",\"%s\",\"%d\",\"0x%02x\"", $switchName,$switchPort,$vlan,$duplex; }; # # MAIN ROUTINE # # determine whether we are a snoop or tcpdump kinda system $cmd=`which tcpdump`; chomp $cmd; if ( $cmd ne "" ) { $cmd= "$cmd $iface -s 1500 -x -c 1 'ether [20:2] = 0x2000' 2>/dev/null |"; } else { $cmd=`which snoop`; chomp $cmd; if ( $cmd ne "" ) { $cmd="$cmd $iface -s 1500 -x0 -c 1 'ether[20:2] = 0x2000' 2>/dev/null |"; } else { print "ERROR: neither snoop nor tcpdump in my path\n"; exit 1; }; }; sub timeout { die "TIMEOUT"; }; $SIG{ALRM}=\&timeout; eval { alarm ($timeout); # # use tcpdump or snoop to get a CDP packet if ( $cmd=~m/snoop/ ) { $packet=snoop ($cmd); } elsif ( $cmd=~m/tcpdump/ ) { $packet=tcpdump($cmd); } else { print "ERROR: snoop or tcpdump not found\n"; exit 1; }; alarm(0); }; if ($@ =~ "TIMEOUT") { $packet=""; }; # # Decode the acquired packet and print the results. print '"' . $idev . '",' . decodePacket ($packet) . "\n";