The boot unit fails closed: if ruleset.nft doesn't load, it loads ruleset-fallback.nft (render.Fallback), saved beside it from the same configuration: nothing new in from external zones, established, the ICMPv6 IPv6 needs and DHCPv6 replies let in, the inside left alone and masqueraded, and the unit marked failed with a critical message. housecarld replaces it when it applies; status, the dashboard and /healthz say while it is in force. no_external_zone now also says the fallback closes everything without an external zone. The boot ruleset has no flowtable: nftables on Debian 13's kernel refuses one naming a device that doesn't exist yet (ppp0 before PPPoE), and with it the whole file. internal/linkwatch listens to rtnetlink link messages; housecarld applies again when a flowtable device appears or comes back as a new interface (by ifindex), under the engine's lock. docs/flowtable.md: what it does, when to use it and when not, how. GET /healthz reports firewall (applied, unconfigured, fallback, missing), changes on probation and the machine's warnings, with 503 unless in force, read at most every five seconds. Renaming a zone, set or service changes what names it in the same change (one history entry, one undo); dynamic sets keep their names. The importer keeps a chain's head in order: once a would-be built-in stays a rule, the ones after it do too, so the maintainer's ICMPv6 accepts stay behind the invalid drop as written. hc settings set --flowtable none empties the list. make test (nft 1.1.3) and make e2e (242 checks, with VLAN sub-interfaces, the fallback and the flowtable) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1003 B
Go
37 lines
1003 B
Go
package linkwatch
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// msg is a netlink message of a type, with a payload of n bytes.
|
|
func msg(typ uint16, n int) []byte {
|
|
b := make([]byte, unix.NLMSG_HDRLEN+n)
|
|
binary.NativeEndian.PutUint32(b[0:4], uint32(len(b)))
|
|
binary.NativeEndian.PutUint16(b[4:6], typ)
|
|
return b
|
|
}
|
|
|
|
func TestLinks(t *testing.T) {
|
|
for _, c := range []struct {
|
|
name string
|
|
b []byte
|
|
want bool
|
|
}{
|
|
{"a new link", msg(unix.RTM_NEWLINK, 16), true},
|
|
{"a link going", msg(unix.RTM_DELLINK, 16), true},
|
|
{"an address", msg(unix.RTM_NEWADDR, 16), false},
|
|
{"a link after an address (padded)", append(msg(unix.RTM_NEWADDR, 13), append(make([]byte, 3), msg(unix.RTM_NEWLINK, 8)...)...), true},
|
|
{"truncated", msg(unix.RTM_NEWLINK, 16)[:10], false},
|
|
{"a length beyond the datagram", msg(unix.RTM_NEWLINK, 16)[:unix.NLMSG_HDRLEN+4], false},
|
|
{"empty", nil, false},
|
|
} {
|
|
if got := links(c.b); got != c.want {
|
|
t.Errorf("%s: %v", c.name, got)
|
|
}
|
|
}
|
|
}
|