diff options
Diffstat (limited to 'examples/historical/clients/lldpdiscover.pl')
-rwxr-xr-x | examples/historical/clients/lldpdiscover.pl | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/examples/historical/clients/lldpdiscover.pl b/examples/historical/clients/lldpdiscover.pl new file mode 100755 index 0000000..2f33bd9 --- /dev/null +++ b/examples/historical/clients/lldpdiscover.pl @@ -0,0 +1,247 @@ +#! /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, add that and then exit. +my ($cmdline_ip, $cmdline_community) = @ARGV; +if (defined($cmdline_ip) && defined($cmdline_community)) { + eval { + my $session = nms::snmp_open_session($cmdline_ip, $cmdline_community); + my $sysname = $session->get('sysName.0'); + my $chassis_id = get_lldp_chassis_id($session); + add_switch($dbh, $cmdline_ip, $sysname, $chassis_id, $cmdline_community); + }; + if ($@) { + mylog("ERROR: $@ (during poll of $cmdline_ip)"); + $dbh->rollback; + } + $dbh->disconnect; + exit; +} + +# Find all candidate SNMP communities. +my $snmpq = $dbh->prepare("SELECT DISTINCT community FROM switches"); +$snmpq->execute; +my @communities = (); +while (my $ref = $snmpq->fetchrow_hashref) { + push @communities, $ref->{'community'}; +} + +# 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' and switchtype <> 'ex2200'"); +$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); + my $chassis_id = get_lldp_chassis_id($session); + 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; + } +} +$dbh->commit; + +# Now ask all switches for their LLDP neighbor table. +$q = $dbh->prepare("SELECT ip, sysname, community FROM switches WHERE lldp_chassis_id IS NOT NULL AND ip <> '127.0.0.1' AND switchtype <> 'ex2200'"); +$q->execute; + +while (my $ref = $q->fetchrow_hashref) { + my ($ip, $sysname, $community) = ($ref->{'ip'}, $ref->{'sysname'}, $ref->{'community'}); + eval { + discover_lldp_neighbors($dbh, $ip, $sysname, $community); + }; + if ($@) { + mylog("ERROR: $@ (during poll of $ip)"); + $dbh->rollback; + } + $dbh->commit; +} + +$dbh->disconnect; + +sub discover_lldp_neighbors { + my ($dbh, $ip, $local_sysname, $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_router'}); + next if ($caps{'cap_enabled_ap'}); + next if ($caps{'cap_enabled_telephone'}); + + next if $sysname =~ /nocnexus/; + + my $sysdesc = $value->{'lldpRemSysDesc'}; + next if $sysdesc =~ /\b(C1530|C3600|C3700)\b/; + + my $exists = $dbh->selectrow_hashref($qexist, undef, $chassis_id)->{'cnt'}; + next if ($exists); + + print "Found $local_sysname -> $sysname ($chassis_id)\n"; + + # 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) { + my $v6addr = nms::convert_ipv6($addr); + next if $v6addr =~ /^fe80:/; # Ignore link-local. + push @v6addrs, $v6addr; + } 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; + } + + # We simply guess that the community is the same as ours. + add_switch($dbh, $addr, $sysname, $chassis_id, @communities); + } +} + +sub mylog { + my $msg = shift; + my $time = POSIX::ctime(time); + $time =~ s/\n.*$//; + printf STDERR "[%s] %s\n", $time, $msg; +} + +sub get_ports { + my ($ip, $sysname, $community) = @_; + my $ret = undef; + eval { + my $session = nms::snmp_open_session($ip, $community); + $ret = $session->gettable('ifTable', columns => [ 'ifType', 'ifDescr' ]); + }; + if ($@) { + mylog("Error during SNMP to $ip ($sysname): $@"); + return undef; + } + return $ret; +} + +sub get_ifindex_for_physical_ports { + my $ports = shift; + my @indices = (); + for my $port (values %$ports) { + next unless ($port->{'ifType'} eq 'ethernetCsmacd'); + push @indices, $port->{'ifIndex'}; + } + return @indices; +} + +sub compress_ports { + my (@ports) = @_; + my $current_range_start = undef; + my $last_port = undef; + + my @ranges = (); + for my $port (sort { $a <=> $b } (@ports)) { + if (!defined($current_range_start)) { + # First element. + $current_range_start = $last_port = $port; + next; + } + if ($port == $last_port + 1) { + # Just extend the current range. + ++$last_port; + } else { + push @ranges, range_from_to($current_range_start, $last_port); + $current_range_start = $last_port = $port; + } + } + push @ranges, range_from_to($current_range_start, $last_port); + return join(',', @ranges); +} + +sub range_from_to { + my ($from, $to) = @_; + if ($from == $to) { + return $from; + } else { + return "$from-$to"; + } +} + +sub add_switch { + my ($dbh, $addr, $sysname, $chassis_id, @communities) = @_; + + # Yay, a new switch! Make a new type for it. + my $ports; + my $community; + for my $cand_community (@communities) { + $community = $cand_community; + $ports = get_ports($addr, $sysname, $community); + last if (defined($ports)); + } + return if (!defined($ports)); + my $portlist = compress_ports(get_ifindex_for_physical_ports($ports)); + mylog("Inserting new switch $sysname ($addr, ports $portlist)."); + my $switchtype = "auto-$sysname-$chassis_id"; + $dbh->do('INSERT INTO switchtypes (switchtype, ports) VALUES (?, ?)', undef, + $switchtype, $portlist); + $dbh->do('INSERT INTO switches (ip, sysname, switchtype, community, lldp_chassis_id) VALUES (?, ?, ?, ?, ?)', undef, + $addr, $sysname, $switchtype, $community, $chassis_id); + for my $port (values %$ports) { + $dbh->do('INSERT INTO portnames (switchtype, port, description) VALUES (?, ?, ?)', + undef, $switchtype, $port->{'ifIndex'}, $port->{'ifDescr'}); + } + + # Entirely random placement. Annoying? Fix it yourself. + my $x = int(rand 1200); + my $y = int(rand 650); + my $box = sprintf "((%d,%d),(%d,%d))", $x, $y, $x+40, $y+40; + $dbh->do("INSERT INTO placements (switch,placement) VALUES (CURRVAL('switches_switch_seq'), ?)", + undef, $box); + + $dbh->commit; +} + +sub get_lldp_chassis_id { + my ($session) = @_; + + # 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'); + return nms::convert_mac($response); +} |