Store concrete ICE candidate for remote candidates. (#4458)

This commit is contained in:
Raja Subramanian
2026-04-17 13:14:47 +05:30
committed by GitHub
parent 2a04bc3ca8
commit dbf5cf6196
3 changed files with 65 additions and 101 deletions
+5 -5
View File
@@ -2331,7 +2331,7 @@ func connectionDetailsFields(infos []*types.ICEConnectionInfo) []any {
if c.Trickle {
cStr += "[trickle]"
}
cStr += " " + c.Local.String()
cStr += " " + c.Candidate.String()
candidates = append(candidates, cStr)
}
for _, c := range info.Remote {
@@ -2344,11 +2344,11 @@ func connectionDetailsFields(infos []*types.ICEConnectionInfo) []any {
if c.Trickle {
cStr += "[trickle]"
}
cStr += " " + fmt.Sprintf("%s %s %s:%d", c.Remote.NetworkType(), c.Remote.Type(), MaybeTruncateIP(c.Remote.Address()), c.Remote.Port())
if relatedAddress := c.Remote.RelatedAddress(); relatedAddress != nil {
relatedAddr := MaybeTruncateIP(relatedAddress.Address)
cStr += " " + fmt.Sprintf("%s %s %s:%d", c.Candidate.Protocol.String(), c.Candidate.Typ.String(), MaybeTruncateIP(c.Candidate.Address), c.Candidate.Port)
if relatedAddress := c.Candidate.RelatedAddress; relatedAddress != "" {
relatedAddr := MaybeTruncateIP(relatedAddress)
if relatedAddr != "" {
cStr += " " + fmt.Sprintf(" related %s:%d", relatedAddr, relatedAddress.Port)
cStr += " " + fmt.Sprintf(" related %s:%d", relatedAddr, c.Candidate.RelatedPort)
}
}
candidates = append(candidates, cStr)
+59 -95
View File
@@ -74,9 +74,7 @@ func (i ICEConnectionType) ReporterType() roomobs.ConnectionType {
// --------------------------------------------
type ICECandidateExtended struct {
// only one of local or remote is set. This is due to type foo in Pion
Local *webrtc.ICECandidate
Remote ice.Candidate
Candidate *webrtc.ICECandidate
SelectedOrder int
Filtered bool
Trickle bool
@@ -135,7 +133,7 @@ func (d *ICEConnectionDetails) GetInfo() *ICEConnectionInfo {
}
for _, c := range d.Local {
info.Local = append(info.Local, &ICECandidateExtended{
Local: c.Local,
Candidate: c.Candidate,
Filtered: c.Filtered,
SelectedOrder: c.SelectedOrder,
Trickle: c.Trickle,
@@ -143,7 +141,7 @@ func (d *ICEConnectionDetails) GetInfo() *ICEConnectionInfo {
}
for _, c := range d.Remote {
info.Remote = append(info.Remote, &ICECandidateExtended{
Remote: c.Remote,
Candidate: c.Candidate,
Filtered: c.Filtered,
SelectedOrder: c.SelectedOrder,
Trickle: c.Trickle,
@@ -163,15 +161,15 @@ func (d *ICEConnectionDetails) AddLocalCandidate(c *webrtc.ICECandidate, filtere
d.lock.Lock()
defer d.lock.Unlock()
compFn := func(e *ICECandidateExtended) bool {
return isCandidateEqualTo(e.Local, c)
return isCandidateEqualTo(e.Candidate, c)
}
if slices.ContainsFunc(d.Local, compFn) {
return
}
d.Local = append(d.Local, &ICECandidateExtended{
Local: c,
Filtered: filtered,
Trickle: trickle,
Candidate: c,
Filtered: filtered,
Trickle: trickle,
})
}
@@ -186,24 +184,30 @@ func (d *ICEConnectionDetails) AddLocalICECandidate(c ice.Candidate, filtered, t
}
func (d *ICEConnectionDetails) AddRemoteCandidate(c webrtc.ICECandidateInit, filtered, trickle, canUpdate bool) {
candidate, err := unmarshalICECandidate(c)
iceCandidate, err := unmarshalICECandidate(c)
if err != nil {
d.logger.Errorw("could not unmarshal candidate", err, "candidate", c)
return
}
d.AddRemoteICECandidate(candidate, filtered, trickle, canUpdate)
d.AddRemoteICECandidate(iceCandidate, filtered, trickle, canUpdate)
}
func (d *ICEConnectionDetails) AddRemoteICECandidate(candidate ice.Candidate, filtered, trickle, canUpdate bool) {
if candidate == nil {
func (d *ICEConnectionDetails) AddRemoteICECandidate(iceCandidate ice.Candidate, filtered, trickle, canUpdate bool) {
if iceCandidate == nil {
// end-of-candidates candidate
return
}
candidate, err := unmarshalCandidate(iceCandidate)
if err != nil {
d.logger.Errorw("could not unmarshal ice candidate", err, "candidate", iceCandidate)
return
}
d.lock.Lock()
defer d.lock.Unlock()
indexFn := func(e *ICECandidateExtended) bool {
return isICECandidateEqualTo(e.Remote, candidate)
return isCandidateEqualTo(e.Candidate, candidate)
}
if idx := slices.IndexFunc(d.Remote, indexFn); idx != -1 {
if canUpdate {
@@ -213,9 +217,9 @@ func (d *ICEConnectionDetails) AddRemoteICECandidate(candidate ice.Candidate, fi
return
}
d.Remote = append(d.Remote, &ICECandidateExtended{
Remote: candidate,
Filtered: filtered,
Trickle: trickle,
Candidate: candidate,
Filtered: filtered,
Trickle: trickle,
})
d.updateConnectionTypeLocked()
}
@@ -235,22 +239,14 @@ func (d *ICEConnectionDetails) SetSelectedPair(pair *webrtc.ICECandidatePair) {
d.selectedCount++
remoteIdx := slices.IndexFunc(d.Remote, func(e *ICECandidateExtended) bool {
return isICECandidateEqualToCandidate(e.Remote, pair.Remote)
return isCandidateEqualTo(e.Candidate, pair.Remote)
})
if remoteIdx < 0 {
// it's possible for prflx candidates to be generated by Pion, we'll add them
candidate, err := unmarshalICECandidate(pair.Remote.ToJSON())
if err != nil {
d.logger.Errorw("could not unmarshal remote candidate", err, "candidate", pair.Remote)
return
}
if candidate == nil {
return
}
d.Remote = append(d.Remote, &ICECandidateExtended{
Remote: candidate,
Filtered: false,
Trickle: false,
Candidate: pair.Remote,
Filtered: false,
Trickle: false,
})
remoteIdx = len(d.Remote) - 1
}
@@ -258,7 +254,7 @@ func (d *ICEConnectionDetails) SetSelectedPair(pair *webrtc.ICECandidatePair) {
d.updateConnectionTypeLocked()
localIdx := slices.IndexFunc(d.Local, func(e *ICECandidateExtended) bool {
return isCandidateEqualTo(e.Local, pair.Local)
return isCandidateEqualTo(e.Candidate, pair.Local)
})
if localIdx < 0 {
d.logger.Errorw("could not match local candidate", nil, "local", pair.Local)
@@ -286,33 +282,33 @@ func (d *ICEConnectionDetails) updateConnectionTypeLocked() {
return
}
remoteCandidate := selectedRemoteCandidate.Remote
switch remoteCandidate.NetworkType() {
case ice.NetworkTypeUDP4, ice.NetworkTypeUDP6:
remoteCandidate := selectedRemoteCandidate.Candidate
switch remoteCandidate.Protocol {
case webrtc.ICEProtocolUDP:
d.Type = ICEConnectionTypeUDP
case ice.NetworkTypeTCP4, ice.NetworkTypeTCP6:
case webrtc.ICEProtocolTCP:
d.Type = ICEConnectionTypeTCP
}
switch remoteCandidate.Type() {
case ice.CandidateTypeRelay:
switch remoteCandidate.Typ {
case webrtc.ICECandidateTypeRelay:
d.Type = ICEConnectionTypeTURN
case ice.CandidateTypePeerReflexive:
case webrtc.ICECandidateTypePrflx:
// if the remote relay candidate pings us *before* we get a relay candidate,
// Pion would have created a prflx candidate with the same address as the relay candidate.
// to report an accurate connection type, we'll compare to see if existing relay candidates match
for _, other := range d.Remote {
or := other.Remote
if or.Type() == ice.CandidateTypeRelay &&
remoteCandidate.Address() == or.Address() &&
or := other.Candidate
if or.Typ == webrtc.ICECandidateTypeRelay &&
remoteCandidate.Address == or.Address &&
// NOTE: port is not compared as relayed address reported by TURN ALLOCATE from
// pion/turn server -> client and later sent from client -> server via ICE Trickle does not
// match port of `prflx` candidate learnt via TURN path. TODO-INVESTIGATE: how and why doesn't
// port match?
//remoteCanddiate.Port() == or.Port() &&
remoteCandidate.NetworkType().NetworkShort() == or.NetworkType().NetworkShort() {
// remoteCandidate.Port == or.Port &&
remoteCandidate.Protocol == or.Protocol {
d.Type = ICEConnectionTypeTURN
break
}
@@ -340,39 +336,6 @@ func isCandidateEqualTo(c1, c2 *webrtc.ICECandidate) bool {
c1.TCPType == c2.TCPType
}
func isICECandidateEqualTo(c1, c2 ice.Candidate) bool {
if c1 == nil && c2 == nil {
return true
}
if (c1 == nil && c2 != nil) || (c1 != nil && c2 == nil) {
return false
}
return c1.Type() == c2.Type() &&
c1.NetworkType() == c2.NetworkType() &&
c1.Address() == c2.Address() &&
c1.Port() == c2.Port() &&
c1.Foundation() == c2.Foundation() &&
c1.Priority() == c2.Priority() &&
c1.RelatedAddress().Equal(c2.RelatedAddress()) &&
c1.TCPType() == c2.TCPType()
}
func isICECandidateEqualToCandidate(c1 ice.Candidate, c2 *webrtc.ICECandidate) bool {
if c1 == nil && c2 == nil {
return true
}
if (c1 == nil && c2 != nil) || (c1 != nil && c2 == nil) {
return false
}
return c1.Type().String() == c2.Typ.String() &&
c1.NetworkType().NetworkShort() == c2.Protocol.String() &&
c1.Address() == c2.Address &&
c1.Port() == int(c2.Port) &&
c1.Foundation() == c2.Foundation &&
c1.Priority() == c2.Priority &&
c1.TCPType().String() == c2.TCPType
}
func unmarshalICECandidate(c webrtc.ICECandidateInit) (ice.Candidate, error) {
candidateValue := strings.TrimPrefix(c.Candidate, "candidate:")
if candidateValue == "" {
@@ -388,28 +351,14 @@ func unmarshalICECandidate(c webrtc.ICECandidateInit) (ice.Candidate, error) {
}
func unmarshalCandidate(i ice.Candidate) (*webrtc.ICECandidate, error) {
var typ webrtc.ICECandidateType
switch i.Type() {
case ice.CandidateTypeHost:
typ = webrtc.ICECandidateTypeHost
case ice.CandidateTypeServerReflexive:
typ = webrtc.ICECandidateTypeSrflx
case ice.CandidateTypePeerReflexive:
typ = webrtc.ICECandidateTypePrflx
case ice.CandidateTypeRelay:
typ = webrtc.ICECandidateTypeRelay
default:
return nil, fmt.Errorf("unknown candidate type: %s", i.Type())
typ, err := convertTypeFromICE(i.Type())
if err != nil {
return nil, err
}
var protocol webrtc.ICEProtocol
switch strings.ToLower(i.NetworkType().NetworkShort()) {
case "udp":
protocol = webrtc.ICEProtocolUDP
case "tcp":
protocol = webrtc.ICEProtocolTCP
default:
return nil, fmt.Errorf("unknown network type: %s", i.NetworkType())
protocol, err := webrtc.NewICEProtocol(i.NetworkType().NetworkShort())
if err != nil {
return nil, err
}
c := webrtc.ICECandidate{
@@ -431,6 +380,21 @@ func unmarshalCandidate(i ice.Candidate) (*webrtc.ICECandidate, error) {
return &c, nil
}
func convertTypeFromICE(t ice.CandidateType) (webrtc.ICECandidateType, error) {
switch t {
case ice.CandidateTypeHost:
return webrtc.ICECandidateTypeHost, nil
case ice.CandidateTypeServerReflexive:
return webrtc.ICECandidateTypeSrflx, nil
case ice.CandidateTypePeerReflexive:
return webrtc.ICECandidateTypePrflx, nil
case ice.CandidateTypeRelay:
return webrtc.ICECandidateTypeRelay, nil
default:
return webrtc.ICECandidateType(t), fmt.Errorf("unknown ice candidate type: %s", t)
}
}
func IsCandidateMDNS(candidate webrtc.ICECandidateInit) bool {
c, err := unmarshalICECandidate(candidate)
if err != nil {
+1 -1
View File
@@ -1355,7 +1355,7 @@ func (c *RTCClient) IsLocalCandidateRelaySelected() bool {
return false
}
for _, local := range info.Local {
if local.SelectedOrder > 0 && local.Local != nil && local.Local.Typ == webrtc.ICECandidateTypeRelay {
if local.SelectedOrder > 0 && local.Candidate != nil && local.Candidate.Typ == webrtc.ICECandidateTypeRelay {
return true
}
}