Skip to content

[client] Add IPv6 routing, fake IPs, and DNS bind selection#5706

Merged
lixmal merged 9 commits intoclient-ipv6-acl-uspfrom
client-ipv6-routing
Apr 9, 2026
Merged

[client] Add IPv6 routing, fake IPs, and DNS bind selection#5706
lixmal merged 9 commits intoclient-ipv6-acl-uspfrom
client-ipv6-routing

Conversation

@lixmal
Copy link
Copy Markdown
Collaborator

@lixmal lixmal commented Mar 26, 2026

Describe your changes

  • Extend forwarder endpoint/ICMP/TCP/UDP handlers for IPv6 address formatting and dual-stack support
  • Add IPv6 fake IP allocation and mapping in fakeip package with tests
  • DNS upstream: bind per-interface/address instead of global, with platform-specific selection (Android, iOS, general)
  • Route manager: handle IPv6 prefixes in dynamic routes and DNS interceptor
  • Anonymizer: update IPv6 output formatting in tests
  • System route ops: add IPv6 loopback handling and forwarding checks on Linux

Stacked on #5688.

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

netbirdio/docs#667

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afef6be1-def8-4c27-978d-a9c417e07a83

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR modernizes address type representations across the networking stack by replacing net.IP and string-based address handling with netip.Addr and netip.AddrPort, expands fake IP allocation to support dual IPv4/IPv6 pools, and refactors DNS client initialization with a new interface. Additionally, it improves IPv6 route management and adds IPv6 forwarding support on Linux.

Changes

Cohort / File(s) Summary
Forwarder address formatting
client/firewall/uspfilter/forwarder/endpoint.go, client/firewall/uspfilter/forwarder/tcp.go, client/firewall/uspfilter/forwarder/udp.go
Replaced string formatting (fmt.Sprintf) for host:port with net.JoinHostPort(...) and explicit strconv.Itoa port conversion; updated endpoint String() to use net.JoinHostPort.
Forwarder logic & ICMP
client/firewall/uspfilter/forwarder/forwarder.go, client/firewall/uspfilter/forwarder/icmp.go
Changed determineDialAddr to return netip.Addr, added ICMPv6 transport registration (icmp.ProtocolNumber6) and simplified ICMPv6 checksum construction; adjusted loopback address construction for netstack.
DNS upstream interface refactor
client/internal/dns/upstream.go, client/internal/dns/upstream_android.go, client/internal/dns/upstream_general.go, client/internal/dns/upstream_ios.go
Introduced privateClientIface and changed GetClientPrivate signatures to accept an interface plus upstream IP; platform implementations updated to derive bind/index from the interface.
Routemanager resolver typing
client/internal/routemanager/dynamic/route.go, client/internal/routemanager/dynamic/route_ios.go, client/internal/routemanager/client/client.go
Switched resolver address from string to netip.AddrPort; updated DNS queries to query both A and AAAA and adjusted client creation to pass typed resolver address/IP.
Dual-family fake IP pools & tests
client/internal/routemanager/fakeip/fakeip.go, client/internal/routemanager/fakeip/fakeip_test.go, client/internal/routemanager/manager.go
Refactored fake-IP Manager to maintain separate IPv4 and IPv6 pools, added GetFakeIPv6Block(), removed IPv4-only restrictions, updated tests and route injection to emit separate v4/v6 fake-route entries.
System ops: IPv6 handling & forwarding
client/internal/routemanager/systemops/systemops_generic.go, client/internal/routemanager/systemops/systemops_linux.go
Made IPv6 split-default route additions/removals conditional on interface IPv6 availability; added IPv6 sysctl forwarding control on Linux (warnings on v6 sysctl failures).
Anonymize defaults
client/anonymize/anonymize.go, client/anonymize/anonymize_test.go
Changed IPv6 anonymization base from 100:: to 2001:db8:ffff:: and adjusted tests accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • pappz
  • crn4

"I hop through packets, quick and spry,
IPv6 and v4 now side by side,
Pools for fakes in dual-stack bloom,
Routes and ICMP find room to zoom —
A rabbit cheers the network's stride! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly addresses the main changes: IPv6 routing support, fake IP allocation, and DNS bind selection across multiple files and subsystems.
Description check ✅ Passed The pull request description provides a clear summary of changes, includes issue/stack references, checks appropriate checklist items, confirms CLA agreement, indicates documentation was added, and provides the docs PR URL.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client-ipv6-routing

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lixmal
Copy link
Copy Markdown
Collaborator Author

lixmal commented Mar 26, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/internal/dns/upstream_ios.go (1)

127-129: ⚠️ Potential issue | 🟡 Minor

Potential nil dereference in getInterfaceIndex.

If net.InterfaceByName returns an error, iface will be nil and accessing iface.Index will panic.

