feat(network): add IPv6 (dual-stack) support to firewall and policy/traffic-shaping - #952
Merged
brunodam merged 2 commits intoAug 1, 2026
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the network command family’s nftables management to be dual-stack (IPv4 + IPv6) across both the host firewall (inet host) and workload policy/traffic-shaping classification (inet weaver), including v6-aware set membership routing and ICMPv6/NDP allowances required under a default-drop policy.
Changes:
- Add family-aware CIDR classification and route mixed IPv4/IPv6 inputs into parallel
ipv4_addr/ipv6_addrnft sets. - Update
inet hostrules to include v6 allow/block sets and ICMPv6 Neighbor Discovery/MLD/PMTUD allowances; extend parsing/rendering and goldens accordingly. - Update
inet weaverpolicy rendering, parsing, and manager operations to maintain v4 + v6 companion sets (including compoundip:portreply-stamp sets), plus daemon reconciliation updates for v6 endpoints.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/sanity/sanity.go | Adds CIDRIsIPv6 helper for CIDR family classification. |
| pkg/sanity/sanity_cidr_test.go | Unit tests for CIDRIsIPv6. |
| internal/workflows/steps/step_network_policy.go | Adjusts install-time policy creation call to pass pod CIDR(s) as a slice. |
| internal/templates/files/network/network-host.nft.tmpl | Adds ipv6 sets/rules and ICMPv6 NDP/MLD/PMTUD allowances under default drop. |
| internal/network/policy/validate_test.go | Updates validation expectations to accept IPv6 and mixed-family inputs. |
| internal/network/policy/testdata/network-weaver.golden.nft | Updates golden to include v6 companion sets/rules. |
| internal/network/policy/testdata/network-weaver-dualstack.golden.nft | Adds dual-stack golden fixture. |
| internal/network/policy/render.go | Renders v4+v6 rules (one per pod-CIDR family) and declares v6 companion sets. |
| internal/network/policy/render_weaver.go | Updates RenderWeaverNft to accept variadic pod CIDRs and recover them from existing artifacts. |
| internal/network/policy/render_weaver_test.go | Updates recovery test for plural pod CIDRs. |
| internal/network/policy/policy.go | Removes IPv6 rejection; introduces V6SetName and family-partitioned element routing. |
| internal/network/policy/policy_test.go | Adds -update golden regeneration and dual-stack golden test; updates manager call sites for pod CIDR slice. |
| internal/network/policy/parse.go | Updates pod-CIDR extraction to recover both v4 and v6 CIDRs from rendered nft. |
| internal/network/policy/parse_test.go | Tests dual-stack and v6-only pod-CIDR recovery. |
| internal/network/policy/manager.go | Updates Create/Add/Remove/Set/Show/Delete and snapshot/restore to operate on both v4 and v6 sets. |
| internal/network/policy/manager_ops_test.go | Adds tests ensuring membership routes by family and show surfaces both sets. |
| internal/network/policy/manager_apply_ports_test.go | Updates manager create calls for pod CIDR slice. |
| internal/network/policy/manager_apply_membership_test.go | Updates apply ordering expectations to include v6 set flush/replace per policy. |
| internal/network/policy/diff.go | Extends compound element/canonicalization to support IPv6 and /128 host collapsing. |
| internal/network/policy/diff_test.go | Tests bracketed IPv6 compound element acceptance and canonicalization. |
| internal/network/firewall/testdata/network-host.golden.nft | Updates firewall golden for dual-stack set declarations and v6 rules. |
| internal/network/firewall/table.go | Adds PodCIDR6 and switches CIDR validation to dual-stack ValidateCIDR. |
| internal/network/firewall/render.go | Splits mixed CIDRs by family for v4/v6 set rendering and routes pod CIDRs by family. |
| internal/network/firewall/parse.go | Parses v4/v6 sets and pod CIDRs back into the Table model. |
| internal/network/firewall/firewall_test.go | Adds dual-stack fixture, -update goldens, and dual-stack render assertions. |
| internal/blocknode/shaper/reconciler.go | Brackets IPv6 ip:port tokens via net.JoinHostPort; normalizes bare v6 host to /128. |
| internal/blocknode/shaper/reconciler_test.go | Adds IPv6 bucketization/normalization tests. |
| internal/blocknode/shaper/policy_map.go | Merges live membership from v4 and v6 sets before diffing to avoid perpetual v6 re-apply. |
| internal/blocknode/shaper/policy_map_test.go | Tests merged live-set diff behavior for dual-stack desired membership. |
| docs/quickstart.md | Documents mixed-family flags, dual-stack behavior, and required ICMPv6/NDP allowances. |
| cmd/cli/commands/network/policy/policy.go | Changes --pod-cidr backing variable to []string. |
| cmd/cli/commands/network/policy/policy_test.go | Resets updated flag state for tests. |
| cmd/cli/commands/network/policy/create.go | Updates --pod-cidr to StringSlice, supports dual-stack input, and passes pod CIDR slice to Manager. |
| cmd/cli/commands/network/firewall/firewall.go | Changes firewall --pod-cidr backing variable to []string. |
| cmd/cli/commands/network/firewall/create.go | Routes mixed pod CIDRs into v4/v6 table fields and updates flag to StringSlice. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
alex-au
force-pushed
the
00945-ipv6-dual-stack-firewall-policy
branch
2 times, most recently
from
July 31, 2026 06:25
d88bddb to
4d5ae31
Compare
alex-au
force-pushed
the
refactor-traffic-shaper-engine
branch
from
July 31, 2026 06:35
16a6b4a to
1b2de20
Compare
alex-au
force-pushed
the
00945-ipv6-dual-stack-firewall-policy
branch
from
July 31, 2026 06:38
4d5ae31 to
79985dd
Compare
…ngle-source tc encoding (#955) Signed-off-by: alex-au <alex.w.aus@gmail.com>
…raffic-shaping Both nftables planes the `network` command family manages were IPv4-only. Make them dual-stack, always rendering v4 and v6 rules, with the existing flags accepting mixed v4/v6 lists routed to the matching family's set. Host firewall (inet host): - Add mgmt_addrs6/blocked_addrs6 (ipv6_addr) sets and parallel ip6 rules, plus a Table.PodCIDR6 companion and a v6 in-cluster rule. - Add the ICMPv6 allowlist the default-drop input chain needs to keep IPv6 working: Neighbor Discovery (NS/NA/RS/RA, hop-limit-255 guarded), MLD, and packet-too-big (the v6 PMTUD signal). ICMPv6 Redirect is deliberately not accepted. --pod-cidr now accepts a mixed v4/v6 list. Workload policy / shaping (inet weaver): - Drop the explicit IPv6 rejection; each policy gains a "<name>6" companion set (policy.V6SetName), including the compound ipv6_addr . inet_service reply-stamp set. Membership routes by family across snapshot/restore and the add/remove/set/show verbs. renderStampRule emits one rule per pod family, so v6 classification activates once a v6 --pod-cidr is supplied. - The traffic-shaper daemon reconciler diffs live membership merged from both family sets (otherwise v6 members would re-apply every tick), normalizes a bare v6 peer to /128, and brackets v6 compound endpoints. Validation moves from sanity.ValidateIPv4CIDR to sanity.ValidateCIDR plus a new sanity.CIDRIsIPv6 family classifier. Goldens gain a -update flag and a dual-stack fixture; quickstart documents the mixed-family flags and the ICMPv6/NDP behavior. Closes #945 Signed-off-by: alex-au <alex.w.aus@gmail.com>
brunodam
force-pushed
the
00945-ipv6-dual-stack-firewall-policy
branch
from
July 31, 2026 23:41
79985dd to
6540de8
Compare
alex-au
force-pushed
the
refactor-traffic-shaper-engine
branch
from
July 31, 2026 23:41
5c22714 to
e0ce748
Compare
brunodam
approved these changes
Jul 31, 2026
alex-au
added a commit
that referenced
this pull request
Aug 1, 2026
…raffic-shaping (#952) Signed-off-by: alex-au <alex.w.aus@gmail.com>
brunodam
pushed a commit
that referenced
this pull request
Aug 1, 2026
…raffic-shaping (#952) Signed-off-by: alex-au <alex.w.aus@gmail.com>
alex-au
added a commit
that referenced
this pull request
Aug 1, 2026
…raffic-shaping (#952) Signed-off-by: alex-au <alex.w.aus@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Both nftables planes the
networkcommand family manages were IPv4-only. This makes them dual-stack: the v4 and v6 sets are always rendered, and the existing flags (--mgmt-cidrs,--blocked-cidrs,--pod-cidr,network policy --cidrs) accept mixed v4/v6 lists, routing each entry to the matching family's nft set.tc/HTB is address-family agnostic, so "IPv6 traffic shaping" is delivered by teaching theinet weaverclassification rules to match IPv6 peers — no qdisc change.Design decisions (from #945): both planes, always dual-stack, unified mixed-family flags, full parity.
Stacking note
Based on
refactor-traffic-shaper-engine(PR #949, unmerged) — this PR targets that branch, notmain. Rebase onto #949's merge target once it lands.Host firewall (
inet host)mgmt_addrs6/blocked_addrs6(ipv6_addr) sets + parallelip6rules; newTable.PodCIDR6and a v6 in-cluster rule.nd-neighbor-solicit/advert,nd-router-solicit/advert, hop-limit-255 guarded), MLD, andpacket-too-big(the v6 PMTUD signal). ICMPv6 Redirect is not accepted (on-link MITM).--pod-cidris now a mixed v4/v6 list.Workload policy / shaping (
inet weaver)<name>6companion set (policy.V6SetName), including the compoundipv6_addr . inet_servicereply-stamp set. Membership routes by family across snapshot/restore and the add/remove/set/show verbs.renderStampRuleemits one rule per pod-CIDR family, so v6 classification activates once a v6--pod-cidris supplied (auto-detection resolves only the v4 pod CIDR today)./128, and brackets v6 compound endpoints.Shared
Validation moves from
sanity.ValidateIPv4CIDRtosanity.ValidateCIDR+ a newsanity.CIDRIsIPv6family classifier. Goldens gain a-updateflag and a dual-stack fixture;docs/quickstart.mddocuments the mixed-family flags and the ICMPv6/NDP behavior.Review guide
Code review checklist
internal/templates/files/network/network-host.nft.tmpl— NDP/MLD/packet-too-bigaccepts precedect state established,related accept; NDP carriesip6 hoplimit 255; nond-redirectaccept rule.internal/network/policy/render.go—renderStampRulereturns one rule per present pod family; v6 rules reference@<name>6andip6 saddr/daddr; compound reply-stamp v6 usesip6 daddr . tcp dport @<name>6.internal/network/policy/manager.go— every membership path (snapshotMembership/restoreSet/Add/Remove/applySet/Show) touches both<name>andV6SetName(<name>);applySetfull-replaces each family so a family with no members is cleared, not left stale.internal/blocknode/shaper/policy_map.gocomputePolicyDeltas— live is merged from bothb.policyNameandV6SetName(b.policyName)before diffing.internal/blocknode/shaper/reconciler.go—hostCIDRbare v6 →/128;bucketizeusesnet.JoinHostPort(brackets v6).internal/network/policy/render_weaver.go—nonEmptyStringsfilter so a caller passing""still triggers.nftpod-CIDR recovery.Test commands
Manual UAT (dual-stack host / VM)
Host firewall — mixed allowlist + NDP:
Expect:
mgmt_addrs6/blocked_addrs6sets populated;ip6 saddr @mgmt_addrs6 tcp dport 22 accept; theicmpv6 type { nd-* } ... hoplimit 255 accept, MLD, andpacket-too-bigrules present. SSH over IPv6 from an allowlisted source still connects;ping6and a large-MTU v6 flow both work (PMTUD intact).Policy classification over IPv6:
Expect:
live set @bn-publisherholds the v4 member,@bn-publisher6the v6 member; the chain has bothip daddr 10.244.0.0/16 ip saddr @bn-publisher ...andip6 daddr 2001:db8:c0de::/64 ip6 saddr @bn-publisher6 ... meta priority set 0x10010 accept. A v6 peer's traffic lands in the same HTB class as the v4 equivalent (tc -s class show dev $EGRESS).Reply-stamp compound over IPv6:
Expect: element
2001:db8::2 . 443in theipv6_addr . inet_serviceset.Risks / rollback
nftapply + UAT step 1 are the gate; unit goldens pin the rule text but not kernel acceptance.<name>6) is single-sourced throughpolicy.V6SetName; a divergence would misroute daemon reconciliation.Related Issues