chiark / gitweb /
0393382cc80931f020dd2cd9fe85937e916416d6
[secnet.git] / polypath-interface-monitor-linux
1 #!/usr/bin/perl -w
2 use strict;
3 use IO::Handle;
4
5 my $us = $0;
6 $us =~ s{.*/}{};
7
8 open DEBUG, ">/dev/null" or die $!;
9
10 if (@ARGV && $ARGV[0] eq '-D') {
11     shift @ARGV;
12     open DEBUG, ">&STDERR" or die $!;
13 }
14
15 die "$us: no arguments permitted\n" if @ARGV;
16
17 our ($monh,$monchild);
18
19 our %reported;
20 #  no entry: not reported, does not exist
21 #  /ry+/: reported, entry exists
22 # during processing only:
23 #  /r/: reported, may not still exist
24 #  /y+/: not reported, entry exists
25
26 sub killmonitor () {
27     return unless $monchild;
28     kill 9, $monchild
29         or warn "$us: cannot kill monitor child [$monchild]: $!\n";
30     $monchild=undef;
31     close $monh;
32 }
33
34 END { killmonitor(); }
35
36 my $restart;
37
38 for (;;) {
39     my $o;
40     eval {
41         if (!$monh) {
42             killmonitor();
43             $monh = new IO::File;
44             $monchild = open $monh, "-|", qw(ip -o monitor addr)
45                 or die "spawn monitor: $!\n";
46             sleep(1) if $restart++;
47         } else {
48             my $discard;
49             my $got = sysread $monh, $discard, 4096;
50             die "read monitor: $!\n" unless defined $got;
51             die "monitor failed\n" unless $got;
52         }
53         $_='r' foreach values %reported;
54         print DEBUG "#########################################\n";
55         foreach my $ip (qw(4 6)) {
56             print DEBUG "###### $ip:\n";
57             my $addrh = new IO::File;
58             open $addrh, "-|", qw(ip -o), "-$ip", qw(addr show)
59                 or die "spawn addr $ip show: $!\n";
60             my $afstr = $ip==4 ? 'inet' : $ip==6 ? 'inet6' : die;
61             while (<$addrh>) {
62                 print DEBUG "#$_";
63                 if (m{^\d+\:\s*(\S+)\s+$afstr\s+([0-9a-z.:]+)(?:/\d+)?\s}) {
64                     my $rhs=$'; #';
65                     my $outline = "$ip $1 $2";
66                     # "ip -o addr show" has a ridiculous output format.  In
67                     # particular, it mixes output keywords which introduce
68                     # values with ones which don't, and there seems to be
69                     # no way to tell without knowing all the possible
70                     # keywords.  We hope that before the \ there is nothing
71                     # which contains arbitrary text (specifically, which
72                     # might be `tentative' other than to specify IPv6
73                     # tentativeness).  We have to do this for IPv6 only
74                     # because in the IPv4 output, the interface name
75                     # appears here!
76                     next if $ip==6 && $rhs=~m{[^\\]* tentative\s};
77                     $reported{$outline} .= "y";
78                 } else {
79                     chomp;
80                     warn "unexpected output from addr $ip show: $_\n";
81                 }
82             }
83             my $r = close $addrh;
84             die "addr $ip show failed $!\n" unless $r;
85             $o = '';
86         }
87         foreach my $k (keys %reported) {
88             local $_ = $reported{$k};
89             if (m/^r$/) {
90                 $o .= "-$k\n";
91                 delete $reported{$k};
92             } elsif (m/^y/) {
93                 $o .= "+$k\n";
94             }
95         }
96     };
97     if ($@) {
98         print STDERR "$us: $@";
99         sleep 5;
100         next;
101     }
102     print $o or die $!;
103     STDOUT->flush or die $!;
104 }