[client] Add IPv6 support to usersace bind#5147
Conversation
📝 WalkthroughWalkthroughAdds a DualStackPacketConn that multiplexes UDP writes between IPv4 and IPv6, integrates dual-stack tracking and unified receiver creation into ICEBind, and adds comprehensive tests and benchmarks for dual-stack behavior and performance. (47 words) Changes
Sequence Diagram(s)sequenceDiagram
participant App as Application
participant DSPC as DualStackPacketConn
participant IPv4 as IPv4_UDPConn
participant IPv6 as IPv6_UDPConn
App->>DSPC: WriteTo(payload, addr)
alt addr is IPv4
DSPC->>IPv4: WriteTo(payload, addr)
IPv4-->>DSPC: (n, err)
else addr is IPv6
alt IPv4 conn exists
DSPC->>IPv4: WriteTo(payload, addr)
IPv4-->>DSPC: error (type-mismatch)
DSPC->>IPv6: WriteTo(payload, addr)
IPv6-->>DSPC: (n, err)
else
DSPC->>IPv6: WriteTo(payload, addr)
IPv6-->>DSPC: (n, err)
end
else invalid addr type
DSPC-->>App: net.OpError (invalid addr type)
end
DSPC-->>App: (n, err)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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
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: 2
🤖 Fix all issues with AI agents
In `@client/iface/bind/ice_bind.go`:
- Around line 279-283: filterOutStunMessages currently reads s.udpMux without
synchronization and can race with createReceiverFn which sets s.udpMux under
s.muUDPMux; fix by acquiring s.muUDPMux when accessing s.udpMux in
filterOutStunMessages (or take a short-lived local copy while holding
s.muUDPMux), then release the lock before calling s.udpMux.HandleSTUNMessage so
the call is done without holding the mutex; reference s.udpMux, s.muUDPMux,
createReceiverFn, and filterOutStunMessages when making the change.
- Around line 237-265: In createOrUpdateMux (method on ICEBind) close the
previous UDP mux instance before overwriting s.udpMux: if s.udpMux != nil call
its Close() (e.g., s.udpMux.Close()) and handle/ignore the returned error as
appropriate, then assign the new udpmux from udpmux.NewUniversalUDPMuxDefault;
do this while holding the existing muUDPMux as the comment requires to ensure
safe replacement and avoid leaking resources from the previous
UniversalUDPMuxDefault.
🧹 Nitpick comments (1)
client/internal/peer/worker_ice.go (1)
332-338: Consider usingnet.JoinHostPortfor consistency.This function manually wraps IPv6 addresses in brackets before using
fmt.Sprintf. Since lines 290-291 now usenet.JoinHostPort(which handles bracketing automatically), consider applying the same pattern here for consistency.♻️ Suggested refactor
func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) { // wait local endpoint configuration time.Sleep(time.Second) - addrString := pair.Remote.Address() - parsed, err := netip.ParseAddr(addrString) - if (err == nil) && (parsed.Is6()) { - addrString = fmt.Sprintf("[%s]", addrString) - //IPv6 Literals need to be wrapped in brackets for Resolve*Addr() - } - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addrString, remoteWgPort)) + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pair.Remote.Address(), strconv.Itoa(remoteWgPort))) if err != nil { w.log.Warnf("got an error while resolving the udp address, err: %s", err) return }
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@client/iface/bind/ice_bind.go`:
- Around line 60-61: ipv4Conn and ipv6Conn can remain pointing at closed sockets
across Close/Open and later be used by createOrUpdateMux causing write errors;
fix by clearing both ipv4Conn and ipv6Conn (under the muUDPMux lock) when
closing or before re-opening receivers—e.g., set ipv4Conn = nil and ipv6Conn =
nil inside Close() (or at start of Open()) while holding muUDPMux so
createOrUpdateMux never sees stale closed UDPConns.
In `@client/internal/peer/worker_ice.go`:
- Around line 390-418: In WorkerICE.logSuccessfulPaths, avoid reading
w.sessionID without the muxAgent lock: acquire w.muxAgent.RLock(), copy
sessionID to a local variable, release the lock, and use that local variable in
the w.log.WithField call; do the same for the other similar logging block in
this file (the subsequent successful/failed-path log) so all accesses to
w.sessionID are guarded and races are eliminated.
♻️ Duplicate comments (2)
client/iface/bind/ice_bind.go (2)
237-265: Close the previous udpMux before replacing it.
createOrUpdateMuxoverwritess.udpMuxwithout closing the prior instance, which can leak goroutines and sockets. (Duplicate of prior review comment.)🔧 Suggested fix
func (s *ICEBind) createOrUpdateMux() { var muxConn net.PacketConn @@ - s.udpMux = udpmux.NewUniversalUDPMuxDefault( + if s.udpMux != nil { + _ = s.udpMux.Close() + } + s.udpMux = udpmux.NewUniversalUDPMuxDefault( udpmux.UniversalUDPMuxParams{ UDPConn: muxConn, Net: s.transportNet, FilterFn: s.filterFn, WGAddress: s.address, MTU: s.mtu, }, ) }
279-283: Synchronize udpMux access in STUN handler.
filterOutStunMessagesreadss.udpMuxwithout synchronization while other paths update it undermuUDPMux, which can race. (Duplicate of prior review comment.)🔧 Suggested fix
- if s.udpMux != nil { - if muxErr := s.udpMux.HandleSTUNMessage(msg, addr); muxErr != nil { + s.muUDPMux.Lock() + mux := s.udpMux + s.muUDPMux.Unlock() + if mux != nil { + if muxErr := mux.HandleSTUNMessage(msg, addr); muxErr != nil { log.Warnf("failed to handle STUN packet: %v", muxErr) } }
🧹 Nitpick comments (1)
client/iface/bind/dual_stack_conn_bench_test.go (1)
14-118: Optional: extract shared benchmark setup helper.The ListenUDP + DualStackPacketConn setup repeats across benchmarks; a small helper would reduce duplication and keep IPv6 skip logic consistent as benchmarks evolve.
|



Describe your changes
Example log output
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:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.