Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Cisco SNMP CDP Poll

by fingers (Acolyte)
on May 16, 2001 at 21:45 UTC ( [id://80979]=sourcecode: print w/replies, xml ) Need Help??
Category: Networking Code
Author/Contact Info fingers
Description: This started out as a script that gathered CDP info by telnet until I realized I could much less intrusively get the same info from SNMP. Give the script an IP for an argument and it will retrieve some CDP neighbor info from the target and then use that info to acquire the IPs of new targets effectively allowing you to map out all of your cisco gear and how they are connected.


Currently Working On
1. making subs more blackbox-ish
2. more error checking
3. get info such as serial number,
number of ports on device,etc.

EDITED May 16th
Corrected a problem with how the script reacted if it has a neighbor with no ip address. It will now display 0.0.0.0 when it sees a null value for IP.
EDITED May 16th
Cleaned up the code a little bit. Made the get_ip get_name get_port and get_type subs a single sub Get_SNMP_Info
Moved all of the IP conversion code into its own sub Convert_IP
The code should be several steps closer to being strict compliant.
EDITED May 17th
Cleaned things up a lot more. Started using pod, and modified the inline comments to improve readability.
Got rid of get_target sub (it really shouldn't have been a sub at all)
Code now works with strict
#!/usr/bin/perl -w
# cdppoll.pl
use strict;
use Net::SNMP;
my($error,$session,$seed_oid,$oid_root,$community,$hostname,$seed_ip);
my(%done);
my(@todo);
$done{"0.0.0.0"}=1; 


#We need a startin 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+});


#Prompt for SNMP community string
print "community: ";
chomp($community = <STDIN>);

@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");

while(@todo){ #Grab a target and go to work
    
    $hostname= shift(@todo);
    unless(exists $done{$hostname}){  #Make sure we haven't done this 
+one yet

        print "\n\nCDP Neighbor Details for $hostname\n";
        print "-------------------------------------------------------
+-------------------------------------\n";
        print "Neighbor IP                 Name                   Inte
+rface                   Type        |\n";
        print "-------------------------------------------------------
+-------------------------------------\n";


        $done{$hostname}=1; #Remember that we checked this IP 

        #Open SNMP session
        ($session,$error) = Net::SNMP->session(Hostname => $hostname, 
+Community => $community);
        return unless($session);
    
        get_oids($seed_oid); #Get the SNMP info for this target

        $session->close;

    }
}

    #----------------------------------------------------------
    #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);
        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"));
            @todo=(@todo,$ip);
            write;
            get_oids($new_oid);
            
            }
        }
#Format the report
format STDOUT =
@<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<
+<<<<<< @<<<<<<<<<<<<<<<<<<<<<
$ip,$name,$port,$type 
.
    }

        sub Convert_IP{ #This sub converts a hex IP to standard xxx.xx
+x.xxx.xxx 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 ""){ 
                $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;

        }



=head1 Name
    cdppoll.pl

=head1 Summary
 This script takes one IP as an argument, uses SNMP to find
 that IPs CDP Neighbors, and then uses the IPs it gathers
 to find more CDP neighbors.
 
 Your network infrastructure should get mapped pretty
 quickly assuming that all of your devices are:
     1. Cisco routers or switches
     2. Using the same Read-Only Community string
     3. Permitting SNMP traffic to the box you are 
        running the script from.
       
 The network that this was tested on is composed of:
     2600 and 3600 series routers
     2900, 3500, 6500 series Catalyst switches
     2500 series terminal access device

=head1 Updated

 5-17-2001
  Cleaned things up a lot more. Started using POD, and modified the in
+line comments
  to improve readability.
  Got rid of get_target sub (it really shouldn't have been a sub at al
+l)
  Code now works with strict

 5-16-2001 
  Cleaned up the code a little bit. Made the get_ip get_name get_port 
+and get_type 
  subs a single sub Get_SNMP_Info 
  Moved all of the IP conversion code into its own sub Convert_IP
  The code should be several steps closer to being strict compliant.
 

 5-16-2001 
  Corrected a problem with how the script reacted if it has a neighbor
+ with no IPaddress. 
  It will now display 0.0.0.0 when it sees a null value for IP.

 5-16-2001 
  Initial working code posted
 
  
