Skip to content

Commit 465d8bd

Browse files
committed
Reject candidates from old generation
Return an error if a candidate with a username fragment that does not match the username fragment in the remote description is added. This usually indicates that the candidate was generated before the renegotiation.
1 parent d6154f6 commit 465d8bd

File tree

2 files changed

+136
-15
lines changed

2 files changed

+136
-15
lines changed

peerconnection.go

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,33 +1962,65 @@ func (pc *PeerConnection) RemoteDescription() *SessionDescription {
19621962
// AddICECandidate accepts an ICE candidate string and adds it
19631963
// to the existing set of candidates.
19641964
func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) error {
1965-
if pc.RemoteDescription() == nil {
1965+
remoteDesc := pc.RemoteDescription()
1966+
if remoteDesc == nil {
19661967
return &rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}
19671968
}
19681969

19691970
candidateValue := strings.TrimPrefix(candidate.Candidate, "candidate:")
19701971

1971-
var iceCandidate *ICECandidate
1972-
if candidateValue != "" {
1973-
candidate, err := ice.UnmarshalCandidate(candidateValue)
1974-
if err != nil {
1975-
if errors.Is(err, ice.ErrUnknownCandidateTyp) || errors.Is(err, ice.ErrDetermineNetworkType) {
1976-
pc.log.Warnf("Discarding remote candidate: %s", err)
1972+
if candidateValue == "" {
1973+
return pc.iceTransport.AddRemoteCandidate(nil)
1974+
}
19771975

1978-
return nil
1979-
}
1976+
cand, err := ice.UnmarshalCandidate(candidateValue)
1977+
if err != nil {
1978+
if errors.Is(err, ice.ErrUnknownCandidateTyp) || errors.Is(err, ice.ErrDetermineNetworkType) {
1979+
pc.log.Warnf("Discarding remote candidate: %s", err)
19801980

1981-
return err
1981+
return nil
19821982
}
19831983

1984-
c, err := newICECandidateFromICE(candidate, "", 0)
1985-
if err != nil {
1986-
return err
1984+
return err
1985+
}
1986+
1987+
// Reject candidates from old generations.
1988+
// If candidate.usernameFragment is not null,
1989+
// and is not equal to any username fragment present in the corresponding media
1990+
// description of an applied remote description,
1991+
// return a promise rejected with a newly created OperationError.
1992+
// https://w3c.github.io/webrtc-pc/#dom-peerconnection-addicecandidate
1993+
if ufrag, ok := cand.GetExtension("ufrag"); ok {
1994+
if !pc.descriptionContainsUfrag(remoteDesc.parsed, ufrag.Value) {
1995+
pc.log.Errorf("dropping candidate with ufrag %s because it doesn't match the current ufrags", ufrag.Value)
1996+
1997+
return nil
19871998
}
1988-
iceCandidate = &c
19891999
}
19902000

1991-
return pc.iceTransport.AddRemoteCandidate(iceCandidate)
2001+
c, err := newICECandidateFromICE(cand, "", 0)
2002+
if err != nil {
2003+
return err
2004+
}
2005+
2006+
return pc.iceTransport.AddRemoteCandidate(&c)
2007+
}
2008+
2009+
// Return true if the sdp contains a specific ufrag.
2010+
func (pc *PeerConnection) descriptionContainsUfrag(sdp *sdp.SessionDescription, matchUfrag string) bool {
2011+
ufrag, ok := sdp.Attribute("ice-ufrag")
2012+
if ok && ufrag == matchUfrag {
2013+
return true
2014+
}
2015+
2016+
for _, media := range sdp.MediaDescriptions {
2017+
ufrag, ok := media.Attribute("ice-ufrag")
2018+
if ok && ufrag == matchUfrag {
2019+
return true
2020+
}
2021+
}
2022+
2023+
return false
19922024
}
19932025

19942026
// ICEConnectionState returns the ICE connection state of the

peerconnection_go_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/pion/dtls/v3"
2626
"github.com/pion/ice/v4"
27+
"github.com/pion/logging"
2728
"github.com/pion/rtcp"
2829
"github.com/pion/rtp"
2930
"github.com/pion/transport/v3/test"
@@ -1916,3 +1917,91 @@ func Test_IPv6(t *testing.T) { //nolint: cyclop
19161917

19171918
closePairNow(t, offerPC, answerPC)
19181919
}
1920+
1921+
type testICELogger struct {
1922+
lastErrorMessage string
1923+
}
1924+
1925+
func (t *testICELogger) Trace(string) {}
1926+
func (t *testICELogger) Tracef(string, ...interface{}) {}
1927+
func (t *testICELogger) Debug(string) {}
1928+
func (t *testICELogger) Debugf(string, ...interface{}) {}
1929+
func (t *testICELogger) Info(string) {}
1930+
func (t *testICELogger) Infof(string, ...interface{}) {}
1931+
func (t *testICELogger) Warn(string) {}
1932+
func (t *testICELogger) Warnf(string, ...interface{}) {}
1933+
func (t *testICELogger) Error(msg string) { t.lastErrorMessage = msg }
1934+
func (t *testICELogger) Errorf(format string, args ...interface{}) {
1935+
t.lastErrorMessage = fmt.Sprintf(format, args...)
1936+
}
1937+
1938+
type testICELoggerFactory struct {
1939+
logger *testICELogger
1940+
}
1941+
1942+
func (t *testICELoggerFactory) NewLogger(string) logging.LeveledLogger {
1943+
return t.logger
1944+
}
1945+
1946+
func TestAddICECandidate__DroppingOldGenerationCandidates(t *testing.T) {
1947+
lim := test.TimeOut(time.Second * 30)
1948+
defer lim.Stop()
1949+
1950+
report := test.CheckRoutines(t)
1951+
defer report()
1952+
1953+
testLogger := &testICELogger{}
1954+
loggerFactory := &testICELoggerFactory{logger: testLogger}
1955+
1956+
// Create a new API with the custom logger
1957+
api := NewAPI(WithSettingEngine(SettingEngine{
1958+
LoggerFactory: loggerFactory,
1959+
}))
1960+
1961+
pc, err := api.NewPeerConnection(Configuration{})
1962+
assert.NoError(t, err)
1963+
1964+
_, err = pc.CreateDataChannel("test", nil)
1965+
assert.NoError(t, err)
1966+
1967+
offer, err := pc.CreateOffer(nil)
1968+
assert.NoError(t, err)
1969+
1970+
offerGatheringComplete := GatheringCompletePromise(pc)
1971+
assert.NoError(t, pc.SetLocalDescription(offer))
1972+
<-offerGatheringComplete
1973+
1974+
remotePC, err := api.NewPeerConnection(Configuration{})
1975+
assert.NoError(t, err)
1976+
1977+
assert.NoError(t, remotePC.SetRemoteDescription(offer))
1978+
1979+
remoteDesc := remotePC.RemoteDescription()
1980+
assert.NotNil(t, remoteDesc)
1981+
1982+
ufrag, hasUfrag := remoteDesc.parsed.MediaDescriptions[0].Attribute("ice-ufrag")
1983+
assert.True(t, hasUfrag)
1984+
1985+
emptyUfragCandidate := ICECandidateInit{
1986+
Candidate: "candidate:1 1 UDP 2122252543 192.168.1.1 12345 typ host",
1987+
}
1988+
err = remotePC.AddICECandidate(emptyUfragCandidate)
1989+
assert.NoError(t, err)
1990+
assert.Empty(t, testLogger.lastErrorMessage)
1991+
1992+
validCandidate := ICECandidateInit{
1993+
Candidate: fmt.Sprintf("candidate:1 1 UDP 2122252543 192.168.1.1 12345 typ host ufrag %s", ufrag),
1994+
}
1995+
err = remotePC.AddICECandidate(validCandidate)
1996+
assert.NoError(t, err)
1997+
assert.Empty(t, testLogger.lastErrorMessage)
1998+
1999+
invalidCandidate := ICECandidateInit{
2000+
Candidate: "candidate:1 1 UDP 2122252543 192.168.1.1 12345 typ host ufrag invalid",
2001+
}
2002+
err = remotePC.AddICECandidate(invalidCandidate)
2003+
assert.NoError(t, err)
2004+
assert.Contains(t, testLogger.lastErrorMessage, "dropping candidate with ufrag")
2005+
2006+
closePairNow(t, pc, remotePC)
2007+
}

0 commit comments

Comments
 (0)