# 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 >[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