summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xfw/test/fwtest.rc200
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 ''