diff options
Diffstat (limited to 'fw')
| -rw-r--r-- | fw/doc/design.md | 9 | ||||
| -rw-r--r-- | fw/doc/todo.md | 29 | ||||
| -rw-r--r-- | fw/lib/svc/fw.ether0 | 27 | ||||
| -rw-r--r-- | fw/man/fw | 66 | ||||
| -rw-r--r-- | fw/src/fw.c | 19 | ||||
| -rwxr-xr-x | fw/test/fwtest.rc | 34 |
6 files changed, 173 insertions, 11 deletions
diff --git a/fw/doc/design.md b/fw/doc/design.md index c14787c..1060ba6 100644 --- a/fw/doc/design.md +++ b/fw/doc/design.md @@ -143,6 +143,15 @@ 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 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 +servers speaking 9P on their own standard input, which is a different +thing altogether. So a supervisor watches the `/srv` name and not the +pid, which `svc` already provides for. The name is only worth watching +if it is honest, which is why anything that stops one part of `fw` now +stops all of it. + **Per-rule logging to `/sys/log/fw`.** Global logging either floods a disk or tells you nothing. `syslog(2)` does not create its file, so `fw` says so at startup rather than dropping the lines silently. diff --git a/fw/doc/todo.md b/fw/doc/todo.md index de5eb8e..d101989 100644 --- a/fw/doc/todo.md +++ b/fw/doc/todo.md @@ -4,7 +4,7 @@ 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 — 72 of +`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. ## Fixed, first round @@ -84,6 +84,16 @@ 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` + 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 + mode. What was true underneath it: the control filesystem going + away ended the server proc and left the relays filtering, so the + `/srv` name could be gone while the firewall was still running. + Now any part stopping stops all of them, which is what makes the + name worth watching. `fwstart` is still the wrong shape; a service + file per card is in `lib/svc`. ## Still open @@ -98,19 +108,18 @@ 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 also blocks `svc` supervision. +does not own it. -### 2. fw daemonizes, so svc cannot supervise 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. -Needs a foreground mode; `ready=srv:name` fits, since `-s` already -posts to `/srv`. `fwstart` is the wrong shape and should probably go — -one service per card is the right one. - -### 3. One card per fw, and rules cannot name a card +### 2. One card per fw, and rules cannot name a card Wants repeatable `-e` and an `ifc=` attribute, together. -### 4. Tflush is not implemented +### 3. 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 @@ -118,7 +127,7 @@ 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. -### 5. Positional `delete n` renumbers +### 4. Positional `delete n` renumbers Inherent to positional deletion; iptables has it too. Said out loud here rather than fixed. diff --git a/fw/lib/svc/fw.ether0 b/fw/lib/svc/fw.ether0 new file mode 100644 index 0000000..93de412 --- /dev/null +++ b/fw/lib/svc/fw.ether0 @@ -0,0 +1,27 @@ +# +# One firewall, one card. Copy this per card that should be filtered: +# the name, the srv name and the -e argument change together. +# +# fw daemonizes, as Plan 9 file servers do, so what init watches is the +# /srv name and not the pid. The name is there exactly while the +# firewall is filtering: it appears when the server posts it and goes +# when the last of fw exits, whether that is the wire failing, the +# control filesystem going away, or a note. +# +# It has to start after the card has an address, because that is where +# fw reads the address, mask and gateway from - and before anything +# dials, because a connection made before fw is up was never filtered. +# +svc=fw.ether0 + exec=/bin/fw + args=-s + args=fw.ether0 + args=-m + args=/mnt/fw/ether0 + args=-e + args=/net/ether0 + args=/lib/fw/host.ndb + needs=ipconfig + ready=srv:fw.ether0 + restart=always + enable=no @@ -348,6 +348,67 @@ everything else instead. .PP .B /lib/fw/fw.rc does this in the right order: private namespace, mount, drop, exec. +.SH SUPERVISION +.I Fw +posts its filesystem on +.BI /srv/ name +with +.BR -s , +forks the server, and the process you started exits - the ordinary +Plan 9 file server shape. +A supervisor must therefore watch the +.B /srv +name rather than the pid. +Under +.IR init (8) +that is +.BR ready=srv: : +.IP +.EX +svc=fw.ether0 + exec=/bin/fw + args=-s + args=fw.ether0 + args=-m + args=/mnt/fw/ether0 + args=-e + args=/net/ether0 + args=/lib/fw/host.ndb + needs=ipconfig + ready=srv:fw.ether0 + restart=always + enable=yes +.EE +.PP +The name means what it says: it appears when the server posts it and +goes when the last of +.I fw +exits. +Anything that stops one part stops all of them - a wire that fails, a +control filesystem that is unmounted, a note - so a +.B /srv +name that is present is a firewall that is filtering. +.PP +There is no foreground mode and none is needed. +What such a mode would buy is a pid to watch, and the +.B /srv +name is the better signal: it survives the process that made it and +answers the question that matters. +.PP +Restarting is not free. +.B Restart=always +will bring +.I fw +back, but taking a card is destructive and is not undone, so the card +must be configured again before the new +.I fw +can read an address off it \- see +.B BUGS +and +.IR ipconfig (8). +A copy of this file per card is the shape to use; one +.I fw +per card is the only shape there is. .SH BOOT .B /lib/fw/fwstart starts a firewall for each card named in @@ -442,6 +503,10 @@ starts one firewall per card at boot .B /lib/ndb/fw which cards are filtered, and with what .TP +.B /lib/svc/fw.ether0 +an example service file for +.IR init (8) +.TP .B /mnt/fw control files .TP @@ -459,6 +524,7 @@ are recorded .IR dial (2), .IR syslog (2), .IR fork (2), +.IR init (8), .IR ipconfig (8) .SH BUGS There is no address translation. diff --git a/fw/src/fw.c b/fw/src/fw.c index 6ae38df..bb97960 100644 --- a/fw/src/fw.c +++ b/fw/src/fw.c @@ -1022,11 +1022,30 @@ Done: respond(r, nil); } +/* + * The control filesystem going away takes the firewall with it. + * + * Without this the server proc ends - its mount gone, its /srv name + * removed - and the relays carry on filtering with nothing left to + * talk to them: no ctl, no rules, no flows, and no way for a + * supervisor to see that anything happened. Measured: three procs + * become two and the traffic keeps flowing. A firewall nobody can + * reach or stop is not a state worth staying in, and it is the same + * answer the relays already give when their wire fails. + */ +static void +endsrv(Srv*) +{ + syslog(0, "fw", "control filesystem gone; stopping"); + threadexitsall("ctl"); +} + static Srv fs = { .read= fsread, .write= fswrite, .destroyfid= fsdestroyfid, +.end= endsrv, }; /* a rule change must not leave traffic running that the rules now forbid */ diff --git a/fw/test/fwtest.rc b/fw/test/fwtest.rc index 0325bfd..3f80242 100755 --- a/fw/test/fwtest.rc +++ b/fw/test/fwtest.rc @@ -46,7 +46,7 @@ mkdir -p $mtpt # The stacks the packet checks build for themselves. Fixed numbers are # fine because they are unbound again at the end of each block, and the # last check proves it. -stacks=(20 21 22 23 24 25 26) +stacks=(20 21 22 23 24 25 26 27 28) # A port of our own. Conversations outlive the run that made them - a # devip Conv is never freed - so a fixed port makes one run's leftovers @@ -551,6 +551,38 @@ echo '== a card' } } +echo '== supervision' +# fw daemonizes, like every other Plan 9 file server: the process you +# exec posts to /srv, forks the server and exits. A supervisor watches +# the /srv name instead of the pid, so the name has to mean what it +# says - present exactly while the firewall is filtering. +@{ + rfork n + mkdir -p $tmp/nD $tmp/nE $tmp/sctl + bind -a '#I27' $tmp/nD + bind -a '#I28' $tmp/nE + $fw -s fw.test.$pid -m $tmp/sctl $tmp/empty.ndb \ + $tmp/nD^'!'^10.9.9.1^'!'^/24 $tmp/nE^'!'^10.9.9.2^'!'^/24 >[2]/dev/null + check 'the process that starts it exits, as a file server does' '' $"status + sleep 2 + + r=`{if(test -e /srv/fw.test.^$pid) echo yes; if not echo no} + check 'and leaves its name in /srv' yes $"r + r=`{ls $tmp/sctl | wc -l} + check 'with the control files mounted' 4 $"r + + # what a supervisor would restart on. Taking the control + # filesystem away used to end the server proc and leave the relays + # filtering: a firewall nobody could reach, stop, or notice. + unmount $tmp/sctl + rm /srv/fw.test.$pid + sleep 3 + r=`{ps | awk '$NF == "fw"' | wc -l} + check 'losing the control filesystem stops the whole firewall' 0 $"r + r=`{sed 1q $tmp/nD/ipifc/0/status | awk '{print $2}'} + check 'and the interface it made goes with it' maxtu $"r +} + echo '== fwstart' if(test -f $lib/fwstart) @{ |
