#!/usr/bin/env perl # # This script demonstrates the Net::CDP::Manager module. It should be # run as root. # use strict; use warnings; # This script can be run directly from the source directory use lib 'blib/arch'; use lib 'blib/lib'; use Net::CDP::Manager; use Net::CDP::Packet qw(:caps); use POSIX qw(setsid); require Sys::Syslog; sub AUTOLOAD { no strict 'vars'; my $func = $AUTOLOAD; $func =~ s/.*:://; if ($func eq 'syslog') { print $program."[". getpgrp(0) . "]: " . $_[1] . "\n"; } else { return 0; } } # Because Sys::Syslog is stupid, and doesn't export useful functions sub LOG_MASK { my $priority = shift; return 1 << $priority; } sub LOG_UPTO { my $priority = shift; return (1 << $priority) - 1 + (1 << $priority); } sub trim { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; } our %config = (); our $program = 'druid_cdpd'; sub pretty { defined $_[0] ? join(' ', @_) : '(unspecified)' } sub duplex { defined $_[0] ? ($_[0] ? 'full' : 'half') : '(unspecified)' } sub trust { defined $_[0] ? ($_[0] ? 'trusted' : 'untrusted') : '(unspecified)' } sub voice_vlan { defined $_[0] ? "Appliance $_[1], VLAN $_[0]" : '(unspecified)' } sub power_consumption { defined $_[0] ? "$_[0] mW" : '(unspecified)' } sub hexify { join ' ', map { map { sprintf '0x%02x', ord } split // } @_ } sub caps { my $caps = shift; my %map = ( CDP_CAP_ROUTER() => 'Router', CDP_CAP_TRANSPARENT_BRIDGE() => 'Transparent bridge', CDP_CAP_SOURCE_BRIDGE() => 'Source route bridge', CDP_CAP_SWITCH() => 'Switch', CDP_CAP_HOST() => 'Host', CDP_CAP_IGMP() => 'IGMP capable', CDP_CAP_REPEATER() => 'Repeater', ); join ', ', @map{sort grep { $caps & $_ } keys %map} } sub log_packet { my ($packet, $port) = @_; # Print out the packet syslog('debug',"Received on port $port:"); syslog('debug',' Version: '. pretty($packet->version)); syslog('debug',' TTL: '. pretty($packet->ttl)); syslog('debug',' Checksum: '. pretty($packet->checksum)); syslog('debug',' Device ID: '. pretty($packet->device)); if ($packet->addresses) { syslog('debug'," Addresses:"); foreach ($packet->addresses) { syslog('debug',' Protocol: '. pretty($_->protocol)); syslog('debug',' Address: '. pretty($_->address)); } } else { syslog('debug'," Addresses: (unspecified)"); } syslog('debug',' Port ID: '. pretty($packet->port)); syslog('debug',' Capabilities: '. caps($packet->capabilities)); syslog('debug',' IOS Version: '. pretty($packet->ios_version)); syslog('debug',' Platform: '. pretty($packet->platform)); if ($packet->ip_prefixes) { syslog('debug'," IP Prefixes:"); foreach ($packet->ip_prefixes) { syslog('debug',' Network: '. hexify($_->network)); syslog('debug',' Length: '. pretty($_->length)); } } else { syslog('debug'," IP Prefixes: (unspecified)"); } syslog('debug',' VTP Management Domain: '. pretty($packet->vtp_management_domain)); syslog('debug',' Native VLAN: '. pretty($packet->native_vlan)); syslog('debug',' Duplex: '. duplex($packet->duplex)); syslog('debug',' Voice VLAN: '. voice_vlan($packet->voice_vlan)); syslog('debug',' Voice VLAN (query): '. voice_vlan($packet->voice_vlan_query)); syslog('debug',' Power Consumption: '. power_consumption($packet->power_consumption)); syslog('debug',' MTU: '. pretty($packet->mtu)); syslog('debug',' Extended Trust: '. trust($packet->trusted)); syslog('debug',' COS for Untrusted ports: '. pretty($packet->untrusted_cos)); if ($packet->management_addresses) { syslog('debug'," Management Addresses:"); foreach ($packet->management_addresses) { syslog('debug',' Protocol: '. pretty($_->protocol)); syslog('debug',' Address: '. pretty($_->address)); } } else { syslog('debug'," Management Addresses: (unspecified)"); } syslog('debug', ''); } sub start { my %args = cdp_args((promiscuous => $config{'promiscuous'})); my $template = cdp_template; my $ios_version; $template->capabilities(CDP_CAP_SWITCH|CDP_CAP_ROUTER|CDP_CAP_TRANSPARENT_BRIDGE|CDP_CAP_IGMP); $template->voice_vlan($config{'voice_vlan'}); $template->duplex('full'); $template->platform('Cisco 851W'); #$template->ios_version($config{'ios_version'}); $ios_version =<ios_version($ios_version); $template->vtp_management_domain(''); cdp_template($template); cdp_manage cdp_ports; cdp_send; syslog("info", "Sent first announcement, managing ". join(', ', sort &cdp_managed)); } sub send_reply { my ($packet, $port) = @_; my $templ = cdp_template; if (($packet->platform ne $templ->platform) and defined($packet->voice_vlan_query)) { log_packet($packet, $port); cdp_send; } } sub main_loop { while (1) { # Update the port list cdp_manage cdp_ports; # Send CDP packets my ($packet, $port, $remain) = cdp_recv; # Wait for CDP packets for 60 seconds cdp_loop \&send_reply, 60; cdp_send; } } sub parse_args { my $parse_arg; my $arg; for $arg (@ARGV) { if (defined($parse_arg)) { $config{$parse_arg} = $arg; $parse_arg = undef; } if ($arg eq '-v') { $parse_arg = 'verbosity'; } if ($arg eq '-vvlan') { $parse_arg = 'voice_vlan'; } if ($arg eq '-ios_version') { $parse_arg = 'ios_version'; } if ($arg eq '-f') { $config{'daemonize'} = 0; } if ($arg eq '-p') { $config{'promiscuous'} = 1; } } } sub set_defaults { $config{'verbosity'} = 0; $config{'voice_vlan'} = 512; $config{'ios_version'} = '4.0'; $config{'daemonize'} = 1; $config{'promiscuous'} = 0; } sub daemonize { if (not $config{'daemonize'}) { return; } chdir '/' or die "Can't chdir to /: $?\n"; open STDIN, '/dev/null' or die "Can't open /dev/null: $?\n"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $?\n"; open STDERR, '>/dev/null' or die "Can't write to /dev/null: $?\n"; defined(my $pid = fork) or die "Can't fork: $?\n"; exit if $pid; setsid or die "Can't start a new session: $?\n"; umask 0; } sub setup_logging { if ($config{'daemonize'} == 1) { import Sys::Syslog qw( :standard :extended :macros); } my @log_levels = (&LOG_UPTO(&LOG_WARNING), &LOG_UPTO(&LOG_INFO), &LOG_UPTO(&LOG_DEBUG)); setlogmask($log_levels[$config{'verbosity'}]); openlog($program, "ndelay,pid", 'daemon'); syslog('info', "Starting Voiceroute Druid CDPd"); } sub catch_end { my $signame = shift; # Gracefully close log syslog('info', "Caught signal SIG$signame, closing..."); closelog(); } set_defaults; parse_args; setup_logging; daemonize; $SIG{TERM} = \&catch_end; $SIG{HUP} = \&catch_end; $SIG{INT} = \&catch_end; start; main_loop;