aboutsummaryrefslogtreecommitdiffstats
path: root/examples/historical/clients/lldpdiscover.pl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/historical/clients/lldpdiscover.pl')
-rwxr-xr-xexamples/historical/clients/lldpdiscover.pl247
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);
+}