From 0f922552ad8cc73c0c3c3674d484c3d78dd8c557 Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Tue, 18 Aug 2026 17:01:49 -0400 Subject: fw: a firewall, at a card, between two networks, or in front of a namespace One program with three modes, sharing one rule engine and one ndb rule language. Which mode it is depends on what you point it at, and it says so at startup rather than choosing silently. fw -e /net/ether0 rules.ndb a card: every packet in or out fw rules.ndb two networks: everything crossing fw rules.ndb one namespace: what programs ask for The first two filter packets on a wire, using the pkt medium: the stack gives up its card and gets a synthetic one with fw on the other end, so nothing reaches it that fw did not pass. Since the stack no longer has ethernet, fw answers ARP for the address it stands in for. The third serves a filtered /net and matches connect and announce before they reach the kernel, so a refusal comes back out of dial(2) with a reason. That is only a boundary if the program also loses #I, which /dev/drivers does and cannot be undone; fw.rc does it in the right order. Rules are ndb, matched top to bottom, first match wins, no match denies. Connections are tracked, so permitting traffic one way permits the replies. A rule change drops connections the new rules forbid rather than letting them finish: a block blocks. Logging is per rule, to /sys/log/fw. Tested on the init-test VM in all three modes: a page fetched through a real card, a TCP handshake across two networks, request filtering with the escape routes closed, live rule changes killing established connections, and one rule file working unchanged at both altitudes. doc/todo.md has what is not done. Item 1 is the one that matters: a fw that dies takes the card's address with it, so the machine loses its network and fw cannot restart unaided. That also blocks svc supervision. Co-Authored-By: Claude Opus 5 --- fw/lib/example.ndb | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 fw/lib/example.ndb (limited to 'fw/lib/example.ndb') diff --git a/fw/lib/example.ndb b/fw/lib/example.ndb new file mode 100644 index 0000000..60f5119 --- /dev/null +++ b/fw/lib/example.ndb @@ -0,0 +1,42 @@ +# +# every attribute fw understands. an attribute that is absent does +# not constrain, so there is never a "*" to write; it is accepted, but +# leaving the attribute out says the same thing more quietly. +# +# allow= the rule permits. dir is in, out or * +# deny= the rule refuses +# proto= a protocol: tcp, udp, icmp, ... +# port= the port at the far end +# lport= the port at this end +# ip= the address at the far end, optionally with a /mask +# ipmask= the mask, if you would rather write it separately +# +# in and out are directions, not layers: the same rule means the same +# thing whether fw is matching it against a packet on a wire or against +# a connection a program asked for. connect and announce are accepted +# as older spellings of out and in. +# +# rules are matched top to bottom and the first one that matches wins. +# nothing matching means deny, so a file with no rules permits nothing. +# + +# deny before allow, since the first match wins +deny=out ip=1.1.1.1 +deny=out ip=8.8.8.8 + +# the two spellings of a masked address are the same rule +allow=out ip=10.0.2.0/24 +allow=out ip=192.168.0.0 ipmask=/16 + +# v6 needs no distinguishing: it is the same attribute +allow=out ip=2001:db8::/32 + +# an entry may also be spread over indented continuation lines +allow=out + proto=tcp + port=443 + +# answer on one port, but never call out on it. lport is our end, so +# this is the port we serve; port would be the caller's, which we do not +# get to know until they call. +allow=in proto=tcp lport=17019 -- cgit v1.2.3