diff options
-rwxr-xr-x | clients/ipv6-dns.pl | 138 | ||||
-rw-r--r-- | clients/ipv6-stats.pl | 98 |
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; +} + |