1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
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 ''
|