=head1 TODO  
  1. Add in better error handling and error messages
  2. Find a better way to check for IPs, currently
     999.9999.9999 would be seen as a valid target 
  3. Work on making the subroutines more blackbox-ish
  
=head1 Thanks
 Hopefully this will be usefull to someone other than myself
 Thanks to all the monks that answered my basic questions
 instead of telling me to RTFM
=cut
Replies are listed 'Best First'.
Re: Cisco SNMP CDP Poll (humble suggestions)
by ybiC (Prior) on May 17, 2001 at 02:14 UTC
        Hi fingers,
    Since you asked, here's my thoughts on Cisco SNMP CDP Poll.   Keep in mind that IANAJAPH, so Wiser Monks Than ITM may tell you I'm fullabeans {grin}

    First off - I like it, and will tweak it for use instead of some of my own Net::Telnet::Cisco-based scripts for query-only operations.   Net::Snmp++

    use strict; is your friend, despite sounding rather stern 8^)

    pod is also your friend. To my eye, that many embedded comments reduce readability

    Use subroutines only for modular functionality (cdp-neighbor, port count, model, etc.)   A wise monk recently advised me to think of subroutines as mini-programs instead of chapters in a book.   Reduces unecessary global variables, and offers potential for improved readability.

    Assign variable names to $_[0] and $_[1], etc early in subroutine.   Improved readability??   references?

    Combine into chomp($community = <STDIN>);

    die "usage: $0 seedip" unless ( $seedhost =~ m{\d+\.\d+\.\d+\.\d+} )   Improved readability??

    die "usage: $0 seedip" unless ( @ARGV == 1 )   Improved readability??

    Use a CPAN module to parse IP address for legality.   m/\d{1,3}.\d{1,3}.\d{1,3},\d{1,3}/ would be small improvement, checking for 1-to3-digit numbers.

    Mixed-case subroutine names for clarity.   There are a few links to relevant posts on my homenode, near the middle in the "educate" section.

    ++fingers for verbish subroutine names.

        cheers,
        Don
        striving toward Perl Adept
        (it's pronounced "why-bick")

    Update 2001-05-17   19:45

    The following adds a configurable delay:
    my $delay = '2'; near head of script along with other variable declarations, and
    sleep($delay); in get_oids() just after write;

    Save results to an output file:
    my $outfile = 'cdppoll.out'; near head of script along with other variable declarations
    open (OUTFILE, ">$outfile") or die"Error opening $outfile WO:$!"; between "chomp" and "@todo"
    format OUTFILE = (same as format STDOUT) right after "format STDOUT"
    close OUTFILE or die"Error closing $outfile:$!"; right after above "format OUTFILE"

    Use x operator to simplify code line for visual output divider, and reduce code width.
    print "\n", '=' x 94, "\n";

    I don't have these coded yet, but could be good to:

    • csv outfile for easy manipulation and importing
    • generate summarized outfile listing only unique devices
    • check hostname (in addition to IP address) for 'already done'.   Avoid discovering same router twice if redundant paths.
    • use Term::ReadKey for no-echo entry of SNMP community string.
    • use hash(es) to reduce number of global variables:   my %oid; $oid{root}, $oid{starting}, $oid{new} instead of $oid_root, $oid_starting, $oid_new

    Update 2001-05-17   20:45
    "multiple devices w/same hostname..."   Ah, I hadn't thought of that. How about an SNMP query for *all* interface addresses then add device to list only if no match?   I've not given any thought to the logic, just searching for an easy identifier of "unique".

      check hostname (in addition to IP address) for 'already done'.Avoid discovering same router twice if redundant paths

      I had initally given some thought to the fact that many routers may get discovered multiple times, because they by their nature have multiple IPs.

      The problem I have with checking by hostname is that it is entirely possible to have multiple devices with the same hostname, (ok - I guess using IPs reserved for private networks it would be possible to have the same IP on multiple devices, but the box you're running the script off of wouldn't be able to have a route to more than one of them at a time).

      I am considering using serial numbers as my second key to verify uniqueness, however the OID for serial numbers appears to vary from device to device.

      Checking the serial number would still entail opening an SNMP session, but at least your output would be a little less redundant.

      Well off I go to find more usefull OIDs ...
Re: Cisco SNMP CDP Poll
by arktkbear (Initiate) on Jul 13, 2001 at 18:07 UTC
    here's one quickie on your todo list (still new at perl, 3 months exp.), but just tryin to help, let me know if it's too ugly :)

    stick this after your intial quad-dotted number check (second die statement)

    my(@ip_pieces) = split /\./, $seed_ip; my($piece); foreach $piece (@ip_pieces) { die("ip out of valid range!") if(($piece < 0) || ($piece > 255)); }
