Watch
0
0
Fork
You've already forked hyperhive
0

egress anti-spoofing: keep established bindings, load tables with bindings and leases in one transaction

This commit is contained in:
atlas 2026-11-13 02:53:37 +01:00 • committed by mara
commit 70179b6c1f

View file

@ -648,23 +648,31 @@ source check lives in that table and the host-side filtering stays in
bound a moment after its veth appears, and the container's DHCP client
retries until then.
- **Where the bindings come from.** Addresses are dynamic, so the table's sets
the commands below fill them at run time. `hive-priv egress-pin` binds a port to the MAC of the
link at the other end of its veth pair, found through the container systemd
registers for it. udev starts it for each new `vb-*` port
(`hive-egress-pin@<port>.service`), and the `hive-egress` unit starts it for
every running container on each start and reload. A binding stays with the
port until the port goes: root in the container can't move the port to
another MAC afterwards (state in `/run/hive-egress-pins`). If two ports have
the same MAC, neither gets a binding. dnsmasq's `dhcp-script` (`hive-priv
egress-lease`) adds each lease as a (MAC, address) pair that times out with
the lease.
fill at run time. `hive-priv egress-pin` binds a port to the MAC of the link at
the other end of its veth pair, found through the container systemd registers
for it. udev starts it for each new `vb-*` port
(`hive-egress-pin@<port>.service`), and a timer (`hive-egress-pin.timer`, every
30 seconds) runs it for the ports that still have no binding. The timer reads
no container while every port has one. The `hive-egress` unit loads the tables
with `hive-priv egress-load`: it looks up the bindings first, while the old
tables still filter, and puts them and the leases from dnsmasq's lease file
into the same nft transaction as the tables. A start or reload never leaves
the bridge with empty sets. If loading the leases fails, the unit logs a
warning and loads the tables with the bindings alone. A binding stays with the
port until the port goes: root in the container can't move the port to another
MAC afterwards (state in `/run/hive-egress-pins`). A port never takes a MAC
that a binding already holds, so a newcomer can't evict an established
container. If two new ports claim one MAC, neither gets a binding. dnsmasq's
`dhcp-script` (`hive-priv egress-lease`) adds each lease as a (MAC, address)
pair that times out with the lease.
- **The window.** The binding takes the MAC the container's link has when the
command first looks, normally within a second of the veth appearing. Until
then root in the container could change its own MAC, but in that time only
the container's early init runs, which is nix-built, and the agent's code
starts later. A container that did change its MAC first would get its own
lease for the new MAC, not another container's: a MAC that two ports have
gets no binding, and a lease follows the `chaddr` the port may send.
starts later. A container that did change its MAC first gets a binding for
that MAC only if nobody holds it; a lease follows the `chaddr` the port may
send. A port refused because it claimed a held MAC sends nothing, and the
timer binds it once its link has a MAC of its own.
- **The log.** A refused frame (all of them with `drop`, the same ones with
`log`) gets a kernel log line with the prefix `hive-egress-spoof `, rate
limited per port: a burst of 20, then one a second. A frame its port can't log