aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKristian Lyngstol <kristian@bohemians.org>2015-06-23 14:28:03 +0200
committerKristian Lyngstol <kristian@bohemians.org>2015-06-23 14:28:03 +0200
commit69c2a79d47aae8c6710a53fafa6e4a1f194c0418 (patch)
treeafe85b7c73516b2d615521df34df4e5a1aea9ab7
parenta495b7094845f2f484b8e6e0ea0b7077bbb790a5 (diff)
parent74c9478a2dbb868f13fc35330a331965e7484249 (diff)
Merge branch 'master' of github.com:tech-server/tgmanage
-rwxr-xr-xclients/ipv6-dns.pl138
-rw-r--r--clients/ipv6-stats.pl98
2 files changed, 236 insertions, 0 deletions
diff --git a/clients/ipv6-dns.pl b/clients/ipv6-dns.pl
new file mode 100755
index 0000000..dcb059d
--- /dev/null
+++ b/clients/ipv6-dns.pl
@@ -0,0 +1,138 @@
+#! /usr/bin/perl
+use DBI;
+use Net::DNS;
+use Net::IP;
+use lib '../include';
+use nms;
+use strict;
+use warnings;
+
+BEGIN {
+require "../include/config.pm";
+ eval {
+ require "../include/config.local.pm";
+ };
+}
+
+my $dbh = nms::db_connect() or die "Can't connect to database";
+my $res = Net::DNS::Resolver->new;
+
+$res->nameservers("$nms::config::pri_hostname.$nms::config::tgname.gathering.org");
+
+my $kname = 'DHCP_UPDATER';
+
+sub get_reverse {
+ my ($ip) = shift;
+ $ip = new Net::IP($ip) or return 0;
+ my $a = $res->query($ip->reverse_ip, 'PTR') or return 0;
+ foreach my $n ($a->answer) {
+ return $n->{'ptrdname'}; # Return first element, ignore the rest (=
+ }
+ return 0;
+}
+
+sub any_quad_a {
+ my ($host) = shift;
+ my $a = $res->query($host, 'AAAA') or return 0;
+ foreach my $n ($a->answer) {
+ return 1 if ($n->{'type'} eq 'AAAA');
+ }
+ return 0;
+}
+
+print "Running automagic IPv6 DNS updates\n";
+while (1) {
+
+ # Fetch visible IPv6 addresses from the last three minutes
+ #my $q = $dbh->prepare("SELECT DISTINCT ON (ipv6.mac) ipv6.address AS v6, ipv6.mac, ipv4.address AS v4, ipv6.time - ipv6.age*'1 second'::interval FROM ipv6 LEFT JOIN ipv4 ON ipv4.mac = ipv6.mac WHERE ipv6.time > NOW()-'3 min'::interval ORDER BY ipv6.mac, ipv6.time DESC, mac")
+ my $q = $dbh->prepare(
+"SELECT DISTINCT ON (v6) v6, ipv6.mac, ipv6.seen, v4
+FROM (SELECT DISTINCT ON (address) address AS v6, mac, seen FROM seen_mac WHERE family(address) = 6 AND seen > CURRENT_TIMESTAMP - '3 min'::interval) ipv6
+LEFT JOIN (SELECT DISTINCT ON (address) address AS v4, mac, seen FROM seen_mac WHERE family(address) = 4 AND seen > CURRENT_TIMESTAMP - '3 min'::interval) ipv4 ON ipv4.mac = ipv6.mac
+ORDER BY v6, ipv6.seen DESC, mac")
+ or die "Can't prepare query";
+ $q->execute() or die "Can't execute query";
+
+ my $aaaas = 0;
+ my $aaaa_errors = 0;
+ my $ptrs = 0;
+ my $ptr_errors = 0;
+ my $update;
+ my $v6;
+ while (my $ref = $q->fetchrow_hashref()) {
+ my $hostname = get_reverse($ref->{'v4'});
+ if ($hostname) {
+ $v6 = $ref->{'v6'};
+ my @parts = split('\.', $hostname);
+ my $hostname = shift @parts;
+ my $domain = join('.', @parts);
+ my $v6arpa = (new Net::IP($v6))->reverse_ip;
+
+ # Don't add records for nets we don't control
+ next if not $v6arpa =~ m/$nms::config::ipv6zone/;
+
+ # Add IPv6 reverse
+ if (!get_reverse($ref->{'v6'})) {
+ $update = Net::DNS::Update->new($nms::config::ipv6zone);
+ $update->push(pre => nxrrset("$v6arpa IN PTR")); # Only update if the RR is nonexistent
+ $update->push(update => rr_add("$v6arpa IN PTR $hostname.$domain."));
+ $update->sign_tsig($kname, $nms::config::ddns_key);
+ my $reply = $res->send($update);
+ if ($reply->header->rcode eq 'NOERROR') {
+ $ptrs++;
+ } else {
+ $ptr_errors++;
+ }
+ }
+
+ # Add IPv6 forward
+ if (!any_quad_a("$hostname.$domain")) {
+ $update = Net::DNS::Update->new($domain);
+ $update->push(pre => nxrrset("$hostname.$domain. IN AAAA $v6")); # Only update if the RR is nonexistent
+ $update->push(update => rr_add("$hostname.$domain. IN AAAA $v6"));
+ $update->sign_tsig($kname, $nms::config::ddns_key);
+ my $reply = $res->send($update);
+ if ($reply->header->rcode eq 'NOERROR') {
+ $aaaas++;
+ } else {
+ $aaaa_errors++;
+ }
+ }
+ }
+ }
+ print "Added $ptrs PTR records. $ptr_errors errors occured.\n";
+ print "Added $aaaas AAAA records. $aaaa_errors errors occured.\n";
+
+
+ # Remove old PTR records, that is, for hosts we haven't seen the last four
+ # hours, but not older than four and a half hours, as it would take forever to
+ # try to delete everything. FIXME: Query the zone file and diff against the
+ # database, to avoid running as many NS-updates as tuples in the result set.
+
+ $q = $dbh->prepare("SELECT DISTINCT address AS v6 FROM seen_mac WHERE seen BETWEEN CURRENT_TIMESTAMP - '4 hours'::interval AND CURRENT_TIMESTAMP - '4 hours 30 minutes'::interval")
+ or die "Can't prepare query";
+ $q->execute() or die "Can't execute query";
+
+ my $i = 0;
+ my $errors = 0;
+ while (my $ref = $q->fetchrow_hashref()) {
+ $v6 = $ref->{'v6'};
+ if (get_reverse($v6)) {
+ my $v6arpa = (new Net::IP($v6))->reverse_ip;
+ my $update = Net::DNS::Update->new($nms::config::ipv6zone);
+ $update->push(pre => yxrrset("$v6arpa PTR")); # Only update if the RR exists
+ $update->push(update => rr_del("$v6arpa IN PTR"));
+ $update->sign_tsig($kname, $nms::config::ddns_key);
+ my $reply = $res->send($update);
+ if ($reply->header->rcode eq 'NOERROR') {
+ $i++;
+ } else {
+ $errors++;
+ }
+ }
+ }
+
+ print "Deleted $i old PTR records. $errors errors occured.\n";
+ print "Sleeping for two minutes.\n";
+ sleep(120);
+}
diff --git a/clients/ipv6-stats.pl b/clients/ipv6-stats.pl
new file mode 100644
index 0000000..ab76cc9
--- /dev/null
+++ b/clients/ipv6-stats.pl
@@ -0,0 +1,98 @@
+#! /usr/bin/perl
+use strict;
+use warnings;
+use lib '../include';
+use nms;
+use Data::Validate::IP qw(is_public_ipv6 is_public_ipv4 is_private_ipv4);
+use Net::MAC qw(mac_is_unicast);
+
+my $dbh = nms::db_connect();
+$dbh->{AutoCommit} = 0;
+
+while (1) {
+ my $coregws = $dbh->prepare("SELECT ip, community, sysname FROM switches WHERE switchtype <> 'dlink3100'")
+ or die "Can't prepare query: $!";
+ $coregws->execute;
+
+ my %seen = ();
+ my $num_v4 = 0;
+ my $num_v6 = 0;
+ while (my $ref = $coregws->fetchrow_hashref) {
+ print STDERR "Querying ".$ref->{'sysname'}." ...\n";
+ my $snmp;
+ eval {
+ $snmp = nms::snmp_open_session($ref->{'ip'}, $ref->{'community'});
+ };
+ warn $@ if $@;
+ next if not $snmp;
+
+ # Pull in old media table that does not support ipv6.
+ my $ip_phys_table = fetch($snmp, ('ipNetToMediaNetAddress', 'ipNetToMediaPhysAddress'));
+ for my $entry (values %$ip_phys_table) {
+ my $ip_addr = $entry->{'ipNetToMediaNetAddress'};
+ my $mac = Net::MAC->new(
+ mac => nms::convert_mac($entry->{'ipNetToMediaPhysAddress'}),
+ die => 0,
+ );
+
+ next if $mac->get_base() != 16 || $mac->get_mac() eq ''; # We only support base 16 addresses
+ next if (!is_public_ipv4($ip_addr) && !is_private_ipv4($ip_addr)); # We consider RFC1918 public
+
+ $seen{$ip_addr} = $mac->get_mac();
+ $num_v4++;
+ }
+
+ # Pull in new media table with IPv6 support
+ $ip_phys_table = $snmp->gettable('ipNetToPhysicalTable');
+ for my $entry (values %$ip_phys_table) {
+ my $type = $entry->{'ipNetToPhysicalNetAddressType'};
+ my $ip_addr = undef;
+ my $mac = Net::MAC->new(
+ mac => nms::convert_mac($entry->{'ipNetToPhysicalPhysAddress'}),
+ die => 0,
+ );
+
+ if ($type != 2) {
+ warn "$ip_addr is of unexpected type $type (should be 2)! Tell Berge.\n";
+ next;
+ }
+
+ $ip_addr = nms::convert_ipv6($entry->{'ipNetToPhysicalNetAddress'});
+
+ next if $mac->get_base() != 16 || $mac->get_mac() eq ''; # We only support base 16 addresses
+ next if not is_public_ipv6($ip_addr);
+
+ $seen{$ip_addr} = $mac->get_mac();
+ $num_v6++;
+ }
+
+ }
+
+ # Populate database
+ my $i = 0;
+ foreach my $ip_addr (keys %seen) {
+ $i++;
+ my $q = $dbh->do('INSERT INTO seen_mac (address, mac) VALUES (?, ?)', undef, $ip_addr, $seen{$ip_addr})
+ or die "Can't execute query: $!";
+ }
+
+ $dbh->commit;
+ print "Collected $num_v6 IPv6 addresses and $num_v4 IPv4 addresses. $i unique addresses.\n";
+ print "Sleeping for 60 seconds ...\n";
+ sleep(60);
+}
+
+
+# Fetch provided fields from a single table returning {iid => {tag => val}}
+sub fetch {
+ my $session = shift;
+ my @vars = map { new SNMP::Varbind([$_]) } @_;
+ my $data = {};
+ foreach my $result (@{$session->bulkwalk(0, 8, new SNMP::VarList(@vars))}) {
+ foreach my $entry (@$result) {
+ $data->{$entry->iid}->{$entry->tag} = $entry->val;
+ }
+ }
+ return $data;
+}
+