diff options
-rwxr-xr-x | extras/tools/lldp/lolwhat.pl | 363 |
1 files changed, 267 insertions, 96 deletions
diff --git a/extras/tools/lldp/lolwhat.pl b/extras/tools/lldp/lolwhat.pl index e6cbd6a..c6d3da7 100755 --- a/extras/tools/lldp/lolwhat.pl +++ b/extras/tools/lldp/lolwhat.pl @@ -21,46 +21,62 @@ use FixedSNMP; use nms; use nms::snmp; -# Actual assets detected, indexed by chassis ID -my %assets; -my %arp; - -# Tracking arrays. Continue scanning until they are of the same length. -my @ips_checked; -my @ips_to_check; - - # If we are given one switch on the command line, add that and then exit. my $cmdline_community = shift; my @ips = @ARGV; +# Actual SNMP results - keyed by IP +my %snmpresults = (); + +# LLDP peers detected from remotes. +# Keyed by chassis ID - as seen remotely. +my %lldppeers = (); + my %lldpmap = (); my %lldpresolver = (); +my %ipmap = (); +my %peermap = (); +my %hood = (); +my %extended = (); -sub mylog { - my $msg = shift; - my $time = POSIX::ctime(time); - $time =~ s/\n.*$//; - printf STDERR "[%s] %s\n", $time, $msg; -} - +# tracking for log indentation +my $mylogindent = ""; +mylog("Polling initial systems"); -my %ipmap = (); -my %peermap = (); -my %snmpresults = (); +logindent(1); foreach my $target (@ips) { - my $snmp = get_snmp_data($target, $cmdline_community); - my $parsed = parse_snmp($snmp); - $snmpresults{$target} = $parsed; + probe_sys($target, $cmdline_community); +} +logindent(-1); + - #print Dumper(\$parsed); +peerdetect(); + +# List of chassis IDs checked. +# Note that we deliberately don't add the chassis id's from systems prboed +# directly from the command line. Experience has shown us that +# lldpLocChassisId can be unreliable, so we don't trust it, even if it does +# mean we might end up polling the same system multiple times. +my @chassis_ids_checked = (); +while (scalar keys %lldppeers > scalar @chassis_ids_checked) { + mylog("Probed auto-detected peers: " . scalar @chassis_ids_checked . " Total detected: " . scalar keys %lldppeers ); + logindent(1); + OUTER: for my $id (keys %lldppeers) { + for my $id2 (@chassis_ids_checked) { + if ($id2 eq $id) { + next OUTER; + } + } + probe_sys($lldppeers{$id}{ip}, $cmdline_community); + push @chassis_ids_checked,$id; + } + peerdetect(); + logindent(-1); } -#print Dumper(\%ipmap); -#print Dumper(\%peermap); +mylog("Probing complete. Trying to make road in the velling"); -my %hood = (); -my %extended = (); +deduplicate(); mylog("Building peermaps"); while (my ($ip, $value) = each %peermap) { @@ -75,8 +91,6 @@ while (my ($ip, $value) = each %peermap) { } } } -#print Dumper(\%hood); -#print Dumper(\%extended); my %result = ( snmpresults => \%snmpresults, hood => \%hood, extended => \%extended, ipmap => \%ipmap, peermap => \%peermap, lldpmap => \%lldpmap); @@ -84,6 +98,37 @@ mylog("Done. Outputting JSON."); print JSON::XS::encode_json(\%result); exit; +sub compare_targets { + my ($t1, $t2) = @_; + my $res = 0; + my $fuckperl = 0; + my $bad = ""; + $bad = "$t2 not found on $t1"; + foreach my $t1ip (@{$snmpresults{$t1}{ips}}) { + if ($fuckperl == 0) { + if ($t1ip eq $t2) { + $res = 1; + $fuckperl = 1; + } + } + } + $fuckperl = 0; + if ($res == 0) { + $bad = "$t1 not found on $t2"; + } + foreach my $t2ip (@{$snmpresults{$t2}{ips}}) { + if ($fuckperl == 0) { + if ($t2ip eq $t1) { + $res = $res + 1; + $fuckperl = 1; + } + } + } + if ($res == 1) { + mylog("... So there are two systems that look 50% similar. $bad (But not the other way around)"); + } + return $res; +} # Get raw SNMP data for an ip/community. sub get_snmp_data { my ($ip, $community) = @_; @@ -100,17 +145,147 @@ sub get_snmp_data { $ret{'ifTable'} = $session->gettable('ifTable'); $ret{'ifXTable'} = $session->gettable('ifXTable'); $ret{'ipAddressTable'} = $session->gettable('ipAddressTable'); + $ret{'ipAddrTable'} = $session->gettable('ipAddrTable'); #print Dumper(\%ret); }; if ($@) { my $tmp = "$@"; chomp($tmp); - mylog("\t" . $tmp); + mylog($tmp); return undef; } return \%ret; } +# Compare a new LLDP with old ones. +sub compare +{ + my %in = %{$_[0]}; + if (!defined($lldppeers{$in{id}})) { + return 1; + } + my %old = %{$lldppeers{$in{id}}}; + if ($old{name} eq $in{name}) { + return 0; + } else { + mylog("\t\tXXX: Fu... chassis ID collision from remote ID $in{id}."); + mylog("\t\t\tXXX: Old sysname/ip: $old{name} / $old{ip}"); + mylog("\t\t\tXXX: New sysname/ip: $in{name} / $in{ip}"); + return -1; + } +} + +sub peerdetect +{ + mylog("Detecting new candidates for probes"); + + logindent(1); + while (my ($target, $value) = each %snmpresults) { + while (my ($port, $data) = each %{$value->{interfaces}}) { + my %d = ( ip => $data->{lldpRemManAddr}, id => $data->{lldpRemChassisId}, name => $data->{lldpRemSysName} ); + if (!defined($d{ip})) { + next; + } + if (!defined($d{id})) { + mylog("wtf is up here? Man addr but no chassis id? I HATE NETWORK EQUIPMENT"); + next; + } + if (!defined($d{name})) { + mylog("I. Hate. Network. Equipment. We got IP, chassis id but no sysname."); + next; + } + if (compare(\%d) > 0) { + %{$lldppeers{$d{id}}} = %d; + mylog("Adding peer $d{name} / $d{ip} / $d{id}"); + } + } + } + logindent(-1); +} + +# Deduplicate entries of SNMP (in case we spotted the same thing twice) and +# detect collisions of sysnames and sort it out. +sub deduplicate +{ + my %remmap = (); + my %syscollisions = (); + my %locmap = (); + mylog("Checking for duplicated/corrupt chassis ids"); + logindent(1); + mylog("Building sysname -> chassis id table where possible"); + logindent(1); + while (my ($target, $value) = each %snmpresults) { + while (my ($port, $data) = each %{$value->{interfaces}}) { + if (!defined($data->{lldpRemSysName})) { + next; + } + my $name = $data->{lldpRemSysName}; + my $id = $data->{lldpRemChassisId}; + if (defined($remmap{$name}) and $remmap{$name} ne $id) { + mylog("sysName collision. This is only an issue if there is _also_ chassis id collision."); + push @{$syscollisions{$name}}, $id; + } else { + $remmap{$name} = $id; + } + } + } + logindent(-1); + mylog("Building local chassis id map"); + logindent(1); + while (my ($target, $value) = each %snmpresults) { + my $locchassis = $value->{lldpLocChassisId}; + if (!defined($locchassis)) { + next; + } + push @{$locmap{$locchassis}}, $target; + } + logindent(-1); + mylog("Checking for chassis id's with duplicate systems"); + logindent(1); + my %fixlist = (); + while (my ($id, $value) = each %locmap) { + if (@$value > 1) { + mylog("Duplicate or collision: chassis id ($id) : " . join(", ", @$value)); + my @checked = (); + foreach my $test (@$value) { + foreach my $test2 (@$value) { + if ($test2 eq $test) { + next; + } + if (compare_targets($test, $test2) != 2) { + mylog("Collision between $test and $test2. Adding to fixlist."); + $fixlist{$test} = 1; + $fixlist{$test2} = 1; + } + } + } + } + } + logindent(-1); + mylog("Applying fixes for ". (keys %fixlist) . " collisions"); + logindent(1); + while (my ($ip, $value) = each %fixlist) { + my $sysname = $snmpresults{$ip}; + if (defined($syscollisions{$sysname})) { + mylog("Can't really fix $ip because there's also a sysname collision for that sysname"); + $snmpresults{$ip}{lldpLocChassisId} = undef; + } elsif (defined($remmap{$sysname})) { + if ($remmap{$sysname} eq $snmpresults{$ip}{lldpLocChassisId}) { + mylog("Couldn't fix $ip / $sysname. The extrapolated chassis id seen from remote is the same as the colliding one.... "); + $snmpresults{$ip}{lldpLocChassisId} = undef; + } else { + mylog("Switching chassis ID from colliding $snmpresults{$ip}{lldpLocChassisId} to what remotes think matches: $remmap{$sysname}"); + $snmpresults{$ip}{lldpLocChassisId} = $remmap{$sysname}; + } + } else { + mylog("No alternate chassis IDs seen for $ip. Unsetting broken chassis ID anyway."); + $snmpresults{$ip}{lldpLocChassisId} = undef; + } + } + logindent(-1); + logindent(-1); +} + # Filter raw SNMP data over to something more legible. # This is the place to add all post-processed results so all parts of the # tool can use them. @@ -119,52 +294,32 @@ sub parse_snmp my $snmp = $_[0]; my %result = (); my %lol = (); - mylog("\tPost-processing SNMP"); + if (!defined($snmp)) { + mylog("XXX: No snmp data. Probably disconnect?"); + return; + } my $sysname = $snmp->{sysName}; - mylog("\tSysname: $sysname"); + mylog("Post-processing SNMP. Sysname: $sysname"); + logindent(1); $result{sysName} = $sysname; $result{sysDescr} = $snmp->{sysDescr}; my $chassis_id = nms::convert_mac($snmp->{lldpLocChassisId}); - my $bad_chassis_id = 0; if (defined($chassis_id)) { $result{lldpLocChassisId} = $chassis_id; - mylog("\tChassis id: $chassis_id"); - if (defined($chassis_id) and defined($lldpmap{$chassis_id}{sysName})) { - mylog("\t\tSwitch/chassis already known?"); - if ($lldpmap{$chassis_id}{sysName} ne $sysname) { - mylog("\t\tXXX: Likely chassis ID collision"); - mylog("\t\tXXX: Chassis ID previously seen as $lldpmap{$chassis_id}{sysName}"); - mylog("\t\tXXX: But we are $sysname! This will dampen the mood."); - $bad_chassis_id = 1; - if (defined($lldpresolver{$sysname})) { - mylog("\t\tXXX: Using magic backup based on sysname and neighbors. Flaky."); - $chassis_id = $lldpresolver{$sysname}; - if (defined($lldpmap{$chassis_id}{sysName})) { - mylog("\t\t\tFuc... it already existed?"); - if ($lldpmap{$chassis_id}{sysName} eq $sysname) { - mylog("\t\t\tOk, it's a match? We might have polled the same box multiple times?"); - } - } - $bad_chassis_id = 0; - } - } - } else { - mylog("\t\tNew chassis"); - $lldpmap{$chassis_id}{sysName} = $sysname; - } + mylog("Chassis id: $chassis_id"); } else { - mylog("\t\tNo lldpLocChassisId found. Bummer. Enable LLDP?"); - $bad_chassis_id = 1; + mylog("XXX: No lldpLocChassisId found. Bummer. Enable LLDP?"); } @{$result{ips}} = (); @{$result{peers}} = (); @{$result{lldppeers}} = (); - mylog("\tParsing lldp neighbors"); + mylog("Parsing lldp neighbors"); + logindent(1); while (my ($key, $value) = each %{$snmp->{lldpRemTable}}) { my $idx = $value->{lldpRemLocalPortNum}; my $rem_chassis_id = nms::convert_mac($value->{'lldpRemChassisId'}); my $remname = $value->{lldpRemSysName}; - mylog("\t\tSpotted $rem_chassis_id / $remname"); + mylog("Spotted $rem_chassis_id / $remname"); foreach my $key2 (keys %$value) { $lol{$idx}{$key2} = $value->{$key2}; } @@ -173,22 +328,10 @@ sub parse_snmp my %caps = (); nms::convert_lldp_caps($value->{'lldpRemSysCapEnabled'}, \%caps); $lol{$idx}{'lldpRemSysCapEnabled'} = \%caps; - $lldpresolver{$remname} = $rem_chassis_id; - if ($bad_chassis_id == 1) { - mylog("\t\tXXX:Skipping lldp-coupling due to broken/nonexistent lldpLocChassisId"); - next; - } - $lldpmap{$chassis_id}{peers}{$rem_chassis_id} = 1; - $lldpmap{$rem_chassis_id}{peers}{$chassis_id} = 1; - if (defined($lldpmap{$rem_chassis_id}{sysName})) { - if ($lldpmap{$rem_chassis_id}{sysName} ne $remname) { - mylog("\t\t\tXXX: Collision .... $rem_chassis_id: $remname vs $lldpmap{$rem_chassis_id}{sysName}"); - } - } else { - $lldpmap{$rem_chassis_id}{sysName} = $remname; - } } - mylog("\tParsing lldp neighbors management interfaces"); + logindent(-1); + mylog("Parsing lldp neighbors management interfaces"); + logindent(1); while (my ($key, $value) = each %{$snmp->{lldpRemManAddrTable}}) { my $old = 0; my $idx = $value->{lldpRemLocalPortNum}; @@ -214,8 +357,8 @@ sub parse_snmp } if ($old != 0) { if ($old != $addrtype) { - mylog("\t\tTwo management IPs discovered on port. v4 vs v6."); - mylog("\t\t\t$lol{$idx}{lldpRemManAddr} vs $remip"); + mylog("Two management IPs discovered on port. v4 vs v6."); + mylog("\t$lol{$idx}{lldpRemManAddr} vs $remip"); } if ($old > $addrtype) { $lol{$idx}{lldpRemManAddr} = $remip; @@ -223,21 +366,21 @@ sub parse_snmp } else { $lol{$idx}{lldpRemManAddr} = $remip; } - if (!defined($ipmap{$remip})) { - $ipmap{$remip} = $remname; - } - mylog("\t\tPeer added: $remip"); push @{$result{peers}}, $remip; push @{$lol{$idx}{peers}}, $remip; } - mylog("\tParsing local interfaces"); + logindent(-1); + mylog("Parsing local interfaces"); + logindent(1); while (my ($key, $value) = each %{$snmp->{ifTable}}) { $value->{ifPhysAddress} = nms::convert_mac($value->{ifPhysAddress}); foreach my $key2 (keys %$value) { $lol{$value->{ifIndex}}{$key2} = $value->{$key2}; } } - mylog("\tParsing ARP table"); + logindent(-1); + mylog("Parsing ARP table"); + logindent(1); while (my ($key, $value) = each %{$snmp->{ipNetToMediaTable}}) { my $mac = nms::convert_mac($value->{ipNetToMediaPhysAddress}); my $idx = $value->{ipNetToMediaIfIndex}; @@ -246,8 +389,8 @@ sub parse_snmp if ($lol{$idx}{ifPhysAddress} eq $mac) { push @{$lol{$idx}{ips}}, $value->{ipNetToMediaNetAddress}; push @{$result{ips}}, $value->{ipNetToMediaNetAddress}; + mylog("Found my self: " . $value->{ipNetToMediaNetAddress}); } else { - mylog("\t\tAdding peer: $value->{ipNetToMediaNetAddress}"); push @{$lol{$idx}{peers}}, $value->{ipNetToMediaNetAddress}; push @{$result{peers}}, $value->{ipNetToMediaNetAddress}; } @@ -257,7 +400,9 @@ sub parse_snmp $lol{$key}{$key2} = $value->{$key2}; } } - mylog("\tParsing ipAddressTable"); + logindent(-1); + mylog("Parsing ipAddressTable"); + logindent(1); while (my ($key, $value) = each %{$snmp->{ipAddressTable}}) { my $addr = $value->{ipAddressAddr}; my $addrtype = $value->{ipAddressAddrType}; @@ -266,23 +411,49 @@ sub parse_snmp } elsif ($addrtype == 2) { $addr = nms::convert_ipv6($addr); } else { - mylog("\t\tSkipping unknown addrtype: $addrtype"); next; } - mylog("\t\tLocal IP added: $addr"); + mylog("Local IP added: $addr"); push @{$result{ips}}, $addr; } + logindent(-1); + mylog("Parsing ipAddrTable"); + logindent(1); + while (my ($key, $value) = each %{$snmp->{ipAddrTable}}) { + push @{$result{ips}}, $value->{ipAdEntAddr}; + mylog("Adding local ipv4 ip $value->{ipAdEntAddr}"); + } + logindent(-1); @{$result{peers}} = sort @{$result{peers}}; @{$result{ips}} = sort @{$result{ips}}; $result{interfaces} = \%lol; - foreach my $localip (@{$result{ips}}) { - $ipmap{$localip} = $result{sysName}; + logindent(-1); + return \%result; +} + +sub logindent { + my $change = $_[0]; + if ($change > 0) { + $mylogindent = $mylogindent . "\t"; + } else { + chop $mylogindent; } - foreach my $peer (@{$result{peers}}) { - if (!defined($peermap{$peer})) { - @{$peermap{$peer}} = (); - } - push @{$peermap{$peer}}, $result{sysName}; +} +sub mylog { + my $msg = shift; + my $time = POSIX::ctime(time); + $time =~ s/\n.*$//; + printf STDERR "[%s] %s%s\n", $time, $mylogindent, $msg; +} +# Porbe a single system. +sub probe_sys +{ + my ($target, $community) = @_; + if (defined($snmpresults{$target})) { + mylog("Already probed $target. Skipping."); + return; } - return \%result; + my $snmp = get_snmp_data($target, $community); + my $parsed = parse_snmp($snmp); + $snmpresults{$target} = $parsed; } |