#!/usr/bin/perl -w # This file is part of secnet. # See README for full list of copyright holders. # # secnet is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # secnet is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # version 3 along with secnet; if not, see # https://www.gnu.org/licenses/gpl.html. use strict; use IO::Handle; my $us = $0; $us =~ s{.*/}{}; open DEBUG, ">/dev/null" or die $!; if (@ARGV && $ARGV[0] eq '-D') { shift @ARGV; open DEBUG, ">&STDERR" or die $!; } die "$us: no arguments permitted\n" if @ARGV; our ($monh,$monchild); our %reported; # no entry: not reported, does not exist # /ry+/: reported, entry exists # during processing only: # /r/: reported, may not still exist # /y+/: not reported, entry exists sub killmonitor () { return unless $monchild; kill 9, $monchild or warn "$us: cannot kill monitor child [$monchild]: $!\n"; $monchild=undef; close $monh; } END { killmonitor(); } my $restart; for (;;) { my $o; eval { if (!$monh) { killmonitor(); $monh = new IO::File; $monchild = open $monh, "-|", qw(ip -o monitor addr) or die "spawn monitor: $!\n"; sleep(1) if $restart++; } else { my $discard; my $got = sysread $monh, $discard, 4096; die "read monitor: $!\n" unless defined $got; die "monitor failed\n" unless $got; } $_='r' foreach values %reported; print DEBUG "#########################################\n"; foreach my $ip (qw(4 6)) { print DEBUG "###### $ip:\n"; my $addrh = new IO::File; open $addrh, "-|", qw(ip -o), "-$ip", qw(addr show) or die "spawn addr $ip show: $!\n"; my $afstr = $ip==4 ? 'inet' : $ip==6 ? 'inet6' : die; while (<$addrh>) { print DEBUG "#$_"; if (m{^\d+\:\s*(\S+)\s+$afstr\s+([0-9a-z.:]+)(?:/\d+)?\s}) { my $rhs=$'; #'; my $outline = "$ip $1 $2"; # "ip -o addr show" has a ridiculous output format. In # particular, it mixes output keywords which introduce # values with ones which don't, and there seems to be # no way to tell without knowing all the possible # keywords. We hope that before the \ there is nothing # which contains arbitrary text (specifically, which # might be `tentative' other than to specify IPv6 # tentativeness). We have to do this for IPv6 only # because in the IPv4 output, the interface name # appears here! next if $ip==6 && $rhs=~m{[^\\]* tentative\s}; $reported{$outline} .= "y"; } else { chomp; warn "unexpected output from addr $ip show: $_\n"; } } my $r = close $addrh; die "addr $ip show failed $!\n" unless $r; $o = ''; } foreach my $k (keys %reported) { local $_ = $reported{$k}; if (m/^r$/) { $o .= "-$k\n"; delete $reported{$k}; } elsif (m/^y/) { $o .= "+$k\n"; } } }; if ($@) { print STDERR "$us: $@"; sleep 5; next; } print $o or die $!; STDOUT->flush or die $!; }