#!/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)*", substr($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 a8 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") : ($cap{'-------0'} = "Not a Router"); $cap =~ m/^[01]{6}[1][01]$/ ? ($cap{'------1-'} = "Ia a Transparent 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 Device") : ($cap{'---0----'} = "Not a End Device"); $cap =~ m/^[01]{2}[1][01]{5}$/ ? ($cap{'--1-----'} = "Is IGMP Capable") : ($cap{'--0-----'} = "Not a IGMP Capable"); $cap =~ m/^[01]{1}[1][01]{6}$/ ? ($cap{'-1------'} = "Is a Repeater") : ($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"; }