doylebobs has asked for the wisdom of the Perl Monks concerning the following question:
Hi, To expand and deepen my knowledge of Perl (and network monitoring) I decided to write a script to decode Cisco's CDP protocol (see below). The script will not decode all types but should decode the most common ones. I would appreciate any feedback on how to improve this. In particular, I use four output formats but I think there must be a better way. Here is a sample output:
------------------------------------------- 0001 Device ID SW-BK-7 +F-1 0002 Addresses 192.16 +8.0.9 192.16 +8.0.1 0003 Port ID FastEt +hernet0/1 0004 Capabilities ------ +-0 Not a Router ------ +0- Not a Transparent -----0 +-- Not a Source-Route ----1- +-- Is a Switch ---0-- +-- Not a End Device --1--- +-- Is IGMP Capable -0---- +-- Not a Repeater 0005 Software Version Cisco IOS Soft +ware, C3560 Software (C3560-IPBASE-M), Version 12.2(25)SE 0006 Platform cisco +WS-C3560-48PS 0009 VTP Management Domain lan1-vtp 000a Native VLAN 1 000b Duplex Full 000e Voice VLAN 100 0012 Trust Bitmap 0 0013 Untrusted Port CoS 0 0016 Management Addresses 192.16 +8.0.9 192.16 +8.0.1 001a Power Available 0 mw 429496 +7295 mw -------------------------------------------
...and here is the script.
#!/usr/bin/perl use strict; use warnings; use Net::Pcap; my $dev = "eth0"; # interface to listen on for CDP packets my ($user_data, $err); my %cdp_types = ( '0001' => 'Device ID', '0002' => 'Addresses', '0003' => 'Port ID', '0004' => 'Capabilities', '0005' => 'Software Version', '0006' => 'Platform', '0008' => 'Protocol Hello', '0009' => 'VTP Management Domain', '000a' => 'Native VLAN', '000b' => 'Duplex', '000e' => 'Voice VLAN', '0010' => 'Power Consumption', '0012' => 'Trust Bitmap', '0013' => 'Untrusted Port CoS', '0016' => 'Management Addresses', '001a' => 'Power Available', ); my $pcap = Net::Pcap::pcap_open_live($dev, 1522, 1, 0, \$err) or die " +Can't open device $dev: $err\n"; Net::Pcap::pcap_loop($pcap, -1, \&callback, $user_data); sub callback { my $packet = pop @_; my $pkt = unpack "H*", $packet; if ((hex(substr($pkt, 24, 4)) <= 1518) && (hex(substr($pkt, 28, 2) +) == 170) && (hex(substr($pkt, 30, 2)) == 170)) { # "SNAP detected\n"; my (%cdp, %cdp_data, %cdp_output); my %ieee_snap_frame = ( dst_mac => substr($pkt, 0, 12), src_mac => substr($pkt, 12, 12), frame_lgt => substr($pkt, 24, 4), dsap => substr($pkt, 28, 2), # + DSAP ssap => substr($pkt, 30, 2), # + SSAP control => substr($pkt, 32, 2), # + !!! 1 or 2 bytes : 1 for SNAP snap => substr($pkt, 34, 10),# + 5 bytes for SNAP - 3 vendor code - 2 ethertype data => substr($pkt, 44), ); if ($ieee_snap_frame{snap} eq "00000c2000") { # "CDP Detected\n"; %cdp = ( version => substr($pkt, 44, 2), ttl => substr($pkt, 46, 2), chksum => substr($pkt, 48, 4), data => substr($pkt, 52), ); my @cdp_arr = split(//, $cdp{data}); # Extract CDP TLV fields from the frame while (@cdp_arr) { my $type = join '', splice(@cdp_arr, 0, 4); my $length = join '', splice(@cdp_arr, 0,4); my $value = join '', splice(@cdp_arr, 0, ((hex($length +) * 2) -8)); $cdp_data{$type} = $value; } # sort hash keys to ensure some order when printed foreach my $cdp_type (sort {hex($a) <=> hex($b)} keys %cdp +_data) { if ($cdp_type =~ m/0001|0003|0005|0006|0009/) { $cdp_output{$cdp_type} = join '', map(chr(hex($_)), + unpack "(a2)*", $cdp_data{$cdp_type}); } elsif ($cdp_type =~ m/0012|0013|000a/) { $cdp_output{$cdp_type} = hex($cdp_data{$cdp_type}); } elsif ($cdp_type =~ m/0002|0016/) { my @ip_addrs; my ($num_of_addresses, @addr) = unpack "a8 (a18)*", + $cdp_data{$cdp_type}; $num_of_addresses = hex(substr($cdp_data{$cdp_type} +, 0, 8)); # Each address section consists of 9 bytes foreach my $addr (@addr) { my @addr = map(hex($_), unpack("(a2)*", subst +r($addr, 10, 8))); push(@ip_addrs, join('.', @addr)); } $cdp_output{$cdp_type} = \@ip_addrs; } elsif ($cdp_type =~ m/0004/) { my $cap = enum_capabilities(unpack "B*", pack "H*", + substr($cdp_data{$cdp_type}, 6, 2)); $cdp_output{$cdp_type} = $cap; } elsif ($cdp_type =~ m/0007|0008/) { $cdp_output{$cdp_type} = $cdp_data{$cdp_type}; } elsif ($cdp_type =~ m/000b/) { $cdp_output{$cdp_type} = hex($cdp_data{$cdp_type}) ? + 'Full' : 'Half'; } elsif ($cdp_type =~ m/0010|000e/) { $cdp_output{$cdp_type} = hex(substr($cdp_data{$cdp_ +type}, -4)); } elsif ($cdp_type =~ m/001a/) { my ($req_id, $man_id, $pa1, $pa2) = unpack("a4 a4 a +8 a8", $cdp_data{$cdp_type}); $cdp_output{$cdp_type} = [map { $_ . " mw" } (hex($ +pa1), hex($pa2))]; } else { $cdp_output{$cdp_type} = $cdp_data{$cdp_type}; } } cdp_print(\%cdp_output); } } } sub enum_capabilities { my $cap = shift; my (%cap, $o_key, $n_key); $cap =~ m/^[01]{7}[1]$/ ? ($cap{'-------1'} = "Is a Router") : ($c +ap{'-------0'} = "Not a Router"); $cap =~ m/^[01]{6}[1][01]$/ ? ($cap{'------1-'} = "Ia a Transparen +t Bridge") : ($cap{'------0-'} = "Not a Transparent Bridge"); $cap =~ m/^[01]{5}[1][01]{2}$/ ? ($cap{'-----1--'} = "Is a Source- +Route Bridge") : ($cap{'-----0--'} = "Not a Source-Route Bridge"); $cap =~ m/^[01]{4}[1][01]{3}$/ ? ($cap{'----1---'} = "Is a Switch" +) : ($cap{'----0---'} = "Not a Switch"); $cap =~ m/^[01]{3}[1][01]{4}$/ ? ($cap{'---1----'} = "Is an End De +vice") : ($cap{'---0----'} = "Not a End Device"); $cap =~ m/^[01]{2}[1][01]{5}$/ ? ($cap{'--1-----'} = "Is IGMP Capa +ble") : ($cap{'--0-----'} = "Not a IGMP Capable"); $cap =~ m/^[01]{1}[1][01]{6}$/ ? ($cap{'-1------'} = "Is a Repeate +r") : ($cap{'-0------'} = "Not a Repeater"); return \%cap; } sub cdp_print { my ($cdp_output) = @_; my ($cdp_type, $k, $v, $value); format OUTPUT = @<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<< +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $cdp_type, $cdp_types{$cdp_type}, $cdp_output->{$cdp_type} . format OUTPUT2 = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $_ . format OUTPUT3 = @<<<<<<<<<<<< @<<<<<<<<<<<<<<<<< $k, $v . format OUTPUT4 = @<<<< @<<<<<<<<<<<<<<<<<<<<< $cdp_type, $cdp_types{$cdp_type} . print "-------------------------------------------\n"; foreach $cdp_type (sort {hex($a) <=> hex($b)} keys %$cdp_output) { if (ref($cdp_output->{$cdp_type}) =~ m/ARRAY/) { $~ = 'OUTPUT4'; write; $~ = 'OUTPUT2'; write foreach (@{$cdp_output->{$cdp_type}}); } elsif ( ref($cdp_output->{$cdp_type}) =~ m/HASH/) { $~ = 'OUTPUT4'; write; $~ = 'OUTPUT3'; foreach $k (sort keys %{$cdp_output->{$cdp_type}}) { $v = $cdp_output->{$cdp_type}->{$k}; write; } } else { $~ = 'OUTPUT'; write; } } print "-------------------------------------------\n"; }
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Advice on script
by NetWallah (Canon) on Jan 05, 2012 at 04:25 UTC | |
|
Re: Advice on script
by toolic (Bishop) on Jan 05, 2012 at 00:46 UTC |