summaryrefslogtreecommitdiff
path: root/fw/test/wire.md
blob: b4494f3226a560d2c5234be4565f8891c0451cde (plain)
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
# Testing fw on a wire

`fwtest.rc` runs on one machine and can go no further than the card: it
can take a spare one and watch what fw does to the interfaces, but it
cannot make a neighbour send anything. Everything past that — ARP
against something that will answer, real traffic being filtered, frames
addressed to somebody else — needs a second machine on the same
segment.

`run.sh` will build one. `-gw` listens, `-lan` connects, and qemu joins
the two into a point-to-point ethernet segment the host is not on. The
LAN card is `ether1` on both.

    ./newvm.sh peer
    ./run.sh -headless -gw  init-test.qcow2 &
    ./run.sh -headless -lan peer.qcow2 &

The peer can be a plain overlay of the base: it needs no fw, only an
address. Both cards must have distinct ethernet addresses, which
`run.sh` derives from the VM name — before it did that, every guest had
qemu's default and two machines on one segment shared a MAC, which is
invisible point-to-point and makes every check below meaningless.

## The rig

On the firewall machine, a stack of its own so the real network is
untouched, and fw on the spare card:

    bind -a '#l1' /net
    bind -a '#I63' /tmp/nW
    @{ conv=`{cat /fd/0}
       echo -n 'bind ether /net/ether1' >[1=0]
       echo -n 'add 10.9.9.1 255.255.255.0' >[1=0] } <>[0] /tmp/nW/ipifc/clone
    echo 'allow=in	proto=icmp' > /tmp/r.ndb
    fw -s fw.wire -n /tmp/nW -e /net/ether1 -m /tmp/ctl /tmp/r.ndb \
        </dev/null >/dev/null >[2]/dev/null

`-s` matters: fw's control files are otherwise only reachable through
the mount in the namespace that started it, and that namespace is gone
by the time you want to look. With a `/srv` name any shell can
`mount /srv/fw.wire /tmp/wctl`.

Redirect all three descriptors. fw daemonizes, so anything that leaves
it holding a pipe — `fw ... | grep`, or a command substitution around
it — waits for a firewall that is not going to exit.

On the peer, the other end of the segment:

    bind -a '#l1' /net
    bind -a '#I64' /tmp/np
    @{ conv=`{cat /fd/0}
       echo -n 'bind ether /net/ether1' >[1=0]
       echo -n 'add 10.9.9.2 255.255.255.0' >[1=0] } <>[0] /tmp/np/ipifc/clone
    rfork n; bind /tmp/np /net

## What it shows

**ARP for an address whose card has been taken.** Nothing else is
answering for 10.9.9.1 — the stack that owns it has a `pkt` interface
and no ethernet — so if `ip/ping 10.9.9.1` works at all, fw answered.
The peer's `/net/arp` says whose:

    ether  OK   10.9.9.1     52540087c8c1     10.9.9.2

which is the firewall's *card*, not the peer's own. The first ping
takes about a second and the rest are sub-millisecond: fw has to ARP
for the peer before it can send the reply, and drops the first one
while it asks. That is the one drop no rule caused, in fw(8) BUGS.

**Filtering, as against forwarding.** With `allow=in proto=icmp` alone,
a TCP connect from the peer sits for 290 seconds and gives up:

    connection timed out

Add the rule through the running firewall and the same connect answers
in 2 seconds:

    echo -n 'prepend allow=in proto=tcp lport=17019' > /tmp/wctl/ctl
    connection refused

"Refused" is the far stack's RST, so the packet arrived; "timed out" is
fw dropping it silently. That difference is the whole point of the
program, and it is the only way to see it from outside.

**Frames addressed to somebody else.** A stack only ever addresses a
frame to the MAC it resolved, so this one has to be forged:
`rawether.c`, in this directory, writes a single frame with whatever
destination you name.

    rawether /net/ether1 52540087c8c1 10.9.9.2 10.9.9.1   # fw's card
    rawether /net/ether1 525400aabbcc 10.9.9.2 10.9.9.1   # nobody's

Watch `passed` in `/tmp/wctl/stats` across each. The card is
promiscuous and has to be, so both frames arrive — a promiscuous reader
on the firewall machine sees both, which is worth confirming first, or
a frame that never arrived looks exactly like one that was filtered:

	52 54 00 aa bb cc 52 54 00 37 df 23 08 00 45 00

With the destination check in `etherin`, the second frame moves nothing.
Without it, both move the counter by the same amount:

	                 to fw's card   to nobody's
	no check              +3            +3
	with the check        +2             0