🛡️ Proposed fix
 func getInterfaceIndex(interfaceName string) (int, error) {
 	iface, err := net.InterfaceByName(interfaceName)
+	if err != nil {
+		return 0, err
+	}
 	return iface.Index, err
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/internal/dns/upstream_ios.go` around lines 127 - 129, The
getInterfaceIndex function risks a nil dereference because it returns
iface.Index without checking err; update getInterfaceIndex to check the error
from net.InterfaceByName before accessing iface (e.g., if err != nil return 0,
err), and only read and return iface.Index when iface is non-nil and err == nil
so the function never dereferences a nil iface.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/internal/routemanager/fakeip/fakeip.go`:
- Around line 88-119: The public APIs AllocateFakeIP, GetFakeIP and GetRealIP
currently dispatch by m.pool(ip) without validating or normalizing inputs; add
validation to each method to first call ip.Unmap() and then check ip.IsValid(),
returning an error (for AllocateFakeIP) or (false) for getters if invalid, and
use the unmapped/normalized addr for subsequent calls to m.pool() so IPv4-mapped
IPv6 addresses are routed to the v4 pool and zero/invalid addrs are rejected;
update references to pool(), allocate(), p.allocated and p.fakeToReal to use the
normalized address.

In `@client/internal/routemanager/systemops/systemops_generic.go`:
- Around line 225-239: The IPv6 blackhole addition currently returns on failure
leaving IPv4 split routes in place; change this to a soft-fail: when
!r.wgInterface.Address().HasIPv6() and either
r.addToRouteTable(splitDefaultv6_1, nextHop) or
r.addToRouteTable(splitDefaultv6_2, nextHop) fails, do not return an error—log a
warning (with the error) and, if the second add fails, attempt to rollback the
first but treat rollback failures as warnings too—this keeps the IPv4 split
routes (added earlier) intact and mirrors the existing IPv6 soft-failure pattern
instead of aborting the operation.

---

Outside diff comments:
In `@client/internal/dns/upstream_ios.go`:
- Around line 127-129: The getInterfaceIndex function risks a nil dereference
because it returns iface.Index without checking err; update getInterfaceIndex to
check the error from net.InterfaceByName before accessing iface (e.g., if err !=
nil return 0, err), and only read and return iface.Index when iface is non-nil
and err == nil so the function never dereferences a nil iface.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f99a061-a328-4706-bffd-c83b8f8218df

📥 Commits

Reviewing files that changed from the base of the PR and between 76414a1 and 8487ca9.

📒 Files selected for processing (18)
  • client/firewall/uspfilter/forwarder/endpoint.go
  • client/firewall/uspfilter/forwarder/forwarder.go
  • client/firewall/uspfilter/forwarder/icmp.go
  • client/firewall/uspfilter/forwarder/tcp.go
  • client/firewall/uspfilter/forwarder/udp.go
  • client/internal/dns/upstream.go
  • client/internal/dns/upstream_android.go
  • client/internal/dns/upstream_general.go
  • client/internal/dns/upstream_ios.go
  • client/internal/routemanager/client/client.go
  • client/internal/routemanager/dnsinterceptor/handler.go
  • client/internal/routemanager/dynamic/route.go
  • client/internal/routemanager/dynamic/route_ios.go
  • client/internal/routemanager/fakeip/fakeip.go
  • client/internal/routemanager/fakeip/fakeip_test.go
  • client/internal/routemanager/manager.go
  • client/internal/routemanager/systemops/systemops_generic.go
  • client/internal/routemanager/systemops/systemops_linux.go

Comment thread client/internal/routemanager/fakeip/fakeip.go Outdated
Comment thread client/internal/routemanager/systemops/systemops_generic.go
@lixmal lixmal force-pushed the client-ipv6-routing branch 2 times, most recently from 1bafb87 to 1ffe411 Compare March 26, 2026 12:37
@lixmal
Copy link
Copy Markdown
Collaborator Author

lixmal commented Mar 26, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lixmal lixmal changed the title [client] Add IPv6 routing support with forwarding, fake IPs, and exit node handling [client] Add IPv6 routing, fake IPs, and DNS bind selection Mar 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/internal/routemanager/fakeip/fakeip.go (1)

76-77: Consider sync.RWMutex for read-heavy lookups.

GetFakeIP and GetRealIP are read-only but currently take an exclusive lock. If these paths are hot, switching to RWMutex + RLock may reduce contention.

♻️ Optional refactor
 type Manager struct {
-	mu sync.Mutex
+	mu sync.RWMutex
 	v4 *fakeIPPool
 	v6 *fakeIPPool
 }
@@
 func (m *Manager) GetFakeIP(realIP netip.Addr) (netip.Addr, bool) {
@@
-	m.mu.Lock()
-	defer m.mu.Unlock()
+	m.mu.RLock()
+	defer m.mu.RUnlock()
@@
 func (m *Manager) GetRealIP(fakeIP netip.Addr) (netip.Addr, bool) {
@@
-	m.mu.Lock()
-	defer m.mu.Unlock()
+	m.mu.RLock()
+	defer m.mu.RUnlock()

Also applies to: 116-117, 130-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/internal/routemanager/fakeip/fakeip.go` around lines 76 - 77, The
mutex "mu" is currently a sync.Mutex causing exclusive locks for read-only
lookup paths; change mu's type to sync.RWMutex and update read-only methods
(e.g., GetFakeIP, GetRealIP and the other read-heavy lookup functions
referenced) to use mu.RLock()/mu.RUnlock() while keeping write paths (where v4
is mutated) using mu.Lock()/mu.Unlock(); ensure you update all Lock/Unlock calls
tied to mu to the appropriate RLock/RUnlock or Lock/Unlock in the fakeip pool
methods so reads are concurrent-safe and writes remain exclusive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/internal/routemanager/dynamic/route_ios.go`:
- Around line 30-37: The loop that calls nbdns.ExchangeWithFallback for each
qtype (dns.TypeA and dns.TypeAAAA) should not abort the entire function on a
transport error for one family if the other query already produced usable
answers; modify the logic in the loop around nbdns.ExchangeWithFallback (and the
surrounding variables msg, response, err, domain.SafeString(), startTime,
r.resolverAddr) to collect/append successful answers into a single result set
and on per-query error just record the error and continue; after the loop, if
the combined answer set is non-empty return it, otherwise return an aggregated
error (or the last error) indicating both lookups failed.

---

Nitpick comments:
In `@client/internal/routemanager/fakeip/fakeip.go`:
- Around line 76-77: The mutex "mu" is currently a sync.Mutex causing exclusive
locks for read-only lookup paths; change mu's type to sync.RWMutex and update
read-only methods (e.g., GetFakeIP, GetRealIP and the other read-heavy lookup
functions referenced) to use mu.RLock()/mu.RUnlock() while keeping write paths
(where v4 is mutated) using mu.Lock()/mu.Unlock(); ensure you update all
Lock/Unlock calls tied to mu to the appropriate RLock/RUnlock or Lock/Unlock in
the fakeip pool methods so reads are concurrent-safe and writes remain
exclusive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bb62c08-34cb-48dc-bdfd-ef2c80b05d57

📥 Commits

Reviewing files that changed from the base of the PR and between 8487ca9 and da6f610.

📒 Files selected for processing (20)
  • client/anonymize/anonymize.go
  • client/anonymize/anonymize_test.go
  • client/firewall/uspfilter/forwarder/endpoint.go
  • client/firewall/uspfilter/forwarder/forwarder.go
  • client/firewall/uspfilter/forwarder/icmp.go
  • client/firewall/uspfilter/forwarder/tcp.go
  • client/firewall/uspfilter/forwarder/udp.go
  • client/internal/dns/upstream.go
  • client/internal/dns/upstream_android.go
  • client/internal/dns/upstream_general.go
  • client/internal/dns/upstream_ios.go
  • client/internal/routemanager/client/client.go
  • client/internal/routemanager/dnsinterceptor/handler.go
  • client/internal/routemanager/dynamic/route.go
  • client/internal/routemanager/dynamic/route_ios.go
  • client/internal/routemanager/fakeip/fakeip.go
  • client/internal/routemanager/fakeip/fakeip_test.go
  • client/internal/routemanager/manager.go
  • client/internal/routemanager/systemops/systemops_generic.go
  • client/internal/routemanager/systemops/systemops_linux.go
✅ Files skipped from review due to trivial changes (4)
  • client/internal/dns/upstream.go
  • client/anonymize/anonymize_test.go
  • client/internal/routemanager/manager.go
  • client/firewall/uspfilter/forwarder/tcp.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • client/firewall/uspfilter/forwarder/endpoint.go
  • client/internal/routemanager/systemops/systemops_generic.go
  • client/internal/dns/upstream_android.go
  • client/internal/routemanager/fakeip/fakeip_test.go

Comment thread client/internal/routemanager/dynamic/route_ios.go
@lixmal lixmal force-pushed the client-ipv6-acl-usp branch from 71266a5 to ed5cfa6 Compare March 27, 2026 14:45
# Conflicts:
#	client/firewall/uspfilter/forwarder/forwarder.go
#	client/firewall/uspfilter/forwarder/icmp.go
var currentMTU uint16 = iface.DefaultMTU

// privateClientIface is the subset of the WireGuard interface needed by GetClientPrivate.
type privateClientIface interface {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It required by ios only. Move into the _ios.go

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't, the signature of the stubs for other platforms also require it.

@lixmal lixmal merged commit 5063fea into client-ipv6-acl-usp Apr 9, 2026
18 of 21 checks passed
@lixmal lixmal deleted the client-ipv6-routing branch April 9, 2026 08:55
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants