Respect transport parameters and allowed net types#901
Conversation
6596f67 to
48404fa
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #901 +/- ##
==========================================
- Coverage 88.36% 88.13% -0.23%
==========================================
Files 45 45
Lines 5654 5715 +61
==========================================
+ Hits 4996 5037 +41
- Misses 452 468 +16
- Partials 206 210 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR makes ICE transport/network-type handling consistent across candidate gathering and remote candidate admission, addressing cases where STUN/TURN URLs with mismatched transport were used and where remote candidates could be accepted on disabled network types.
Changes:
- Filter remote candidates by
AgentConfig.NetworkTypesinaddRemoteCandidate, with logging for ignored candidates. - Introduce URL transport interpretation helpers and apply them to srflx gathering to only use supported (UDP) URLs.
- Refactor relay gathering to honor configured network types and local bind filtering, with added/updated tests (including a skipped IPv6 TURN scenario).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
gather.go |
Adds URL/network-type helper logic; filters srflx URLs by effective transport; refactors TURN relay gathering to select behavior per URL and network type. |
gather_test.go |
Updates interface mask handling for IPv6 and adds tests for relay transport/network-type selection and srflx URL transport filtering. |
agent.go |
Ignores remote candidates whose NetworkType is disabled in agent configuration. |
agent_test.go |
Adds tests validating remote/prflx candidates are discarded when network types are disabled. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| conn, connectErr := a.net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr) | ||
| if connectErr != nil { | ||
| a.log.Warnf("Failed to dial TCP address %s: %v", turnServerAddr, connectErr) | ||
| turnServerAddr := fmt.Sprintf("%s:%d", url.Host, url.Port) |
There was a problem hiding this comment.
turnServerAddr is built via fmt.Sprintf("%s:%d", url.Host, url.Port), which can produce an invalid address for IPv6 hosts (missing brackets) and is inconsistent with the net.JoinHostPort usage in the srflx paths. Use net.JoinHostPort(url.Host, strconv.Itoa(url.Port)) here as well so TURN over IPv6 literals (and future IPv6 TURN support) works correctly.
| turnServerAddr := fmt.Sprintf("%s:%d", url.Host, url.Port) | |
| turnServerAddr := net.JoinHostPort(url.Host, strconv.Itoa(url.Port)) |
| udpConn, dialErr := a.net.DialUDP(network, nil, udpAddr) | ||
| if dialErr != nil { | ||
| a.log.Warnf("Failed to dial DTLS address %s: %v", turnServerAddr, dialErr) | ||
|
|
||
| return | ||
| } | ||
| return | ||
| } | ||
|
|
||
| conn := tls.Client(tcpConn, &tls.Config{ | ||
| ServerName: url.Host, | ||
| InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec | ||
| }) | ||
| conn, connectErr := dtls.ClientWithOptions(&fakenet.PacketConn{Conn: udpConn}, udpConn.RemoteAddr(), | ||
| dtls.WithServerName(url.Host), | ||
| dtls.WithInsecureSkipVerify(a.insecureSkipVerify), //nolint:gosec | ||
| dtls.WithLoggerFactory(a.loggerFactory), | ||
| ) | ||
| if connectErr != nil { | ||
| a.log.Warnf("Failed to create DTLS client: %v", turnServerAddr, connectErr) | ||
|
|
||
| if hsErr := conn.HandshakeContext(ctx); hsErr != nil { | ||
| if closeErr := tcpConn.Close(); closeErr != nil { | ||
| a.log.Errorf("Failed to close relay connection: %v", closeErr) | ||
| return | ||
| } | ||
| a.log.Warnf("Failed to connect to relay: %v", hsErr) | ||
|
|
||
| return | ||
| } | ||
| if connectErr = conn.HandshakeContext(ctx); connectErr != nil { | ||
| a.log.Warnf("Failed to create DTLS client: %v", turnServerAddr, connectErr) | ||
|
|
||
| relAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() //nolint:forcetypeassert | ||
| relPort = conn.LocalAddr().(*net.TCPAddr).Port //nolint:forcetypeassert | ||
| relayProtocol = relayProtocolTLS | ||
| locConn = turn.NewSTUNConn(conn) | ||
| default: | ||
| a.log.Warnf("Unable to handle URL in gatherCandidatesRelay %v", url) | ||
| return |
There was a problem hiding this comment.
In the DTLS (TURNS+UDP) branch, udpConn is opened before dtls.ClientWithOptions/HandshakeContext, but on connectErr or handshake failure the code returns without closing udpConn (and potentially without closing conn). This can leak sockets during gathering retries/timeouts; ensure the UDP socket and DTLS conn are closed on all error paths.
| rAddr := relayConn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert | ||
| if shouldFilterLocationTracked(rAddr.IP) { | ||
| a.log.Warnf("TURN address %s is somehow filtered for location tracking reasons", rAddr.IP) | ||
|
|
There was a problem hiding this comment.
If shouldFilterLocationTracked(rAddr.IP) is true, the function returns without cleaning up resources from client.Listen()/Allocate() (e.g., relayConn, locConn, and the TURN client). This can leak connections; close the allocated relay connection and underlying client/locConn before returning on this path.
| if relayConErr := relayConn.Close(); relayConErr != nil { | |
| a.log.Warnf("Failed to close relay %v", relayConErr) | |
| } | |
| client.Close() | |
| if locCloseErr := locConn.Close(); locCloseErr != nil { | |
| a.log.Warnf("Failed to close local connection %v", locCloseErr) | |
| } |
| if !useFilteredLocalAddrs { // nolint:nestif | ||
| if networkType.IsIPv6() { | ||
| bindAddrs = append(bindAddrs, "[::]:0") | ||
| } else { | ||
| bindAddrs = append(bindAddrs, "0.0.0.0:0") | ||
| } |
There was a problem hiding this comment.
The if networkType.IsIPv6() branch inside if !useFilteredLocalAddrs is currently unreachable because IPv6 network types are already skipped at the top of the loop. Either remove this dead branch for clarity, or move the IPv6 skip closer to where IPv6 is actually unsupported so the bind address selection logic stays coherent when IPv6 TURN is implemented.
| if !useFilteredLocalAddrs { // nolint:nestif | |
| if networkType.IsIPv6() { | |
| bindAddrs = append(bindAddrs, "[::]:0") | |
| } else { | |
| bindAddrs = append(bindAddrs, "0.0.0.0:0") | |
| } | |
| if !useFilteredLocalAddrs { | |
| bindAddrs = append(bindAddrs, "0.0.0.0:0") |
There was a problem hiding this comment.
left as-is, it will be needed later for IPv6 TURN
| func TestAddRemoteCandidateRespectsNetworkTypes(t *testing.T) { | ||
| defer test.CheckRoutines(t)() | ||
|
|
||
| agent, err := NewAgentWithOptions( | ||
| WithNetworkTypes([]NetworkType{NetworkTypeUDP4}), | ||
| ) | ||
| require.NoError(t, err) | ||
| defer func() { | ||
| require.NoError(t, agent.Close()) | ||
| }() | ||
|
|
||
| tcpCandidate, err := UnmarshalCandidate("1052353102 1 tcp 1675624447 192.0.2.1 8080 typ host tcptype passive") | ||
| require.NoError(t, err) | ||
| require.NoError(t, agent.AddRemoteCandidate(tcpCandidate)) | ||
|
|
||
| remoteCandidates, err := agent.GetRemoteCandidates() | ||
| require.NoError(t, err) | ||
| require.Len(t, remoteCandidates, 0) | ||
| } |
There was a problem hiding this comment.
AddRemoteCandidate processes candidates asynchronously (it spawns a goroutine that calls a.loop.Run(...)). This test calls GetRemoteCandidates() immediately after AddRemoteCandidate(...), so it may pass even if the candidate would be incorrectly accepted later. Use require.Eventually (as in TestAddRemoteCandidateHonorsRemoteIPFilter) or run addRemoteCandidate via agent.loop.Run to deterministically assert the candidate was rejected.
48404fa to
6ca4fc9
Compare
Make transport/network-type handling consistent across candidate gathering and remote candidate admission. This fixes cases where the ICE agent could use STUN/TURN URLs with mismatched transport or accept remote candidates on disabled network types.
6ca4fc9 to
b764b3a
Compare
JoTurk
left a comment
There was a problem hiding this comment.
we should add more tests here but we can mere it as its, thank you.
Summary
This change makes transport/network-type handling consistent across candidate gathering and remote candidate admission.
It fixes cases where the ICE agent could use STUN/TURN URLs with mismatched transport or accept remote candidates on disabled network types.
What changed
1) Remote candidate filtering by configured network types
addRemoteCandidateto ignore candidates whoseNetworkTypeis not enabled inAgentConfig.NetworkTypes.2) URL transport interpretation and filtering
effectiveURLProtoType(applies default transport when URI transport is omitted)urlSupportsSrflxGatheringrelayNetworkTypesForURLconfiguredNetworkTypesgatherCandidatesSrflx,gatherCandidatesSrflxUDPMux) so only valid UDP URLs are used.3) Relay gathering honors network types and filtered local binds
net.JoinHostPort(..., strconv.Itoa(...))for safer address construction.4) IPv6 TURN status (important)
t.Skip("IPv6 TURN is not supported yet")).Reference issue
Fixes #367