Re: Cisco SNMP CDP Poll
by kderkinderen (Initiate) on Aug 19, 2001 at 06:03 UTC
    I seem to have hit a snag. During a poll I get the following results. Always on the same set of routers.
    Illegal hexadecimal digit ' ' ignored at ./cdppoll line 99, <STDIN> line 1. Illegal hexadecimal digit 'X' ignored at ./cdppoll line 100, <STDIN> l +ine 1. Use of uninitialized value in hex at ./cdppoll line 102, <STDIN> line +1. 0.0.0.0 vadgw001.winstar.com Serial2/2 + cisco 7206
    Trying to trace it down I added some statements to the Get_SNMP_Info sub Get_SNMP_Info{ #This sub gets the value of an oid
    my($crap , $value , $result); my($oid) = $_[0]; $result = $session->get_request("$oid"); $result = $session->get_request("$oid"); #return unless (defined $result); ($crap , $value) = %$result; print "DEBUG: $oid = $value\n"; print "DEBUG: $oid = $crap\n"; return $value; }
    and come up with this:
    DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.4.7.429 = X> DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.4.7.429 = 1.3.6.1.4.1.9.9.23.1.2.1.1 +.4.7.429 substr outside of string at ./cdppoll line 97, <STDIN> line 1. Illegal hexadecimal digit ' ' ignored at ./cdppoll line 99, <STDIN> line 1. Illegal hexadecimal digit 'X' ignored at ./cdppoll line 100, <STDIN> l +ine 1. Use of uninitialized value in hex at ./cdppoll line 102, <STDIN> line +1. DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.6.7.429 = vadgw001.winstar.com DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.6.7.429 = 1.3.6.1.4.1.9.9.23.1.2.1.1 +.6.7.429 DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.7.7.429 = Serial2/2 DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.7.7.429 = 1.3.6.1.4.1.9.9.23.1.2.1.1 +.7.7.429 DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.8.7.429 = cisco 7206 DEBUG: 1.3.6.1.4.1.9.9.23.1.2.1.1.8.7.429 = 1.3.6.1.4.1.9.9.23.1.2.1.1 +.8.7.429 0.0.0.0 vadgw001.winstar.com Serial2/2 + cisco 7206
    I can't figure out where the X> is coming from. I do an snmpget on that same oid and get good results.
    snmpget -v2c vaugw001 something .1.3.6.1.4.1.9.9.23.1.2.1.1.4.7.429 enterprises.9.9.23.1.2.1.1.4.7.429 = Hex: 0A 00 58 3E
    Has anyone reported this yet? It happens every time on exactly the same entries. Kevin
      some devices do not insert a 0x infront of hex ip The result is that the ip could not be translated. Maybe this could solve the problem?!
      ################################################## sub Convert_IP { my($ip , $result , $crap); my($hex1 , $hex2 , $hex3 , $hex4); my($oct1 , $oct2 , $oct3 , $oct4); my($hex_ip) = $_[0] ; #========================================================= +================== # Manche Geräte schicken die hexadezimale IP-Adresse # ohne führendes "0X" welches eine Hexadezimale Zahl # kennzeichnet # Für den Fall das keine "0x" mitgeschickt wird unter # Umständen die $hex_ip als ASCII Zeichen intepretiert. # Diese Schleife wandelt das ASCII Zeichen in eine # Hexadezimale Zahl um # und setzt ein "0x" vor den String. Die so neu generierte # Hexadezimale Zahl # kann dann wiederum weiterverarbeitet werden #===================================================================== +====== if (length($hex_ip) ne '10') { my @hexadezimale_IP_Adresse_ohne_0x=unpack("H2" x length($hex_ip), pac +k("A*",$hex_ip)); unshift @hexadezimale_IP_Adresse_ohne_0x, "0x"; $hex_ip = join("", @hexadezimale_IP_Adresse_ohne_0x); $hex_ip =~ s/\s+//g; print qq~hex_ip: $hex_ip\n~; } if (substr($hex_ip,0,1) eq "") { $ip = "0.0.0.0"; print "IP: $ip\n"; } 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; }
        In my case I have found that some devices return strange output, it is in case no mgmt IP is available in sh cdp nei detail (CSS11501), older IOS or different platform.
        DB<69> p $_[0] DB<70> x $_[0] 0 "\c@\c@\c@\c@"
        My dirty hack for that was just check for valid hex-like input
        #if (substr($hex_ip,0,1) eq ""){ if ($hex_ip !~ m/0x.*/){ $ip = "0.0.0.0"; }
Re: Cisco SNMP CDP Poll
by kderkinderen (Initiate) on Aug 06, 2001 at 14:57 UTC
    Sorry for the clueless question. How do I redirect the output to a test file? I tried a simple 1>/home/kderkind/outfile.txt but it was blank. I then tried to open a file from within - but I guess I don't understand the "format" and "write" well enough. Got nothing - output goes nowhere. Thanks, Kevin
      Easiest way(I think) would be to change the bit of code that reads:
      #Prompt for SNMP community string print "community: "; chomp($community = <STDIN>); to $community=WHATEVERYOURCOMMUNITYSTRINGIS;
      Then launch the script with cdppoll.pl ip > nameoftextfile
      I am sure that there is a better way to do this(and one day I'll get off my but and add some text output as an option)
      Or if you open a filehandle to write to inside the code change the format STDOUT to format FILEHANDLE and the write to write FILEHANDLE. That should work as well.
        Put stuff like the community string into a config file and load it as needed. I use Config::Tiny but there are a host of alternatives.

        I put any variable that I may need in more than one program into a central system config file. That way, when community strings, passwords, file paths, etc. change, I only have to modify one file instead of twenty.

        Hope this helps,

        Jack

Re: Cisco SNMP CDP Poll
by dkessinger (Initiate) on Jan 01, 2012 at 15:58 UTC
    Running in Window XP Mode
    C:\Documents and Settings\XPMUser\My Documents\script>perl -v

    This is perl 5, version 12, subversion 4 (v5.12.4) built for MSWin32-x86-multi-thread

    C:\Documents and Settings\XPMUser\My Documents\script>perl cdpsnmp.txt xxx.xxx.xxx.xxx
    community: !!!!!!!


    CDP Neighbor Details for xxx.xxx.xxx.xxx
    --------------------------------------------------------------------------------------------
    Neighbor IP Name Interface Type |
    --------------------------------------------------------------------------------------------
    oid_context_match() is obsolete, use oid_base_match() instead at cdpsnmp.txt line 61

    line 61 = while(Net::SNMP::oid_context_match($starting_oid,$new_oid)){

    Help with this error please. I am a newbi to perl. I finally got it to run and it fails :(
    any help would be appreciated
    Daniel
      I just did this:
      sub get_oids{ my($starting_oid , $new_oid , $unique_oid , $result , $crap); my($ip , $name , $port , $type); $starting_oid = $_[0]; $new_oid = $starting_oid ; # while(Net::SNMP::oid_context_match($starting_oid,$new_oid)){ while(Net::SNMP::oid_base_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)) +{ if (Net::SNMP::oid_base_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")); @todo=(@todo,$ip); write; get_oids($new_oid); } } #Format the report format STDOUT = @<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<< +<<<<<< @<<<<<<<<<<<<<<<<<<<<< $ip,$name,$port,$type . }
Re: Cisco SNMP CDP Poll
by ArifS (Beadle) on Oct 08, 2014 at 14:10 UTC
    It dies-
    community: public CDP Neighbor Details for ---------------------------------------------------------------------- +---------- ------------ Neighbor IP Name Interface + T ype | ---------------------------------------------------------------------- +---------- ------------ Press any key to continue . . .
    Any suggestion where I put the IP address for the root-device? Is it at $done{"0.0.0.0"}=1; ?
    Please let me know...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://80979]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (6)
As of 2024-04-16 05:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found