Support running ICE lite locally and on a remote

* Parse ice-lite property from remote SessionDescription
* Add option to SettingEngine that allows the local ICEAgent to run
  in lite mode
This commit is contained in:
Sebastian Waisbrot
2019-08-28 16:47:21 -03:00
committed by Sean DuBois
parent c178b8ef97
commit e0aab79044
6 changed files with 101 additions and 4 deletions

View File

@@ -20,6 +20,7 @@ func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) {
api.settingEngine.timeout.ICERelayAcceptanceMinWait, api.settingEngine.timeout.ICERelayAcceptanceMinWait,
api.settingEngine.LoggerFactory, api.settingEngine.LoggerFactory,
api.settingEngine.candidates.ICETrickle, api.settingEngine.candidates.ICETrickle,
api.settingEngine.candidates.ICELite,
api.settingEngine.candidates.ICENetworkTypes, api.settingEngine.candidates.ICENetworkTypes,
opts, opts,
) )

View File

@@ -21,6 +21,7 @@ type ICEGatherer struct {
validatedServers []*ice.URL validatedServers []*ice.URL
agentIsTrickle bool agentIsTrickle bool
lite bool
agent *ice.Agent agent *ice.Agent
portMin uint16 portMin uint16
@@ -54,6 +55,7 @@ func NewICEGatherer(
relayAcceptanceMinWait *time.Duration, relayAcceptanceMinWait *time.Duration,
loggerFactory logging.LoggerFactory, loggerFactory logging.LoggerFactory,
agentIsTrickle bool, agentIsTrickle bool,
lite bool,
networkTypes []NetworkType, networkTypes []NetworkType,
opts ICEGatherOptions, opts ICEGatherOptions,
) (*ICEGatherer, error) { ) (*ICEGatherer, error) {
@@ -69,7 +71,9 @@ func NewICEGatherer(
} }
candidateTypes := []ice.CandidateType{} candidateTypes := []ice.CandidateType{}
if opts.ICEGatherPolicy == ICETransportPolicyRelay { if lite {
candidateTypes = append(candidateTypes, ice.CandidateTypeHost)
} else if opts.ICEGatherPolicy == ICETransportPolicyRelay {
candidateTypes = append(candidateTypes, ice.CandidateTypeRelay) candidateTypes = append(candidateTypes, ice.CandidateTypeRelay)
} }
@@ -83,6 +87,7 @@ func NewICEGatherer(
loggerFactory: loggerFactory, loggerFactory: loggerFactory,
log: loggerFactory.NewLogger("ice"), log: loggerFactory.NewLogger("ice"),
agentIsTrickle: agentIsTrickle, agentIsTrickle: agentIsTrickle,
lite: lite,
networkTypes: networkTypes, networkTypes: networkTypes,
candidateTypes: candidateTypes, candidateTypes: candidateTypes,
candidateSelectionTimeout: candidateSelectionTimeout, candidateSelectionTimeout: candidateSelectionTimeout,
@@ -103,6 +108,7 @@ func (g *ICEGatherer) createAgent() error {
config := &ice.AgentConfig{ config := &ice.AgentConfig{
Trickle: g.agentIsTrickle, Trickle: g.agentIsTrickle,
Lite: g.lite,
Urls: g.validatedServers, Urls: g.validatedServers,
PortMin: g.portMin, PortMin: g.portMin,
PortMax: g.portMax, PortMax: g.portMax,

View File

@@ -22,7 +22,7 @@ func TestNewICEGatherer_Success(t *testing.T) {
ICEServers: []ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}, ICEServers: []ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}},
} }
gatherer, err := NewICEGatherer(0, 0, nil, nil, nil, nil, nil, nil, nil, logging.NewDefaultLoggerFactory(), false, nil, opts) gatherer, err := NewICEGatherer(0, 0, nil, nil, nil, nil, nil, nil, nil, logging.NewDefaultLoggerFactory(), false, false, nil, opts)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -71,7 +71,7 @@ func TestICEGather_LocalCandidateOrder(t *testing.T) {
} }
to := time.Second to := time.Second
gatherer, err := NewICEGatherer(10000, 10010, &to, &to, &to, &to, &to, &to, &to, logging.NewDefaultLoggerFactory(), false, []NetworkType{NetworkTypeUDP4}, opts) gatherer, err := NewICEGatherer(10000, 10010, &to, &to, &to, &to, &to, &to, &to, logging.NewDefaultLoggerFactory(), false, false, []NetworkType{NetworkTypeUDP4}, opts)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@@ -460,6 +460,11 @@ func (pc *PeerConnection) CreateOffer(options *OfferOptions) (SessionDescription
} }
} }
if pc.iceGatherer.lite {
// RFC 5245 S15.3
d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite)
}
midValue := strconv.Itoa(bundleCount) midValue := strconv.Itoa(bundleCount)
if pc.configuration.SDPSemantics == SDPSemanticsPlanB { if pc.configuration.SDPSemantics == SDPSemanticsPlanB {
midValue = "data" midValue = "data"
@@ -869,6 +874,10 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
if desc.Type == SDPTypeOffer { if desc.Type == SDPTypeOffer {
weOffer = false weOffer = false
} }
remoteIsLite := false
if liteValue, haveRemoteIs := desc.parsed.Attribute(sdp.AttrKeyICELite); haveRemoteIs && liteValue == sdp.AttrKeyICELite {
remoteIsLite = true
}
fingerprint, haveFingerprint := desc.parsed.Attribute("fingerprint") fingerprint, haveFingerprint := desc.parsed.Attribute("fingerprint")
for _, m := range pc.RemoteDescription().parsed.MediaDescriptions { for _, m := range pc.RemoteDescription().parsed.MediaDescriptions {
@@ -940,7 +949,10 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
// Start the ice transport // Start the ice transport
iceRole := ICERoleControlled iceRole := ICERoleControlled
if weOffer { // If one of the agents is lite and the other one is not, the lite agent must be the controlling agent.
// If both or neither agents are lite the offering agent is controlling.
// RFC 8445 S6.1.1
if (weOffer && remoteIsLite == pc.iceGatherer.lite) || (remoteIsLite && !pc.iceGatherer.lite) {
iceRole = ICERoleControlling iceRole = ICERoleControlling
} }
err := pc.iceTransport.Start( err := pc.iceTransport.Start(

View File

@@ -556,3 +556,75 @@ func TestGatherOnSetLocalDescription(t *testing.T) {
<-pcAnswerGathered <-pcAnswerGathered
} }
func TestPeerConnection_OfferingLite(t *testing.T) {
s := SettingEngine{}
s.SetLite(true)
offerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{})
if err != nil {
t.Fatal(err)
}
answerPC, err := NewAPI().NewPeerConnection(Configuration{})
if err != nil {
t.Fatal(err)
}
if err = signalPair(offerPC, answerPC); err != nil {
t.Fatal(err)
}
iceComplete := make(chan interface{})
answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
if iceState == ICEConnectionStateConnected {
select {
case <-iceComplete:
default:
close(iceComplete)
}
}
})
<-iceComplete
if err = offerPC.Close(); err != nil {
t.Fatal(err)
} else if err = answerPC.Close(); err != nil {
t.Fatal(err)
}
}
func TestPeerConnection_AnsweringLite(t *testing.T) {
offerPC, err := NewAPI().NewPeerConnection(Configuration{})
if err != nil {
t.Fatal(err)
}
s := SettingEngine{}
s.SetLite(true)
answerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{})
if err != nil {
t.Fatal(err)
}
if err = signalPair(offerPC, answerPC); err != nil {
t.Fatal(err)
}
iceComplete := make(chan interface{})
answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
if iceState == ICEConnectionStateConnected {
select {
case <-iceComplete:
default:
close(iceComplete)
}
}
})
<-iceComplete
if err = offerPC.Close(); err != nil {
t.Fatal(err)
} else if err = answerPC.Close(); err != nil {
t.Fatal(err)
}
}

View File

@@ -30,6 +30,7 @@ type SettingEngine struct {
ICERelayAcceptanceMinWait *time.Duration ICERelayAcceptanceMinWait *time.Duration
} }
candidates struct { candidates struct {
ICELite bool
ICETrickle bool ICETrickle bool
ICENetworkTypes []NetworkType ICENetworkTypes []NetworkType
} }
@@ -88,6 +89,11 @@ func (e *SettingEngine) SetEphemeralUDPPortRange(portMin, portMax uint16) error
return nil return nil
} }
// SetLite configures whether or not the ice agent should be a lite agent
func (e *SettingEngine) SetLite(lite bool) {
e.candidates.ICELite = lite
}
// SetTrickle configures whether or not the ice agent should gather candidates // SetTrickle configures whether or not the ice agent should gather candidates
// via the trickle method or synchronously. // via the trickle method or synchronously.
func (e *SettingEngine) SetTrickle(trickle bool) { func (e *SettingEngine) SetTrickle(trickle bool) {