summaryrefslogtreecommitdiff
path: root/fw/src/ether.c
AgeCommit message (Collapse)Author
4 daysether: a frame addressed to somebody else is not oursCalvin Morrison
fw asks the card for promiscuous mode and then hands the protected stack every IP frame that arrives on it, whoever it was for. On a switched network that is mostly nothing; on anything else it is the neighbours' traffic, judged against the rules, counted in stats, and entered in the flow table as conversations that were never ours. A flow created that way outlives the packet that made it and will let traffic past that no rule was asked about. Promiscuous is still needed. The stack behind fw joins multicast groups on a pkt interface, which has no way to tell a card about them, so without it the groups would never be received at all. What it costs is the filter ethermux would otherwise have applied: if(!tome && !multi && !f->prom) continue; Frames addressed to this card, plus broadcast and multicast, would have arrived for nothing. So etherin puts that test back itself: the destination is ours, or it is a group address, or the frame is not ours to look at. No test. This is on the wire side of card mode, and the suite can take a spare card but cannot make a neighbour send to it. Reproducing it needs a second machine on the same segment, which is on the list of things never tested and stays there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 daysether: keep the frame buffer off the proc stack, and log the v6 dropCalvin Morrison
Maxframe was Ehdrlen + 16K, an automatic in etherwriteip, which runs on a proc created with a 32K stack. It fits, and the previous fix was right that 64K did not, but half a stack for a buffer that -- since the pkt interface is now told "mtu 1500" -- can never hold more than 1514 bytes is a number waiting to be wrong again, and libthread allocates that stack with malloc, so being wrong means quietly corrupting the heap rather than faulting. So the buffer belongs to the caller, with its size, and the guard is against that size. etherout allocates it once, from the same Maxpkt it sizes its read buffer with, which is the only place that knows how much can arrive. ether.c no longer has a length of its own to drift. The IPv6 message now also goes to syslog. fw daemonizes, so a message on file descriptor 2 goes wherever the shell that started it was pointing, which for a firewall started at boot is nowhere. No test. etherwriteip is reached only in card mode, which the suite stays out of on purpose because a test that can leave the machine with no network is a test nobody runs. A unit harness for ether.c -- point efd at a pipe, call etherwriteip, read the frame back -- would cover this and the broadcast mapping that todo.md still records as written but never observed. Worth doing; not done here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 daysfw: fix a bypass, a broken round-trip, and eleven others from reviewCalvin Morrison
The serious one is that the ctl filter was a blacklist. checkctl looked at connect and announce and passed everything else, but udpctl takes "headers", and udpcreate gives a conversation a live write queue at clone time: c->wq = qbypass(udpkick, c); So three writes -- clone, "headers", a header-prefixed datagram to data -- sent a packet anywhere, with no connect for a rule to match. rudp and icmpv6 have the same verb, gre has raw and forward. none.ndb did not mean "no network at all", though the manual said it did. It is now a whitelist of control messages that cannot reach the network by themselves, which is the argument this code already made about ndb attributes it does not recognise, applied where it was not. Also blocking: %M was never installed, so fmtrules emitted ipmask=%M% and every ctl edit on a rule set containing ip= failed, while save wrote a file reload would reject. Tests had exercised the ctl path and the ip= path but never together. parserules built the new list in the globals with no lock, so for the length of a reload the relay procs walked a list that was empty and then half built -- exactly what installrules' comment promised could not happen. etherwriteip put a 64KB frame on a 32KB proc stack, the same bug design.md records learning and fixing in relay(). putback and notehandler were dead code: atexit matches on the registering pid and _exits never runs the handlers, which is why the cleanup "did not fire" rather than being flaky. Nothing puts the card back, and the docs that said otherwise are corrected. The rest: expired flows kept matching and refreshing themselves; the pkt interface claimed a 4096 MTU from a 1514-byte card; ports were read out of non-first fragments; /net/ndb and /net/log were writable and ipifc/*/data readable through the filter; control files were world-writable, and owning them as a user called "fw" locked out the administrator instead; delete 0 appended a rule reading <nil>; a dead relay left one direction unfiltered with nothing to notice; and IPv6 unicast under -e was dropped in silence when it is simply not implemented. All three modes regression tested after: a namespace refusing headers and port 22 while allowing 443, a card passing https and then blocking it live, and the machine's network restored afterwards. doc/todo.md says which of these were reproduced and which were read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 daysfw: a firewall, at a card, between two networks, or in front of a namespaceCalvin Morrison
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 <side> <side> 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 <noreply@anthropic.com>