Skip to content

Conversation

@pappz
Copy link
Contributor

@pappz pappz commented Jan 23, 2026

Add IPv6 packet header support in UDP raw socket proxy to handle both IPv4 and IPv6 source addresses.

Refactor error handling in proxy bind implementations to validate endpoints before acquiring locks. With this error handling logic, the WireGuard connection is not broken.

Describe your changes

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:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Dual-stack IPv4/IPv6 support for local packet handling and per-family sending paths; separate sockets and bind addresses.
    • New close capability for raw-socket helpers and improved resource cleanup.
  • Bug Fixes

    • Stronger endpoint validation to avoid applying invalid targets or changing state on error.
  • Refactor

    • Consolidated address-translation logic and reordered state-update locking for clearer control flow.
  • Tests

    • New tests covering redirect behavior for IPv4/IPv6 and multiple target switches.

✏️ Tip: You can customize this high-level summary in your review settings.

Add IPv6 packet header support in UDP raw socket proxy
to handle both IPv4 and IPv6 source addresses.
Refactor error handling in proxy bind implementations
to validate endpoints before acquiring locks.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Centralizes address-to-endpoint validation, makes RedirectAs guard against nil/invalid endpoints and reorder state updates, and adds dual‑stack (IPv4/IPv6) raw‑socket handling and header generation across eBPF and UDP proxy paths and tests.

Changes

Cohort / File(s) Summary
Bind proxy (addr translation & RedirectAs)
client/iface/wgproxy/bind/proxy.go
Reintroduced private addrToEndpoint helper with nil/validation errors; RedirectAs calls it, updates wgCurrentUsed, clears paused, signals condition and unlocks only on success; removed inlined addr conversion.
eBPF wrapper (nil/IP guards + signature tweak)
client/iface/wgproxy/ebpf/wrapper.go
RedirectAs now validates endpoint/endpoint.IP early and returns on invalid input; AddTurnConn signature changed to ignore endpoint (_ *net.UDPAddr).
eBPF proxy (dual‑stack send path)
client/iface/wgproxy/ebpf/proxy.go
Reworked to maintain separate IPv4/IPv6 raw sockets (rawConnIPv4, rawConnIPv6), choose IPv4/IPv6 path at send time (IP header, network layer, dst IP, socket), compute UDP checksum against chosen layer, and share serialization options.
Raw socket helpers (IPv4/IPv6 sockets & prepare)
client/iface/wgproxy/rawsocket/rawsocket.go
Replaced single PrepareSenderRawSocket with PrepareSenderRawSocketIPv4() and PrepareSenderRawSocketIPv6() plus internal prepareSenderRawSocket(family,isIPv4); conditional IP_HDRINCL/IPV6_HDRINCL, improved resource cleanup and error handling.
UDP raw sender (local host addr & headers)
client/iface/wgproxy/udp/rawsocket.go
SrcFaker adds localHostAddr; constructor selects IPv4 vs IPv6 raw socket and local host addr; prepareHeaders builds IPv4/IPv6 headers and returns network layer; SendPkg writes to chosen local host addr; added Close() and init cleanup.
UDP proxy (API signature)
client/iface/wgproxy/udp/proxy.go
AddTurnConn parameter changed to _ *net.UDPAddr (unused).
Tests (redirect behavior)
client/iface/wgproxy/redirect_test.go
New Linux/non‑Android tests for RedirectAs across eBPF/UDP and IPv4/IPv6, including multiple-switch scenarios and helper compareUDPAddr.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant BindProxy as Bind Proxy
  participant EBPFWrap as eBPF Wrapper
  participant EBPFProxy as eBPF Proxy
  participant RawSockets as RawSocket(s)
  participant Network

  Caller->>BindProxy: RedirectAs(addr)
  BindProxy->>BindProxy: addrToEndpoint(addr)
  alt resolved
    BindProxy->>BindProxy: lock → set wgCurrentUsed → clear paused → signal cond → unlock
    BindProxy->>EBPFWrap: RedirectAs(endpoint)
    EBPFWrap->>EBPFWrap: validate endpoint & endpoint.IP
    EBPFWrap->>EBPFProxy: sendPkg(endpointAddr)
    EBPFProxy->>EBPFProxy: choose IPv4 or IPv6 path
    EBPFProxy->>RawSockets: build ip/net layers, checksum, serialize
    RawSockets->>Network: write packet to dst (127.0.0.1 or ::1)
  else failed
    BindProxy-->>Caller: return error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lixmal
  • crn4

Poem

🐇 I hop from v4 to v6 with cheer,

