Conversation
|
Important Review skippedToo many files! This PR contains 161 files, which is 11 over the limit of 150. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (161)
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds IPv6 overlay support and a DisableIPv6 toggle across client, device, DNS, netflow, SSH, status, UI, and protobuf schemas; introduces compact netip encoding utilities; migrates WireGuard iface/address types to carry IPv6; and adds targeted nolint suppressions for a backward-compatible PeerIP field. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Engine
participant Device
participant Management
participant DNS
Client->>Engine: Start with EngineConfig{WgAddr: wgaddr.Address, DisableIPv6: bool}
Engine->>Device: Create/Update interface with wgaddr.Address (may include IPv6)
Device-->>Engine: Report assigned v4/v6 (or clear IPv6 on failure)
Engine->>Management: Login/register with System Info (Flags.DisableIPv6, Capabilities)
Management-->>Client: Send PeerConfig (address_v6, sourcePrefixes, capabilities)
Client->>DNS: Generate reverse zones for v4 and v6 based on Engine.WgAddr
Client->>Engine: Apply peer AllowedIPs filtered by local IPv6 availability
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
shared/management/proto/management.proto (1)
338-340: Proto field addition looks good.The
address_v6field appropriately extendsPeerConfigfor dual-stack peer support.Minor documentation consideration: The comment says "16 bytes IP + 1 byte prefix length" which matches the compact format, but for a peer's overlay address the prefix length is typically implied (e.g., always
/128for IPv6). Consider clarifying whether the prefix length byte is meaningful here or if it could be simplified to just 16 bytes for the address sinceDecodeAddr(fromnetiputil) discards the prefix length anyway.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@shared/management/proto/management.proto` around lines 338 - 340, The comment for the new bytes field address_v6 on PeerConfig is ambiguous about the prefix byte; either explicitly document that the extra prefix-length byte is ignored by DecodeAddr (netiputil) or remove it and change the wire-format expectation to a plain 16-byte IPv6 address. Update the proto comment for address_v6 to state the intended format (e.g., "16 bytes IPv6 address, no prefix byte" or "16 bytes IP + 1 byte prefix (prefix is meaningful)"), and if you choose to remove the prefix byte, update any decoding/encoding logic that reads/writes address_v6 (search for DecodeAddr and usages in netiputil and consumers of PeerConfig) to handle the 16-byte-only form. Ensure the comment and code for address_v6, PeerConfig, and DecodeAddr/netiputil are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@shared/netiputil/compact.go`:
- Around line 24-42: The Gosec G602 false positives in DecodePrefix arise from
slice access checks that are actually guarded by the switch on len(b); suppress
them by adding a nolint directive: annotate the relevant slice-access lines in
DecodePrefix (the cases handling length 5 and 17, i.e., the uses of b[:4], b[4],
b[:16], and b[16]) with "//nolint:gosec // G602 false positive: length is
checked by switch case" so the analyzer ignores these safe accesses while
preserving the existing guard logic.
---
Nitpick comments:
In `@shared/management/proto/management.proto`:
- Around line 338-340: The comment for the new bytes field address_v6 on
PeerConfig is ambiguous about the prefix byte; either explicitly document that
the extra prefix-length byte is ignored by DecodeAddr (netiputil) or remove it
and change the wire-format expectation to a plain 16-byte IPv6 address. Update
the proto comment for address_v6 to state the intended format (e.g., "16 bytes
IPv6 address, no prefix byte" or "16 bytes IP + 1 byte prefix (prefix is
meaningful)"), and if you choose to remove the prefix byte, update any
decoding/encoding logic that reads/writes address_v6 (search for DecodeAddr and
usages in netiputil and consumers of PeerConfig) to handle the 16-byte-only
form. Ensure the comment and code for address_v6, PeerConfig, and
DecodeAddr/netiputil are consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 172f0e99-f851-4579-a9e4-d40f6310a23d
⛔ Files ignored due to path filters (1)
shared/management/proto/management.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (7)
client/internal/acl/manager.goclient/internal/debug/debug.gomanagement/internals/shared/grpc/conversion.gomanagement/server/peer_test.goshared/management/proto/management.protoshared/netiputil/compact.goshared/netiputil/compact_test.go
There was a problem hiding this comment.
🧹 Nitpick comments (1)
shared/netiputil/compact.go (1)
23-42: Consider validating prefix length bounds for robustness.The decoded prefix length (last byte) is not validated against valid bounds. For IPv4, valid bits are 0-32; for IPv6, 0-128. If the input contains out-of-range values,
netip.PrefixFromreturns an invalid prefix silently, requiring callers to checkIsValid().For a wire protocol decoder, explicit validation with a clear error message improves debuggability when malformed data is received.
🛡️ Proposed fix to validate prefix length
func DecodePrefix(b []byte) (netip.Prefix, error) { switch len(b) { case 5: var ip4 [4]byte copy(ip4[:], b) - return netip.PrefixFrom(netip.AddrFrom4(ip4), int(b[len(b)-1])), nil + bits := int(b[len(b)-1]) + if bits > 32 { + return netip.Prefix{}, fmt.Errorf("invalid IPv4 prefix length %d (max 32)", bits) + } + return netip.PrefixFrom(netip.AddrFrom4(ip4), bits), nil case 17: var ip6 [16]byte copy(ip6[:], b) addr := netip.AddrFrom16(ip6).Unmap() bits := int(b[len(b)-1]) + maxBits := 128 + if addr.Is4() { + maxBits = 32 + } + if bits > maxBits { + return netip.Prefix{}, fmt.Errorf("invalid prefix length %d (max %d)", bits, maxBits) + } if addr.Is4() && bits > 32 { bits = 32 } return netip.PrefixFrom(addr, bits), nil🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@shared/netiputil/compact.go` around lines 23 - 42, DecodePrefix does not validate the prefix length byte and can produce an invalid netip.Prefix; update DecodePrefix to read the trailing byte into bits and explicitly validate bounds: for the 5-byte (IPv4) case ensure 0 <= bits <= 32 (return a clear fmt.Errorf if not), and for the 17-byte (IPv6) case ensure 0 <= bits <= 128 (return error if out of range); keep the existing addr.Unmap() and the special case that an IPv6-mapped IPv4 address should not claim >32 bits (either validate before or after Unmap and return an error if inconsistent), and only call netip.PrefixFrom when bits are within the validated range so callers never receive silently invalid prefixes from DecodePrefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@shared/netiputil/compact.go`:
- Around line 23-42: DecodePrefix does not validate the prefix length byte and
can produce an invalid netip.Prefix; update DecodePrefix to read the trailing
byte into bits and explicitly validate bounds: for the 5-byte (IPv4) case ensure
0 <= bits <= 32 (return a clear fmt.Errorf if not), and for the 17-byte (IPv6)
case ensure 0 <= bits <= 128 (return error if out of range); keep the existing
addr.Unmap() and the special case that an IPv6-mapped IPv4 address should not
claim >32 bits (either validate before or after Unmap and return an error if
inconsistent), and only call netip.PrefixFrom when bits are within the validated
range so callers never receive silently invalid prefixes from DecodePrefix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4c0facdd-fccf-46f0-b534-18bb5d96c612
📒 Files selected for processing (1)
shared/netiputil/compact.go
There was a problem hiding this comment.
🧹 Nitpick comments (1)
shared/netiputil/compact.go (1)
16-21: Consider validating input prefix.The implementation is correct for valid prefixes. However, if an invalid/zero
netip.Prefixis passed,p.Bits()returns -1 (becomes 255 as byte) and a zeroAddrproduces a 1-byte result. Both would fail on decode, so it's fail-safe, but explicit validation could provide clearer error messages at the source.💡 Optional: Add validation for invalid prefixes
// EncodePrefix encodes a netip.Prefix into compact bytes. // The address is always unmapped before encoding. -func EncodePrefix(p netip.Prefix) []byte { +func EncodePrefix(p netip.Prefix) ([]byte, error) { + if !p.IsValid() { + return nil, fmt.Errorf("invalid prefix") + } addr := p.Addr().Unmap() - return append(addr.AsSlice(), byte(p.Bits())) + return append(addr.AsSlice(), byte(p.Bits())), nil }Note: This would require updating callers to handle the error. Given the current usage context where callers likely pass valid prefixes, keeping the current signature may be acceptable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@shared/netiputil/compact.go` around lines 16 - 21, Validate the input prefix in EncodePrefix by checking netip.Prefix validity and bit length before encoding: change EncodePrefix(p netip.Prefix) to return ([]byte, error), verify p.IsValid() and p.Bits() >= 0 (or other appropriate checks) and return a descriptive error when invalid instead of producing a malformed byte slice; update all callers to handle the error accordingly so decoding won't receive impossible values like 255 bits or a one-byte address.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@shared/netiputil/compact.go`:
- Around line 16-21: Validate the input prefix in EncodePrefix by checking
netip.Prefix validity and bit length before encoding: change EncodePrefix(p
netip.Prefix) to return ([]byte, error), verify p.IsValid() and p.Bits() >= 0
(or other appropriate checks) and return a descriptive error when invalid
instead of producing a malformed byte slice; update all callers to handle the
error accordingly so decoding won't receive impossible values like 255 bits or a
one-byte address.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 58f63604-5572-410f-90a1-859c6db3bd11
📒 Files selected for processing (2)
shared/netiputil/compact.goshared/netiputil/compact_test.go
✅ Files skipped from review due to trivial changes (1)
- shared/netiputil/compact_test.go
PeerCapability is reported in PeerSystemMeta.capabilities on login/sync. Management uses it instead of version gating to determine client features. disableIPv6 in Flags lets users opt out of IPv6 overlay.
b7be56f to
e2f7748
Compare
…anic, v6 validation
…anic, v6 validation
# Conflicts: # client/iface/wgaddr/address.go
…anic, v6 validation
# Conflicts: # client/iface/wgaddr/address.go
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
…arams - Add IPv6 router dispatch to AddOutputDNAT/RemoveOutputDNAT in both nftables and iptables managers (was hardcoded to v4 router only). - Fix all DNAT and AddDNATRule dispatch methods to check Is6() first, then error with ErrIPv6NotInitialized if v6 components are missing. Previously the hasIPv6() && Is6() pattern silently fell through to the v4 router for v6 addresses when v6 was not initialized. - Add ErrIPv6NotInitialized sentinel error, replace all ad-hoc "IPv6 not initialized" format strings across both managers. - Rename sourcePort/targetPort to originalPort/translatedPort in all DNAT method signatures to reflect actual DNAT semantics. - Remove stale "localAddr must be IPv4" comments from interface.
removeFromServerNetwork and CleanUp hardcoded useNewDNSRoute=false when building the router pair for RemoveNatRule. This meant the destination was a Prefix (0.0.0.0/0) instead of a DomainSet, so the IsSet() branch in RemoveNatRule that removes the v6 duplicate never triggered. The v6 NAT rule leaked until the next full Reset. Store useNewDNSRoute on the Router from UpdateRoutes and use it consistently in removeFromServerNetwork and CleanUp, making add and remove symmetric.
The legacy DNS resolver path creates NAT pairs with destination 0.0.0.0/0 (a prefix, not a DomainSet). The v6 NAT duplication only triggered for DomainSets, so legacy dynamic routes never got a v6 NAT rule. Extract NeedsV6NATDuplicate and ToV6NatPair helpers that detect both DomainSets and the v4 default wildcard 0.0.0.0/0. Both nftables and iptables managers now use these for Add/RemoveNatRule, ensuring v6 NAT duplication works for both modern and legacy DNS resolver paths.
The v6 NAT duplication only triggered for DomainSet destinations (modern DNS path). Legacy dynamic routes use a 0.0.0.0/0 prefix destination, so the v6 NAT rule was never created. Add a Dynamic field to RouterPair so the firewall manager can distinguish dynamic routes from exit nodes (both use /0 prefixes). Set it from route.IsDynamic() in routeToRouterPair and propagate through GetInversePair. Both nftables and iptables managers check pair.Dynamic instead of destination shape. Also accumulate errors in RemoveNatRule so v6 cleanup is attempted even if v4 removal fails.
The cleanup path tried to delete the jump rule to NETBIRD-NAT-OUTPUT unconditionally, producing a noisy debug log when the chain was never created (common for the v6 router when no OutputDNAT rules exist). Check ChainExists first, consistent with the chain deletion loop below.
The packet tracer resolved 'self' to the v4 overlay address unconditionally, causing "mixed address families" errors when tracing v6 traffic. Pick the self address matching the peer's address family. Add Engine.GetWgV6Addr() and rework parseAddress into resolveTraceAddresses which parses the non-self address first to determine the family, then resolves self accordingly.
# Conflicts: # management/server/types/networkmap_components.go
…outesToSync Extract deleteAccountUsers from DeleteAccount (complexity 21 -> ~14). Extract processResourcePolicies and getResourcePolicyPeers from getNetworkResourcesRoutesToSync (complexity 31 -> ~15). Fixes SonarCloud S3776 violations.
…sourcesRoutesToSync" This reverts commit 14a39f1.
# Conflicts: # management/server/types/networkmap_components.go
Resolve conflict in setupAndroidRoutes: merge IPv6 fake IP route with the explicit fake IP route storage from #5865. Notifier now stores a slice of fake IP routes (v4 + v6) via SetFakeIPRoutes to preserve the stale route re-injection fix.
# Conflicts: # client/firewall/iptables/manager_linux.go # client/firewall/nftables/manager_linux.go # client/firewall/nftables/router_linux.go
|



Describe your changes
This PR implements IPv6 overlay support in client and management.
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
netbirdio/docs#667
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests