diff options
Diffstat (limited to 'clients')
-rwxr-xr-x | clients/lldpdiscover.pl | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/clients/lldpdiscover.pl b/clients/lldpdiscover.pl new file mode 100755 index 0000000..f90c5c3 --- /dev/null +++ b/clients/lldpdiscover.pl @@ -0,0 +1,136 @@ +#! /usr/bin/perl +use DBI; +use POSIX; +use Time::HiRes; +use strict; +use warnings; + +use lib '../include'; +use nms; + +my $dbh = nms::db_connect(); +$dbh->{AutoCommit} = 0; + +# If we are given one switch on the command line, poll that and then exit. +my ($cmdline_ip, $cmdline_community) = @ARGV; +if (defined($cmdline_ip) && defined($cmdline_community)) { + eval { + discover_lldp_neighbors($dbh, $cmdline_ip, $cmdline_community); + }; + if ($@) { + mylog("ERROR: $@ (during poll of $cmdline_ip)"); + $dbh->rollback; + } + $dbh->disconnect; + exit; +} + +# First, find all machines that lack an LLDP chassis ID. +my $q = $dbh->prepare("SELECT switch, ip, community FROM switches WHERE lldp_chassis_id IS NULL AND ip <> '127.0.0.1'"); +$q->execute; + +while (my $ref = $q->fetchrow_hashref) { + my ($switch, $ip, $community) = ($ref->{'switch'}, $ref->{'ip'}, $ref->{'community'}); + eval { + my $session = nms::snmp_open_session($ip, $community); + + # Cisco returns completely bogus values if we use get() + # on lldpLocChassisId.0, it seems. Work around it by using getnext(). + my $response = $session->getnext('lldpLocChassisId'); + my $chassis_id = nms::convert_mac($response); + die "SNMP error: " . $session->error() if (!defined($chassis_id)); + $dbh->do('UPDATE switches SET lldp_chassis_id=? WHERE switch=?', undef, + $chassis_id, $switch); + mylog("Set chassis ID for $ip to $chassis_id."); + }; + if ($@) { + mylog("ERROR: $@ (during poll of $ip)"); + $dbh->rollback; + } +} + +# Now ask all switches for their LLDP neighbor table. +$q = $dbh->prepare("SELECT ip, community FROM switches WHERE lldp_chassis_id IS NOT NULL AND ip <> '127.0.0.1'"); +$q->execute; + +while (my $ref = $q->fetchrow_hashref) { + my ($ip, $community) = ($ref->{'ip'}, $ref->{'community'}); + eval { + discover_lldp_neighbors($dbh, $ip, $community); + }; + if ($@) { + mylog("ERROR: $@ (during poll of $ip)"); + $dbh->rollback; + } +} + +$dbh->disconnect; + +sub discover_lldp_neighbors { + my ($dbh, $ip, $community) = @_; + my $qexist = $dbh->prepare('SELECT COUNT(*) AS cnt FROM switches WHERE lldp_chassis_id=?'); + + my $session = nms::snmp_open_session($ip, $community); + my $remtable = $session->gettable('lldpRemTable'); + my $addrtable; + while (my ($key, $value) = each %$remtable) { + my $chassis_id = nms::convert_mac($value->{'lldpRemChassisId'}); + my $sysname = $value->{'lldpRemSysName'}; + + # Do not try to poll servers. + my %caps = (); + nms::convert_lldp_caps($value->{'lldpRemSysCapEnabled'}, \%caps); + next if (!$caps{'cap_enabled_bridge'} && !$caps{'cap_enabled_ap'} && !$caps{'cap_enabled_router'}); + + my $exists = $dbh->selectrow_hashref($qexist, undef, $chassis_id)->{'cnt'}; + next if ($exists); + + # Pull in the management address table lazily. + $addrtable = $session->gettable("lldpRemManAddrTable") if (!defined($addrtable)); + + # Search for this key in the address table. + my @v4addrs = (); + my @v6addrs = (); + while (my ($addrkey, $addrvalue) = each %$addrtable) { + #next unless $addrkey =~ /^\Q$key\E\.1\.4\.(.*)$/; # 1.4 = ipv4, 2.16 = ipv6 + next unless $addrkey =~ /^\Q$key\E\./; # 1.4 = ipv4, 2.16 = ipv6 + my $addr = $addrvalue->{'lldpRemManAddr'}; + my $addrtype = $addrvalue->{'lldpRemManAddrSubtype'}; + if ($addrtype == 1) { + push @v4addrs, nms::convert_ipv4($addr); + } elsif ($addrtype == 2) { + push @v6addrs, nms::convert_ipv6($addr); + } else { + die "Unknown address type $addr"; + } + } + my $addr; + if (scalar @v6addrs > 0) { + $addr = $v6addrs[0]; + } elsif (scalar @v4addrs > 0) { + $addr = $v4addrs[0]; + } else { + warn "Could not find a management address for chassis ID $chassis_id (sysname=$sysname, lldpRemIndex=$key)"; + next; + } + + # Yay, a new switch! Make a new type for it. + # We simply guess that the community is the same as ours. + # TODO(sesse): Autopopulate ports from type. + # TODO(sesse): Autopopulate port names. + mylog("Inserting new switch $sysname ($addr)."); + my $switchtype = "auto-$sysname-$chassis_id"; + $dbh->do('INSERT INTO switchtypes (switchtype, ports) VALUES (?, ?)', undef, + $switchtype, ''); + $dbh->do('INSERT INTO switches (ip, sysname, switchtype, community, lldp_chassis_id) VALUES (?, ?, ?, ?, ?)', undef, + $addr, $sysname, $switchtype, $community, $chassis_id); + $dbh->commit; + } +} + +sub mylog { + my $msg = shift; + my $time = POSIX::ctime(time); + $time =~ s/\n.*$//; + printf STDERR "[%s] %s\n", $time, $msg; +} |