Merge remote-tracking branch 'mdw/mdw/powm-sec'
[secnet] / polypath-interface-monitor-linux
CommitLineData
e61a41a4 1#!/usr/bin/perl -w
c215a4bc
IJ
2
3# This file is part of secnet.
4# See README for full list of copyright holders.
5#
6# secnet is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version d of the License, or
9# (at your option) any later version.
10#
11# secnet is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14# General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# version 3 along with secnet; if not, see
18# https://www.gnu.org/licenses/gpl.html.
19
e61a41a4
IJ
20use strict;
21use IO::Handle;
22
23my $us = $0;
24$us =~ s{.*/}{};
25
90666d10
IJ
26open DEBUG, ">/dev/null" or die $!;
27
28if (@ARGV && $ARGV[0] eq '-D') {
29 shift @ARGV;
30 open DEBUG, ">&STDERR" or die $!;
31}
32
e61a41a4
IJ
33die "$us: no arguments permitted\n" if @ARGV;
34
35our ($monh,$monchild);
36
37our %reported;
38# no entry: not reported, does not exist
39# /ry+/: reported, entry exists
40# during processing only:
41# /r/: reported, may not still exist
42# /y+/: not reported, entry exists
43
44sub killmonitor () {
45 return unless $monchild;
46 kill 9, $monchild
47 or warn "$us: cannot kill monitor child [$monchild]: $!\n";
48 $monchild=undef;
49 close $monh;
50}
51
52END { killmonitor(); }
53
54my $restart;
55
56for (;;) {
57 my $o;
58 eval {
59 if (!$monh) {
60 killmonitor();
61 $monh = new IO::File;
62 $monchild = open $monh, "-|", qw(ip -o monitor addr)
63 or die "spawn monitor: $!\n";
64 sleep(1) if $restart++;
65 } else {
66 my $discard;
67 my $got = sysread $monh, $discard, 4096;
68 die "read monitor: $!\n" unless defined $got;
69 die "monitor failed\n" unless $got;
70 }
71 $_='r' foreach values %reported;
90666d10 72 print DEBUG "#########################################\n";
e61a41a4 73 foreach my $ip (qw(4 6)) {
90666d10 74 print DEBUG "###### $ip:\n";
e61a41a4
IJ
75 my $addrh = new IO::File;
76 open $addrh, "-|", qw(ip -o), "-$ip", qw(addr show)
77 or die "spawn addr $ip show: $!\n";
78 my $afstr = $ip==4 ? 'inet' : $ip==6 ? 'inet6' : die;
79 while (<$addrh>) {
90666d10 80 print DEBUG "#$_";
e61a41a4 81 if (m{^\d+\:\s*(\S+)\s+$afstr\s+([0-9a-z.:]+)(?:/\d+)?\s}) {
caa97633 82 my $rhs=$'; #';
e61a41a4 83 my $outline = "$ip $1 $2";
caa97633
IJ
84 # "ip -o addr show" has a ridiculous output format. In
85 # particular, it mixes output keywords which introduce
86 # values with ones which don't, and there seems to be
87 # no way to tell without knowing all the possible
88 # keywords. We hope that before the \ there is nothing
89 # which contains arbitrary text (specifically, which
90 # might be `tentative' other than to specify IPv6
91 # tentativeness). We have to do this for IPv6 only
92 # because in the IPv4 output, the interface name
93 # appears here!
94 next if $ip==6 && $rhs=~m{[^\\]* tentative\s};
e61a41a4
IJ
95 $reported{$outline} .= "y";
96 } else {
97 chomp;
98 warn "unexpected output from addr $ip show: $_\n";
99 }
100 }
101 my $r = close $addrh;
102 die "addr $ip show failed $!\n" unless $r;
103 $o = '';
104 }
105 foreach my $k (keys %reported) {
106 local $_ = $reported{$k};
107 if (m/^r$/) {
108 $o .= "-$k\n";
109 delete $reported{$k};
110 } elsif (m/^y/) {
111 $o .= "+$k\n";
112 }
113 }
114 };
115 if ($@) {
116 print STDERR "$us: $@";
117 sleep 5;
118 next;
119 }
120 print $o or die $!;
121 STDOUT->flush or die $!;
122}