| 1 | #!/usr/bin/perl -w |
| 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 3 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 | |
| 20 | use strict; |
| 21 | use IO::Handle; |
| 22 | |
| 23 | my $us = $0; |
| 24 | $us =~ s{.*/}{}; |
| 25 | |
| 26 | open DEBUG, ">/dev/null" or die $!; |
| 27 | |
| 28 | if (@ARGV && $ARGV[0] eq '-D') { |
| 29 | shift @ARGV; |
| 30 | open DEBUG, ">&STDERR" or die $!; |
| 31 | } |
| 32 | |
| 33 | die "$us: no arguments permitted\n" if @ARGV; |
| 34 | |
| 35 | our ($monh,$monchild); |
| 36 | |
| 37 | our %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 | |
| 44 | sub 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 | |
| 52 | END { killmonitor(); } |
| 53 | |
| 54 | my $restart; |
| 55 | |
| 56 | for (;;) { |
| 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; |
| 72 | print DEBUG "#########################################\n"; |
| 73 | foreach my $ip (qw(4 6)) { |
| 74 | print DEBUG "###### $ip:\n"; |
| 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>) { |
| 80 | print DEBUG "#$_"; |
| 81 | if (m{^\d+\:\s*(\S+)\s+$afstr\s+([0-9a-z.:]+)(?:/\d+)?\s}) { |
| 82 | my $rhs=$'; #'; |
| 83 | my $outline = "$ip $1 $2"; |
| 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}; |
| 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 | } |