summaryrefslogtreecommitdiff
path: root/fw/doc
diff options
context:
space:
mode:
Diffstat (limited to 'fw/doc')
-rw-r--r--fw/doc/design.md42
-rw-r--r--fw/doc/todo.md53
2 files changed, 69 insertions, 26 deletions
diff --git a/fw/doc/design.md b/fw/doc/design.md
index 1060ba6..98eca29 100644
--- a/fw/doc/design.md
+++ b/fw/doc/design.md
@@ -3,8 +3,11 @@
**A firewall for 9front. One program, filtering at a network card, between
two networks, or in front of one namespace.**
-Status: working and tested on the init-test VM, in all three modes. Not
-yet fit to run on a machine you care about — see `todo.md`.
+Status: working on the init-test VM in all three modes, with 87 checks
+in `test/fwtest.rc` covering every bug that has shipped. What is left
+open is in `todo.md`, and none of it is a reason not to run this any
+more; what has never been tested is the wire — a second machine on the
+same segment, and IPv6 anywhere.
## Why
@@ -143,6 +146,41 @@ forbid are dropped. pf and iptables leave them running until they time
out; that is a wart everyone has to learn, and "I blocked it, why is the
transfer still going" is the wrong thing to discover at 3am.
+**A dead firewall takes the network with it.** There are two things
+that can happen when a firewall dies: the traffic it was filtering
+flows unfiltered, or it stops. Only the second is defensible. A machine
+that is briefly off the network is a machine an operator notices and
+fixes; a machine that is briefly on the network with no rules is a
+machine nobody notices, and it is exactly the moment you built the
+firewall for.
+
+`fw` does the second, in all three modes, and it is the mechanism
+rather than anything careful:
+
+ card: pktmedium is unbindonclose, so the interface and the
+ address go when fw's fds close. The card is left
+ bound to nothing and nothing is reading it.
+ gateway: the same, on both sides at once.
+ namespace: the mount is hung up, and #I was dropped from the
+ process group's device mask, so the program cannot
+ bind the real stack back in its place.
+
+Measured, not assumed — a card-mode fw killed leaves `device` with
+nothing after it, no address in `ipselftab` and no default route; a
+sandboxed program gets `i/o on hungup channel` from `/net` and
+`mount/attach disallowed` from `bind -a '#I' /net`.
+
+This is why `putback` was deleted rather than repaired. It meant to
+restore the card to the stack as fw exited, which is precisely the
+first option: the machine back on the network with no firewall on it.
+It never ran, so nothing was ever wrong; had it run, it would have been
+wrong every time.
+
+The cost is that recovery needs an address `fw` no longer knows, and
+that is a real cost, paid deliberately. `-a` and `-g` in the service
+file buy it back: crash, network down, restart, network up and
+filtered, and no moment in between where it is up and unfiltered.
+
**A file server, so it detaches.** `fw` posts to `/srv`, forks the
server and lets the process you started exit — what 42 of the 47 file
servers in `/sys/src/cmd` do, and the five that do not are stdio
diff --git a/fw/doc/todo.md b/fw/doc/todo.md
index d101989..6c39e9b 100644
--- a/fw/doc/todo.md
+++ b/fw/doc/todo.md
@@ -4,8 +4,9 @@ Most of this came from code review. Where a finding has been fixed it
says so; where it has not, it says what is actually true. An item never
appears in both halves.
-`test/fwtest.rc` is a check for every bug that has shipped here — 77 of
-them now. Run it twice in a row after touching anything.
+`test/fwtest.rc` is a check for every bug that has shipped here, and
+for the properties that must not quietly stop being true — 87 of them
+now. Run it twice in a row after touching anything.
## Fixed, first round
@@ -84,7 +85,22 @@ them now. Run it twice in a row after touching anything.
10. **Promiscuous mode had no filter behind it.** The card must be
promiscuous for multicast, but every neighbour's unicast was then
judged, counted and flow-tracked as if it were ours.
-11. **"fw daemonizes, so svc cannot supervise it" was wrong.** `svc`
+11. **"A dead fw takes the network with it" was the requirement, not
+ the bug.** It had been item 1 since the beginning, on the grounds
+ that a dead `fw` leaves the machine with no network. That is the
+ only defensible thing for a firewall to do: the alternative is
+ traffic flowing unfiltered, which is the moment the firewall exists
+ for. All three modes do it, by mechanism rather than by care —
+ `unbindonclose` on the card and the gateway, a hung-up mount plus a
+ dropped `#I` in a namespace — and all three are now measured.
+ `putback`, deleted earlier as dead code, was the fail-open version
+ of this; it never ran, so it was never wrong, but repairing it
+ would have been. What remained was that recovery did not work:
+ `-a` and `-g` are now in the service file so a restart does not
+ need the address it just lost, and `fw` clears the dead mount its
+ own corpse left on the control mountpoint, which was refusing the
+ restart by way of the check meant to protect the card.
+12. **"fw daemonizes, so svc cannot supervise it" was wrong.** `svc`
has had the detaching shape from the start: `ready=srv:` watches
the `/srv` name, and for those services the process exiting "is
normal and never causes a restart". Nothing needed a foreground
@@ -97,29 +113,11 @@ them now. Run it twice in a row after touching anything.
## Still open
-### 1. Nothing puts the card back
-
-Taking a card is destructive and is not undone. The pkt interface is
-`unbindonclose`, so when `fw` stops the interface and the address both
-go, leaving the card bound to nothing and the machine with no network.
-`fw` cannot restart unaided either: the address it would read off the
-card is the address that just vanished.
-
-The cleanup that claimed to handle this was dead code and is gone. The
-answer is not more note handling — it has to be something that outlives
-`fw`, which means the supervisor, with the address stored where `fw`
-does not own it.
-
-This is what makes `restart=always` a half-measure rather than an
-answer: `svc` will start `fw` again, and the new one has no address to
-read off a card that no longer has one. It needs `-a`, or the card
-configured again first. Supervision works; recovery does not.
-
-### 2. One card per fw, and rules cannot name a card
+### 1. One card per fw, and rules cannot name a card
Wants repeatable `-e` and an `ifc=` attribute, together.
-### 3. Tflush is not implemented
+### 2. Tflush is not implemented
A request `fw` is blocked on cannot be abandoned. Only affects
namespace mode, where waiting for an inbound connection is the one
@@ -127,11 +125,18 @@ operation that blocks indefinitely. ~80 lines, with a race that cannot
be fully closed: between the syscall returning and the handler clearing
its entry, a note may land on a worker that has moved on.
-### 4. Positional `delete n` renumbers
+### 3. Positional `delete n` renumbers
Inherent to positional deletion; iptables has it too. Said out loud
here rather than fixed.
+## Decided, and now written down
+
+**A dead `fw` leaves the network down, not open.** See `design.md` and
+FAILURE in `fw(8)`. The tests assert it in card and namespace mode,
+because it is exactly the kind of property a later helpful change would
+reverse without meaning to.
+
## Never tested
- **The wire side of card mode.** The suite can take a spare card — it