Support nftables and iptables #15

Open
opened 2024-07-16 06:17:48 +01:00 by danny · 4 comments
danny commented 2024-07-16 06:17:48 +01:00 (Migrated from gitlab.keyop.co.uk)

Support whichever is available, preferring nftables

Support whichever is available, preferring nftables
Collaborator

Plan

Today the entire packet-interception/NAT mechanism is hard-wired to iptables via
github.com/coreos/go-iptables and a bundled, capability-enabled private copy of the iptables
binary. This adds an nftables backend alongside it, auto-preferring nftables when available
(with an explicit override), while leaving the well-tested existing iptables path unchanged in
substance.

Verified empirically against a real kernel first (this sandbox has real nft + root via
sudo, cleaned up afterward): confirmed a type filter hook output priority mangle chain
resolves to priority -150 and type nat hook output priority dstnat resolves to -100 -
preserving the exact same hook-ordering guarantee issue #4 depends on (interception strictly
before DNAT, within one packet's OUTPUT-hook traversal). Also confirmed nft -j list gives
clean structured JSON with comment/handle/counter fields, and that deletion is by stable
handle rather than positional index - a genuine improvement over iptables' index-based
deletion (which is why deleteIPTablesTableChainTagRules has to delete in reverse order, and
part of why iptablesMu exists at all).

Design

  • New firewallBackend interface (internal/nfq_forwarder/firewall.go) consolidating the four
    existing test-only indirection vars (initNfqIptablesFunc, deleteNfqIptablesRulesFunc,
    addDNATRulesFunc, deleteDNATRuleForTunnelFunc) into one seam: initQueueRule,
    deleteQueueRules, addDNATRule, deleteDNATRule, dnatCounters.
  • iptablesBackend (firewall_iptables.go): a near-mechanical move of the existing
    initNfqIptables/deleteNfqIptablesRules/addDNATRules/deleteDNATRuleForTunnel/
    refreshStats bodies into methods - same tables (mangle/nat/OUTPUT), same comment-tag
    scheme, same crash-recovery scan of filter too. Nothing about this path changes in substance.
  • nftablesBackend (firewall_nftables.go, new): owns one dedicated inet nfq_forwarder table
    with an output chain (priority mangle, queue num <n> rules) and an output_nat chain
    (priority dstnat, counter ... dnat to ... rules) - shells out to nft -j (JSON), matching
    go-iptables's own CLI-wrapping approach rather than adding a new netlink-level dependency
    (none is cached/already a dependency). Still comment-tags rules per queue/tunnel exactly like
    today, since the table is shared across connections/processes the same way iptables'
    mangle/nat/OUTPUT is.
  • selectFirewallBackend(override string): prefers nftables if nft is on $PATH and a
    harmless nft list tables actually runs without error; falls back to iptables otherwise. New
    firewall_backend: auto|iptables|nftables config field lets --config force a specific
    backend, skipping detection entirely (an invalid override fails loudly, matching how other
    config validation errors behave). Logged once at startup either way.
  • Packaging: both iptables and nft private copies get bundled and setcap'd the same way
    (CAP_DAC_READ_SEARCH,CAP_NET_ADMIN,CAP_NET_RAW - confirmed NET_ADMIN/NET_RAW are the
    genuine netlink capabilities nftables needs too, not iptables-legacy-specific) - runtime
    detection then picks whichever's actually usable on the installed host.
  • Also fixing an adjacent, pre-existing bug found during investigation: cmd/setup/setup.go's
    CLI description text still says the NFQUEUE rule lives in the 'filter' table - stale since
    issue #4 moved it to 'mangle'.

Testing

New firewall_nftables_test.go mirrors the existing iptables tests' philosophy (safe regardless
of CI's privilege level, doc-comment-driven log-output assertions for table/chain/priority
choices). TestIptablesMutatingFunctionsSerializeOnSharedLock becomes table-driven across both
backends. Given this sandbox's real root access, I'll also do genuine real-host verification
(force firewall_backend: nftables, confirm actual rule creation/counters/teardown via
sudo nft -j list table inet nfq_forwarder) before considering this done - not just passing
unit tests.

Branch: issue15_nftables_support.

