diff options
Diffstat (limited to 'fw/test')
| -rwxr-xr-x | fw/test/fwtest.rc | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/fw/test/fwtest.rc b/fw/test/fwtest.rc new file mode 100755 index 0000000..0e947f3 --- /dev/null +++ b/fw/test/fwtest.rc @@ -0,0 +1,200 @@ +#!/bin/rc +# fwtest [fw] - check fw against the things that have broken before. +# +# Every check here is a bug that once shipped. Run it after touching +# anything; it needs no network of its own and does not disturb the +# machine's, because the packet checks run between two IP stacks it +# makes for itself. +# +# 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. +rfork ne + +fw=$1 +if(~ $#fw 0) + fw=/bin/fw +if(! test -x $fw){ + echo fwtest: no $fw >[1=2] + exit nofw +} + +tmp=/tmp/fwtest.$pid +mkdir -p $tmp +nA=$tmp/nA +nB=$tmp/nB +mkdir -p $nA $nB +mtpt=$tmp/ctl +mkdir -p $mtpt + +# 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 + +# check <name> <expected> <got> +fn check { + if(~ $2 $3){ + echo ok >> $res + echo ' ok ' $1 + } + if not { + echo FAIL >> $res + echo ' FAIL ' $1 + echo ' want: '$2 + echo ' got: '$3 + } +} + +# 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. +fn wr { + if(@{ echo -n $2 > $1 } >[2]/dev/null) + echo ok + if not + echo refused +} + +fn rd { + if(@{ cat $1 >/dev/null } >[2]/dev/null) + echo ok + if not + echo refused +} + +echo '== rules: parsing' + +cat > $tmp/bad.ndb <<'!' +allow=out prot=tcp +! +r=`{$fw $tmp/bad.ndb >[2=1] | sed 's/.*: //' | sed 1q} +check 'a mistyped attribute is fatal' 'unknown attribute' $"r + +cat > $tmp/empty.ndb <<'!' +# nothing +! + +echo '== namespace mode' +@{ + rfork n + $fw $tmp/empty.ndb >[2]/dev/null + + r=`{wr /net/tcp/clone 'connect 10.0.0.1!80'} + check 'an empty rule set denies connect' refused $"r + + r=`{wr /net/udp/clone 'headers'} + check 'headers is refused (it sends without connect)' refused $"r + + r=`{wr /net/gre/clone 'raw'} + check 'gre raw is refused' refused $"r + + r=`{wr /net/tcp/clone 'ttl 32'} + check 'ttl is allowed' ok $"r + + r=`{wr /net/ndb 'x'} + check '/net/ndb is not writable' refused $"r + + r=`{wr /net/log 'tcp'} + check '/net/log is not writable' refused $"r + + r=`{rd /net/ipifc/0/data} + check 'an interface data file is not readable' refused $"r + r=`{rd /net/ipifc/0/status} + check 'but its status still is' ok $"r +} + +cat > $tmp/lport.ndb <<'!' +allow=in proto=tcp lport=17099 +! +@{ + rfork n + $fw $tmp/lport.ndb >[2]/dev/null + r=`{wr /net/tcp/clone 'announce 17099'} + check 'announce matches lport, not port' ok $"r + r=`{wr /net/tcp/clone 'announce 17098'} + check 'a different port is denied' refused $"r +} + +echo '== rules: round-trip through ctl' +cat > $tmp/ip.ndb <<'!' +allow=out proto=tcp ip=10.9.0.0/24 port=80 +deny=* log=yes +! +@{ + rfork n + bind -a '#I20' $nA + bind -a '#I21' $nB + $fw -m $mtpt $tmp/ip.ndb $nA^'!'^10.9.9.1^'!'^/24 $nB^'!'^10.9.9.2^'!'^/24 >[2]/dev/null & + sleep 3 + + r=`{grep -c '%M' $mtpt/rules} + check 'a mask prints as a mask, not %M%' 0 $"r + + r=`{wr $mtpt/ctl 'append deny=out proto=udp'} + check 'a rule set with ip= survives a ctl edit' ok $"r + + r=`{grep -c . $mtpt/rules} + check 'the appended rule is there' 3 $"r + + # two writes, one open: the fragmented case + @{ echo 'allow=out proto=tcp port=80' + echo 'deny=* log=yes' } > $mtpt/rules + sleep 1 + r=`{grep -c . $mtpt/rules} + check 'a rule set written in two writes is not truncated' 2 $"r + + r=`{wr $mtpt/ctl 'reload '^$tmp/bad.ndb} + check 'reloading a bad file is refused' refused $"r + r=`{grep -c . $mtpt/rules} + check 'and leaves the old rules alone' 2 $"r + + r=`{wr $mtpt/ctl 'delete 0'} + check 'delete 0 is refused' refused $"r +} + +echo '== packets, between two stacks' +cat > $tmp/wire.ndb <<'!' +allow=in proto=tcp lport=17099 +! +@{ + rfork n + 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 & + sleep 3 + + 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 & + sleep 3 + @{ echo -n 'connect 10.9.9.2!17099'; sleep 20 } > $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} + 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 + sleep 1 + r=`{grep -c 17099 $mtpt/flows} + check 'blocking a port drops the live connection' 0 $"r +} + +echo +npass=`{grep -c '^ok' $res} +nfail=`{grep -c '^FAIL' $res} +echo $"npass' passed, '$"nfail' failed' +rm -rf $tmp +if(! ~ $"nfail 0) + exit failed +exit '' |
