summaryrefslogtreecommitdiff
path: root/fw
diff options
context:
space:
mode:
Diffstat (limited to 'fw')
-rwxr-xr-xfw/test/fwtest.rc122
1 files changed, 101 insertions, 21 deletions
diff --git a/fw/test/fwtest.rc b/fw/test/fwtest.rc
index 0e947f3..0b923e9 100755
--- a/fw/test/fwtest.rc
+++ b/fw/test/fwtest.rc
@@ -8,6 +8,13 @@
#
# Card mode is not covered: it takes the machine's card away, and a
# test that can leave you with no network is a test nobody runs.
+#
+# Run it twice in a row. It used to pass the first time and fail the
+# second, because it left its firewalls running and their interfaces
+# bound, and the next run's stack routed out the dead one; the failure
+# was blamed on timing in a comment that told the next person to
+# disbelieve a real result. The last two checks exist so that cannot
+# happen again quietly.
rfork ne
fw=$1
@@ -26,12 +33,23 @@ mkdir -p $nA $nB
mtpt=$tmp/ctl
mkdir -p $mtpt
+# The stacks the packet checks build for themselves. Fixed numbers are
+# fine because they are unbound again at the end of each block, and the
+# last check proves it.
+stacks=(20 21 22 23)
+
+# A port of our own. Conversations outlive the run that made them - a
+# devip Conv is never freed - so a fixed port makes one run's leftovers
+# into the next run's "address in use".
+port=`{echo $pid | awk '{print 17000 + ($1 % 900)}'}
+
# Results go to a file, not to variables: every check below runs inside
# an @{} that needs its own namespace, and an assignment in there never
# reaches the parent. Counting in variables silently reported one pass
# out of seventeen.
res=$tmp/results
>$res
+>$tmp/why
# check <name> <expected> <got>
fn check {
@@ -44,26 +62,70 @@ fn check {
echo ' FAIL ' $1
echo ' want: '$2
echo ' got: '$3
+ if(test -s $tmp/why)
+ echo ' said: '`{cat $tmp/why}
}
+ >$tmp/why
}
-# Did that write succeed? Not what it said: rc reports a failed
-# redirect from the outer shell, so the message cannot be captured from
-# in here. Refused or not is the thing being tested anyway.
+# Did that write succeed?
+#
+# Not with rc's own redirection: a failed > is reported by the shell
+# itself and escapes any >[2] put around it, so there is no way to see
+# what went wrong. cp does the same create(2) and reports it on its
+# own standard error, where a failing check can print it.
fn wr {
- if(@{ echo -n $2 > $1 } >[2]/dev/null)
+ echo -n $2 > $tmp/in
+ >$tmp/why
+ if(@{ cp $tmp/in $1 } >[2] $tmp/why)
echo ok
if not
echo refused
}
fn rd {
- if(@{ cat $1 >/dev/null } >[2]/dev/null)
+ >$tmp/why
+ if(@{ cat $1 >/dev/null } >[2] $tmp/why)
echo ok
if not
echo refused
}
+# Is it there at all, on the real /net?
+#
+# Every "refused" check below is also passed by a path that does not
+# exist, which is how a check for a hole can go green on a kernel that
+# never had the hole to begin with. So each one is paired with this,
+# asked outside the sandbox: the thing being refused has to be a thing.
+fn have {
+ if(test -e $1)
+ echo yes
+ if not
+ echo no
+}
+
+# Stop the firewalls a packet check started.
+#
+# Take the interfaces away and fw follows: the relay's read fails and
+# threadexitsall takes the rest of it down. Nothing else can find
+# these processes - fw daemonizes, so the shell's $apid is gone before
+# the server exists, and ps shows fw no arguments to match on - and
+# "kill fw" would be wrong on a machine running a real one. Doing it
+# this way also exercises the fail-closed path: if one relay ever goes
+# back to dying quietly, the leak checks at the end catch it.
+fn stopfw {
+ for(d in $*)
+ for(i in `{ls -p $d/ipifc | grep '^[0-9]'})
+ @{ echo -n unbind > $d/ipifc/$i/ctl } >[2]/dev/null
+ sleep 2
+}
+
+# fw processes belonging to anyone, counted the same way twice
+fn nfw {
+ ps | awk '$NF == "fw"' | wc -l
+}
+fwbefore=`{nfw}
+
echo '== rules: parsing'
cat > $tmp/bad.ndb <<'!'
@@ -76,6 +138,12 @@ cat > $tmp/empty.ndb <<'!'
# nothing
!
+echo '== namespace mode: what is refused is there to refuse'
+for(p in /net/udp/clone /net/gre/clone /net/ndb /net/log /net/ipifc/0/data){
+ r=`{have $p}
+ check 'the real /net has '^$p yes $"r
+}
+
echo '== namespace mode'
@{
rfork n
@@ -105,13 +173,11 @@ echo '== namespace mode'
check 'but its status still is' ok $"r
}
-cat > $tmp/lport.ndb <<'!'
-allow=in proto=tcp lport=17099
-!
@{
rfork n
+ echo 'allow=in proto=tcp lport='^$port > $tmp/lport.ndb
$fw $tmp/lport.ndb >[2]/dev/null
- r=`{wr /net/tcp/clone 'announce 17099'}
+ r=`{wr /net/tcp/clone 'announce '^$port}
check 'announce matches lport, not port' ok $"r
r=`{wr /net/tcp/clone 'announce 17098'}
check 'a different port is denied' refused $"r
@@ -152,14 +218,14 @@ deny=* log=yes
r=`{wr $mtpt/ctl 'delete 0'}
check 'delete 0 is refused' refused $"r
+
+ stopfw $nA $nB
}
echo '== packets, between two stacks'
-cat > $tmp/wire.ndb <<'!'
-allow=in proto=tcp lport=17099
-!
@{
rfork n
+ echo 'allow=in proto=tcp lport='^$port > $tmp/wire.ndb
bind -a '#I22' $nA
bind -a '#I23' $nB
$fw -m $mtpt $tmp/wire.ndb $nA^'!'^10.9.9.1^'!'^/24 $nB^'!'^10.9.9.2^'!'^/24 >[2]/dev/null &
@@ -168,28 +234,42 @@ allow=in proto=tcp lport=17099
r=`{cat $nA/ipifc/0/status | sed 1q | awk '{print $4}'}
check 'the pkt interface does not claim a 4096 mtu' 1500 $"r
- @{ echo -n 'announce 17099'; sleep 25 } > $nB/tcp/clone &
+ @{ echo -n 'announce '^$port; sleep 15 } > $nB/tcp/clone &
sleep 3
- @{ echo -n 'connect 10.9.9.2!17099'; sleep 20 } > $nA/tcp/clone &
+ @{ echo -n 'connect 10.9.9.2!'^$port; sleep 12 } > $nA/tcp/clone &
sleep 6
- # Timing-sensitive: the handshake has to complete through fw
- # before this looks. It passes when run on its own and fails
- # here intermittently, so a failure of this one check alone is
- # not evidence of a fault - check it by hand before believing it.
- r=`{grep -c 17099 $mtpt/flows}
+ r=`{grep -c $port $mtpt/flows}
check 'a permitted connection crosses, and is tracked' 1 $"r
# one rule, both directions: state, not a second rule
r=`{grep -c . $mtpt/rules}
check 'it took one rule to do that' 1 $"r
- echo -n 'prepend deny=in proto=tcp lport=17099' > $mtpt/ctl
+ echo -n 'prepend deny=in proto=tcp lport='^$port > $mtpt/ctl
sleep 1
- r=`{grep -c 17099 $mtpt/flows}
+ r=`{grep -c $port $mtpt/flows}
check 'blocking a port drops the live connection' 0 $"r
+
+ stopfw $nA $nB
}
+echo '== the test cleans up after itself'
+r=`{nfw}
+check 'no fw processes are left running' $"fwbefore $"r
+
+>$tmp/ifcs
+for(s in $stacks)
+ @{
+ rfork n
+ mkdir -p $tmp/z
+ bind -a '#I'^$s $tmp/z
+ for(i in `{ls -p $tmp/z/ipifc | grep '^[0-9]'})
+ sed 1q $tmp/z/ipifc/$i/status >> $tmp/ifcs
+ }
+r=`{grep -c 'device pkt' $tmp/ifcs}
+check 'no interfaces are left bound, so the next run is clean' 0 $"r
+
echo
npass=`{grep -c '^ok' $res}
nfail=`{grep -c '^FAIL' $res}