aboutsummaryrefslogtreecommitdiffstats
path: root/clients/lldpdiscover.pl
blob: f90c5c3c75eed634d090b36a26cf64d7026651d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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;
}