Connections, "every packet" rules, and a packet tracer

- Connections: the kernel's tracked connections, read and closed over
  ctnetlink (internal/conntrack, raw netlink like the drop log), with
  their NAT and TCP state; closing one makes the rules check its next
  packet again (GET /connections, POST /connections/close; hc
  connections; Traffic → Connections).
- Rules can check every packet (every_packet, --every-packet): before
  established connections are accepted, so a drop or reject also stops
  connections already open. They can't accept.
- A packet tracer (internal/trace): a table of its own, inet
  housecarl-trace, marks the packets a filter picks, `nft monitor trace`
  reports every rule and policy they meet in every table, and the tracer
  names what accepted or dropped them, Housecarl's rules by name (POST,
  GET, DELETE /trace; hc trace; Traffic → Trace). One trace at a time,
  at most 200 packets and 20 a second; the table goes when it ends.

e2e: 157 checks, among them an upload a normal block doesn't stop and an
every-packet one does, and a trace naming the blocking rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-19 20:22:34 +01:00
1 parent 98e11a8bec
commit a45c893830
34 files changed
+1830 -41

No files matched your search

+13 -3
View File
@@ -141,9 +141,13 @@ type Rule struct {
// GroupV4 and GroupV6 group addresses for the per-address limits,
// connection limit and quota: by prefix length (0: the default, each
// IPv4 address alone, IPv6 by /64, since one device has several).
GroupV4 int `json:"group_v4,omitempty"`
GroupV6 int `json:"group_v6,omitempty"`
Action string `json:"action"`
GroupV4 int `json:"group_v4,omitempty"`
GroupV6 int `json:"group_v6,omitempty"`
// EveryPacket checks the rule against every packet, before established
// connections are accepted, so a drop or reject also stops connections
// already open, not just new ones.
EveryPacket bool `json:"every_packet,omitempty"`
Action string `json:"action"`
// RejectWith: "" (ICMP port/admin unreachable as fits) or tcp-reset.
RejectWith string `json:"reject_with,omitempty"`
Log bool `json:"log,omitempty"`
@@ -252,6 +256,12 @@ func (r *Rule) CountsTraffic() bool {
return bytes
}
// ChecksEveryPacket reports whether a rule is checked before the built-in
// that accepts established connections: it counts traffic, or asks to.
func (r *Rule) ChecksEveryPacket() bool {
return r.Raw == "" && (r.EveryPacket || r.CountsTraffic())
}
// Groups returns a rule's address grouping, defaults applied.
func (r *Rule) Groups() (v4, v6 int) {
v4, v6 = r.GroupV4, r.GroupV6