I check each addr till the path is clear.
I lock, I signal, then endpoints align,
Packets find tunnels, swift and fine. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 '[client] Add IPv6 support to UDP WireGuard proxy' directly summarizes the main change: adding IPv6 support to the UDP proxy, which aligns with the substantial changes across multiple files for dual-stack IPv4/IPv6 handling.
Description check ✅ Passed The description covers the main changes (IPv6 packet header support, error handling refactoring) and addresses bug fix classification with documentation checkbox completed, but lacks specific issue ticket number, GitHub links, and the secondary changes (like API refactoring) are not detailed.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link
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: 0

Caution

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

⚠️ Outside diff range comments (1)
client/iface/wgproxy/ebpf/wrapper.go (1)

93-105: Minor typo in error message.

The error message says "package redirection" but should likely be "packet redirection" to be consistent with network terminology.

Suggested fix
 func (p *ProxyWrapper) RedirectAs(endpoint *net.UDPAddr) {
 	if endpoint == nil || endpoint.IP == nil {
-		log.Errorf("failed to start package redirection, endpoint is nil")
+		log.Errorf("failed to start packet redirection, endpoint is nil")
 		return
 	}
🧹 Nitpick comments (1)
client/iface/wgproxy/bind/proxy.go (1)

116-130: Good refactor with early validation; minor typo in error message.

The refactored flow correctly validates the endpoint before acquiring the lock, preventing state changes on invalid input. However, the error message says "package redirection" but should be "packet redirection" (same typo as in wrapper.go).

Suggested fix
 func (p *ProxyBind) RedirectAs(endpoint *net.UDPAddr) {
 	ep, err := addrToEndpoint(endpoint)
 	if err != nil {
-		log.Errorf("failed to start package redirection: %v", err)
+		log.Errorf("failed to start packet redirection: %v", err)
 		return
 	}

pappz added 2 commits January 24, 2026 19:30
The sendPkg function now detects IP version and creates appropriate IPv4 or IPv6 headers, preventing IPv6 addresses from being placed into IPv4 packet headers.
- PrepareSenderRawSocket() now returns both IPv4 and IPv6 sockets
- UDP proxy creates only the needed socket based on address family
- eBPF proxy maintains both sockets and selects appropriate one
- Added warning logs for Close() errors during cleanup
Copy link
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/iface/wgproxy/rawsocket/rawsocket.go (1)

82-99: File descriptor leak: file must be closed after successful net.FilePacketConn call.

net.FilePacketConn duplicates the underlying file descriptor using poll.DupCloseOnExec on Unix systems. The original *os.File must be explicitly closed; closing one side does not close the other. Without closing file after line 90 succeeds, the original file descriptor leaks.

Proposed fix
 	file := os.NewFile(uintptr(fd), fmt.Sprintf("fd %d", fd))
 	if file == nil {
 		if closeErr := syscall.Close(fd); closeErr != nil {
 			log.Warnf("failed to close raw socket fd: %v", closeErr)
 		}
 		return nil, fmt.Errorf("converting fd to file failed")
 	}
+	defer file.Close()
+
 	packetConn, err := net.FilePacketConn(file)
 	if err != nil {
-		if closeErr := file.Close(); closeErr != nil {
-			log.Warnf("failed to close file: %v", closeErr)
-		}
 		return nil, fmt.Errorf("converting file to packet conn failed: %w", err)
 	}
 
 	return packetConn, nil
🤖 Fix all issues with AI agents
In `@client/iface/wgproxy/ebpf/proxy.go`:
- Around line 242-249: In WGEBPFProxy.sendPkg add a defensive nil check for the
endpointAddr parameter before accessing endpointAddr.IP to avoid a nil pointer
dereference; if endpointAddr is nil, return a non-nil error (or
wrap/contextualize an existing error) immediately, e.g., validate at the top of
sendPkg and return an error like "nil endpointAddr" so subsequent logic that
uses endpointAddr.IP, endpointAddr.Port or assigns dstIP only runs when
endpointAddr is non-nil.

In `@client/iface/wgproxy/udp/rawsocket.go`:
- Around line 40-55: The function NewSrcFaker must guard against a nil srcAddr
to avoid a panic when accessing srcAddr.IP; add an early nil check at the top of
NewSrcFaker that returns a descriptive error if srcAddr == nil, then proceed
with the existing branch that calls
rawsocket.PrepareSenderRawSocketIPv4/PrepareSenderRawSocketIPv6 and sets
localHostNetIPAddrV4/localHostNetIPAddrV6.
🧹 Nitpick comments (1)
client/iface/wgproxy/udp/rawsocket.go (1)

108-128: Consider using global constants for localhost IPs to avoid duplication.

Lines 109 and 120 use hardcoded net.ParseIP("127.0.0.1") and net.ParseIP("::1") respectively, while the file already has localHostNetIPAddrV4 and localHostNetIPAddrV6 defined.

Proposed fix
 	if srcAddr.IP.To4() != nil {
 		// IPv4
 		ipv4 := &layers.IPv4{
-			DstIP:    net.ParseIP("127.0.0.1"),
+			DstIP:    localHostNetIPAddrV4.IP,
 			SrcIP:    srcAddr.IP,
 			Version:  4,
 			TTL:      64,
 			Protocol: layers.IPProtocolUDP,
 		}
 		ipH = ipv4
 		networkLayer = ipv4
 	} else {
 		// IPv6
 		ipv6 := &layers.IPv6{
-			DstIP:      net.ParseIP("::1"),
+			DstIP:      localHostNetIPAddrV6.IP,
 			SrcIP:      srcAddr.IP,
 			Version:    6,
 			HopLimit:   64,
 			NextHeader: layers.IPProtocolUDP,
 		}

pappz added 2 commits January 26, 2026 10:06
…variables for both IPv4 and IPv6 in raw socket headers
- Split PrepareSenderRawSocket into IPv4 and IPv6 variants
- Make IPv6 raw socket optional, log warning on failure
Copy link
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

🤖 Fix all issues with AI agents
In `@client/iface/wgproxy/rawsocket/rawsocket.go`:
- Around line 63-77: The os.File created by os.NewFile (variable "file") is only
closed on error, leaking the original FD because net.FilePacketConn duplicates
the descriptor; after successfully calling net.FilePacketConn(file) (where the
result is "packetConn"), close the original file (call file.Close()) and log any
close error (e.g., with log.Warnf) but do not close packetConn—ensure this
happens immediately after the successful net.FilePacketConn call so the original
FD is released while preserving the duplicated descriptor used by packetConn.
♻️ Duplicate comments (1)
client/iface/wgproxy/ebpf/proxy.go (1)

250-257: Guard against nil endpointAddr.

Line 257 dereferences endpointAddr.IP; a nil pointer will panic.

🛡️ Proposed fix
 func (p *WGEBPFProxy) sendPkg(data []byte, endpointAddr *net.UDPAddr) error {
+	if endpointAddr == nil {
+		return fmt.Errorf("endpoint address is nil")
+	}
 
 	var ipH gopacket.SerializableLayer

- Close original os.File after net.FilePacketConn duplicates the FD to prevent leaking the descriptor.
- Add tests
Copy link
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

🤖 Fix all issues with AI agents
In `@client/iface/wgproxy/rawsocket/rawsocket.go`:
- Around line 33-43: The IPv6 handling comment and socket options are wrong:
update the IPv6 branch in the raw socket setup (the block using isIPv4 and fd
and syscall.SetsockoptInt) to explicitly set IPV6_HDRINCL for IPv6 sockets
instead of relying on IPPROTO_RAW, and change the comment to state that IPv6
requires IPV6_HDRINCL to accept application-provided IPv6 headers (use
syscall.SetsockoptInt with the IPV6 level and IPV6_HDRINCL option and handle
errors the same way as the IPv4 branch).
🧹 Nitpick comments (3)
client/iface/wgproxy/udp/proxy.go (1)

67-67: Consider using Debugf instead of Infof for connection logging.

This log line will emit for every TURN connection addition. If this is primarily for debugging/development purposes, Debugf would be more appropriate to avoid noise in production logs.

-	log.Infof("remote conn: %v", remoteConn.RemoteAddr())
+	log.Debugf("remote conn: %v", remoteConn.RemoteAddr())
client/iface/wgproxy/redirect_test.go (2)

29-123: Hardcoded ports may cause test flakiness in parallel execution.

Each test uses a unique hardcoded port (51850-51853), but these could conflict with other tests or system services. Consider using port 0 for dynamic allocation where possible, or add t.Parallel() with port allocation from a pool.

However, since these tests require root privileges (eBPF/raw sockets) and likely run sequentially, this may be acceptable for now.


218-222: Time-based synchronization is fragile but acceptable for tests.

The 100ms sleep for redirect processing is a common test pattern but could cause flakiness on slow CI systems. Consider adding a longer timeout or retry mechanism if flakiness is observed.

lixmal
lixmal previously approved these changes Jan 26, 2026
lixmal
lixmal previously approved these changes Jan 26, 2026
@sonarqubecloud
Copy link

@pappz pappz merged commit 05af39a into main Jan 26, 2026
38 of 39 checks passed
@pappz pappz deleted the fix/ipv6-proxy branch January 26, 2026 13:03
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.

3 participants