## Plan Today the entire packet-interception/NAT mechanism is hard-wired to `iptables` via `github.com/coreos/go-iptables` and a bundled, capability-enabled private copy of the `iptables` binary. This adds an `nftables` backend alongside it, auto-preferring nftables when available (with an explicit override), while leaving the well-tested existing iptables path unchanged in substance. **Verified empirically against a real kernel first** (this sandbox has real `nft` + root via `sudo`, cleaned up afterward): confirmed a `type filter hook output priority mangle` chain resolves to priority `-150` and `type nat hook output priority dstnat` resolves to `-100` - preserving the exact same hook-ordering guarantee issue #4 depends on (interception strictly before DNAT, within one packet's `OUTPUT`-hook traversal). Also confirmed `nft -j list` gives clean structured JSON with `comment`/`handle`/counter fields, and that deletion is by stable `handle` rather than positional index - a genuine improvement over iptables' index-based deletion (which is why `deleteIPTablesTableChainTagRules` has to delete in reverse order, and part of why `iptablesMu` exists at all). ### Design - New `firewallBackend` interface (`internal/nfq_forwarder/firewall.go`) consolidating the four existing test-only indirection vars (`initNfqIptablesFunc`, `deleteNfqIptablesRulesFunc`, `addDNATRulesFunc`, `deleteDNATRuleForTunnelFunc`) into one seam: `initQueueRule`, `deleteQueueRules`, `addDNATRule`, `deleteDNATRule`, `dnatCounters`. - `iptablesBackend` (`firewall_iptables.go`): a near-mechanical move of the *existing* `initNfqIptables`/`deleteNfqIptablesRules`/`addDNATRules`/`deleteDNATRuleForTunnel`/ `refreshStats` bodies into methods - same tables (`mangle`/`nat`/`OUTPUT`), same comment-tag scheme, same crash-recovery scan of `filter` too. Nothing about this path changes in substance. - `nftablesBackend` (`firewall_nftables.go`, new): owns one dedicated `inet nfq_forwarder` table with an `output` chain (`priority mangle`, `queue num <n>` rules) and an `output_nat` chain (`priority dstnat`, `counter ... dnat to ...` rules) - shells out to `nft -j` (JSON), matching `go-iptables`'s own CLI-wrapping approach rather than adding a new netlink-level dependency (none is cached/already a dependency). Still comment-tags rules per queue/tunnel exactly like today, since the table is shared across connections/processes the same way iptables' `mangle`/`nat`/`OUTPUT` is. - `selectFirewallBackend(override string)`: prefers nftables if `nft` is on `$PATH` *and* a harmless `nft list tables` actually runs without error; falls back to iptables otherwise. New `firewall_backend: auto|iptables|nftables` config field lets `--config` force a specific backend, skipping detection entirely (an invalid override fails loudly, matching how other config validation errors behave). Logged once at startup either way. - Packaging: both `iptables` and `nft` private copies get bundled and `setcap`'d the same way (`CAP_DAC_READ_SEARCH,CAP_NET_ADMIN,CAP_NET_RAW` - confirmed `NET_ADMIN`/`NET_RAW` are the genuine netlink capabilities nftables needs too, not iptables-legacy-specific) - runtime detection then picks whichever's actually usable on the installed host. - Also fixing an adjacent, pre-existing bug found during investigation: `cmd/setup/setup.go`'s CLI description text still says the NFQUEUE rule lives in the `'filter'` table - stale since issue #4 moved it to `'mangle'`. ### Testing New `firewall_nftables_test.go` mirrors the existing iptables tests' philosophy (safe regardless of CI's privilege level, doc-comment-driven log-output assertions for table/chain/priority choices). `TestIptablesMutatingFunctionsSerializeOnSharedLock` becomes table-driven across both backends. Given this sandbox's real root access, I'll also do genuine real-host verification (force `firewall_backend: nftables`, confirm actual rule creation/counters/teardown via `sudo nft -j list table inet nfq_forwarder`) before considering this done - not just passing unit tests. Branch: `issue15_nftables_support`.
Collaborator

Implemented in PR #92 - see the PR description for the full design summary and real-host verification status. Requested review from @danny.

Implemented in PR #92 - see the PR description for the full design summary and real-host verification status. Requested review from @danny.
Collaborator

Pausing this for now - it's a large one (full firewall-backend abstraction to support both iptables and nftables). I've done the investigation groundwork (mapped every current iptables touchpoint, empirically verified nftables' nft -j JSON output covers everything needed - comments, counters, hook-priority ordering matching mangle/nat - and confirmed the existing capability-grant mechanism generalizes to a bundled nft copy the same way it works for iptables today), but haven't finalized a design yet. Will pick this back up later - marked On Hold in the meantime.

Pausing this for now - it's a large one (full firewall-backend abstraction to support both iptables and nftables). I've done the investigation groundwork (mapped every current iptables touchpoint, empirically verified nftables' `nft -j` JSON output covers everything needed - comments, counters, hook-priority ordering matching mangle/nat - and confirmed the existing capability-grant mechanism generalizes to a bundled `nft` copy the same way it works for `iptables` today), but haven't finalized a design yet. Will pick this back up later - marked On Hold in the meantime.
Collaborator

Correction to my last comment: this is actually already fully implemented, not just investigated. See PR #92 (branch issue15_nftables_support) - a firewallBackend interface with iptablesBackend/nftablesBackend implementations, real end-to-end nftables verification against this sandbox's actual nft, and packaging updates to bundle+setcap both binaries. I lost track of that context and redundantly redid some investigation just now (no code changes, purely read-only - apologies for the noise). Labeling as On Hold per Danny's request; PR #92 remains open awaiting review.

Correction to my last comment: this is actually already fully implemented, not just investigated. See PR #92 (branch `issue15_nftables_support`) - a `firewallBackend` interface with `iptablesBackend`/`nftablesBackend` implementations, real end-to-end nftables verification against this sandbox's actual `nft`, and packaging updates to bundle+setcap both binaries. I lost track of that context and redundantly redid some investigation just now (no code changes, purely read-only - apologies for the noise). Labeling as On Hold per Danny's request; PR #92 remains open awaiting review.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
keyop-go/nfq_forwarder#15
No description provided.