Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions internal/protocols/webrtc/peer_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,20 @@ func (co *PeerConnection) removeUnwantedCandidates(firstMedia *sdp.MediaDescript
}
}

var newAttributes []sdp.Attribute //nolint:prealloc
// When using the shared UDP mux with no explicit interface filter and no
// additional hosts, fetch current live IPs to detect stale candidates
// left over from a prior network configuration (e.g. after a WiFi change).
var liveIPs []string
if co.ICEUDPMux != nil && !co.IPsFromInterfaces && len(co.AdditionalHosts) == 0 {
var err error
liveIPs, err = interfaceIPs(nil)
if err != nil {
co.Log.Log(logger.Warn,
"failed to query live interface IPs, skipping stale candidate filter: %v", err)
}
}

var newAttributes []sdp.Attribute //nolint:prealloc
for _, attr := range firstMedia.Attributes {
if attr.Key == "candidate" {
parts := strings.Split(attr.Value, " ")
Expand All @@ -480,17 +492,27 @@ func (co *PeerConnection) removeUnwantedCandidates(firstMedia *sdp.MediaDescript
continue
}

// hide disallowed IPs
if parts[7] == "host" && !slices.Contains(allowedIPs, parts[4]) {
continue
if parts[7] == "host" {
if len(liveIPs) > 0 && parts[2] == "udp" {
// drop stale mux candidates whose IP no longer exists
// on any live interface (network-change fix)
if !slices.Contains(liveIPs, parts[4]) {
co.Log.Log(logger.Debug,
"dropping stale mux candidate %s: IP %s not found on current interfaces",
attr.Value, parts[4])
continue
}
} else {
// hide IPs not in the allowed interface list
if !slices.Contains(allowedIPs, parts[4]) {
continue
}
}
}
}

newAttributes = append(newAttributes, attr)
}

firstMedia.Attributes = newAttributes

return nil
}

Expand Down
30 changes: 30 additions & 0 deletions internal/protocols/webrtc/peer_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import (
"github.com/stretchr/testify/require"
)

type fakeUDPMux struct{}

func (f *fakeUDPMux) Close() error { return nil }
func (f *fakeUDPMux) GetConn(string, net.Addr) (net.PacketConn, error) { return nil, nil }
func (f *fakeUDPMux) RemoveConnByUfrag(string) {}
func (f *fakeUDPMux) GetListenAddresses() []net.Addr { return nil }

type nilWriter struct{}

func (nilWriter) Write(p []byte) (int, error) {
Expand Down Expand Up @@ -886,3 +893,26 @@ func TestPeerConnectionPublishDataChannel(t *testing.T) {

<-dataReceived
}

func TestRemoveUnwantedCandidatesStaleIP(t *testing.T) {
// Simulates a candidate whose IP is no longer present on any interface
// (e.g. after a WiFi network change). It should be dropped.
co := &PeerConnection{
ICEUDPMux: &fakeUDPMux{}, // non-nil triggers the liveIPs check
Log: test.NilLogger,
}

staleIP := "10.99.99.99" // guaranteed not on any real interface
media := &sdp.MediaDescription{
Attributes: []sdp.Attribute{
{Key: "candidate", Value: "123 1 udp 2130706431 " + staleIP + " 8189 typ host generation 0"},
{Key: "candidate", Value: "456 1 udp 2130706431 127.0.0.1 8189 typ host generation 0"},
},
}

err := co.removeUnwantedCandidates(media)
require.NoError(t, err)

require.Len(t, media.Attributes, 1)
require.Contains(t, media.Attributes[0].Value, "127.0.0.1")
}