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
|
#!/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 $!;
}
|