Monday, September 12, 2011

AIX / VIOS CDP - Cisco Discovery Protocol to gather information about port on CISCO switch

The Cisco Discovery Protocol (CDP) is a proprietary Data Link Layer network protocol developed by Cisco Systems. It is used to share information about other directly connected Cisco equipment, such as the operating system version and IP address.

Device connected to CISCO CDP enabled switch can sniff CDP packets and gather inflormation about port like switch name, trunk and VLAN IDs, port number like Gi1/5.

VMware support it by default and it can be seen in GUI in networking tab.

AIX / VIOS doesn't support that by default but it can be gathered by script using tcpdump.

Script for CDP on AIX can be downloaded here:
http://the-welters.com/professional/scripts/


Here is the script:

#!/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";

2 comments:

  1. Here also nice tool for CDP
    http://www.perzl.org/aix/index.php?n=Main.Cdpr

    ReplyDelete
  2. Thanks, seems to be useful as well :)

    ReplyDelete