From 18f7a214aae82a41a6d91a90a7652cf66e3e90e2 Mon Sep 17 00:00:00 2001 From: cnderrauber Date: Tue, 19 Apr 2022 17:32:16 +0800 Subject: [PATCH] Accept aggressive nomination Add config for accept aggressive nomination, it is useful for compatible with chrome or other clients use aggressive nominate to update nominate candidate pairs. --- AUTHORS.txt | 1 + agent.go | 4 ++ agent_config.go | 3 ++ agent_test.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ selection.go | 5 +- 5 files changed, 136 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 4b96858..301f3f4 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -14,6 +14,7 @@ backkem buptczq cgojin Chao Yuan +cnderrauber David Hamilton David Zhao Henry diff --git a/agent.go b/agent.go index 951103b..ebdd1d8 100644 --- a/agent.go +++ b/agent.go @@ -133,6 +133,8 @@ type Agent struct { insecureSkipVerify bool proxyDialer proxy.Dialer + + acceptAggressiveNomination bool } type task struct { @@ -314,6 +316,8 @@ func NewAgent(config *AgentConfig) (*Agent, error) { //nolint:gocognit interfaceFilter: config.InterfaceFilter, insecureSkipVerify: config.InsecureSkipVerify, + + acceptAggressiveNomination: config.AcceptAggressiveNomination, } a.tcpMux = config.TCPMux diff --git a/agent_config.go b/agent_config.go index b373939..08aa158 100644 --- a/agent_config.go +++ b/agent_config.go @@ -159,6 +159,9 @@ type AgentConfig struct { // Proxy Dialer is a dialer that should be implemented by the user based on golang.org/x/net/proxy // dial interface in order to support corporate proxies ProxyDialer proxy.Dialer + + // Accept aggressive nomination in RFC 5245 for compatible with chrome and other browsers + AcceptAggressiveNomination bool } // initWithDefaults populates an agent and falls back to defaults if fields are unset diff --git a/agent_test.go b/agent_test.go index ff06dca..24537f3 100644 --- a/agent_test.go +++ b/agent_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package ice @@ -1763,3 +1764,126 @@ func TestGetSelectedCandidatePair(t *testing.T) { assert.NoError(t, aAgent.Close()) assert.NoError(t, bAgent.Close()) } + +func TestAcceptAggressiveNomination(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + // Create a network with two interfaces + wan, err := vnet.NewRouter(&vnet.RouterConfig{ + CIDR: "0.0.0.0/0", + LoggerFactory: logging.NewDefaultLoggerFactory(), + }) + assert.NoError(t, err) + + net0 := vnet.NewNet(&vnet.NetConfig{ + StaticIPs: []string{"192.168.0.1"}, + }) + assert.NoError(t, wan.AddNet(net0)) + + net1 := vnet.NewNet(&vnet.NetConfig{ + StaticIPs: []string{"192.168.0.2", "192.168.0.3", "192.168.0.4"}, + }) + assert.NoError(t, wan.AddNet(net1)) + + assert.NoError(t, wan.Start()) + + aNotifier, aConnected := onConnected() + bNotifier, bConnected := onConnected() + + KeepaliveInterval := time.Hour + cfg0 := &AgentConfig{ + NetworkTypes: supportedNetworkTypes(), + MulticastDNSMode: MulticastDNSModeDisabled, + Net: net0, + + KeepaliveInterval: &KeepaliveInterval, + CheckInterval: &KeepaliveInterval, + AcceptAggressiveNomination: true, + } + + var aAgent, bAgent *Agent + aAgent, err = NewAgent(cfg0) + require.NoError(t, err) + require.NoError(t, aAgent.OnConnectionStateChange(aNotifier)) + + cfg1 := &AgentConfig{ + NetworkTypes: supportedNetworkTypes(), + MulticastDNSMode: MulticastDNSModeDisabled, + Net: net1, + KeepaliveInterval: &KeepaliveInterval, + CheckInterval: &KeepaliveInterval, + } + + bAgent, err = NewAgent(cfg1) + require.NoError(t, err) + require.NoError(t, bAgent.OnConnectionStateChange(bNotifier)) + + aConn, bConn := connect(aAgent, bAgent) + + // Ensure pair selected + // Note: this assumes ConnectionStateConnected is thrown after selecting the final pair + <-aConnected + <-bConnected + + // Send new USE-CANDIDATE message with higher priority to update the selected pair + buildMsg := func(class stun.MessageClass, username, key string, priority uint32) *stun.Message { + msg, err1 := stun.Build(stun.NewType(stun.MethodBinding, class), stun.TransactionID, + stun.NewUsername(username), + stun.NewShortTermIntegrity(key), + UseCandidate(), + PriorityAttr(priority), + stun.Fingerprint, + ) + if err1 != nil { + t.Fatal(err1) + } + + return msg + } + + selectedCh := make(chan Candidate, 1) + var expectNewSelectedCandidate Candidate + err = aAgent.OnSelectedCandidatePairChange(func(_, remote Candidate) { + selectedCh <- remote + }) + require.NoError(t, err) + var bcandidates []Candidate + bcandidates, err = bAgent.GetLocalCandidates() + require.NoError(t, err) + + for _, c := range bcandidates { + if c != bAgent.getSelectedPair().Local { + if expectNewSelectedCandidate == nil { + incr_priority: + for _, candidates := range aAgent.remoteCandidates { + for _, candidate := range candidates { + if candidate.Equal(c) { + candidate.(*CandidateHost).priorityOverride += 1000 + break incr_priority + } + } + } + expectNewSelectedCandidate = c + } + _, err = c.writeTo(buildMsg(stun.ClassRequest, aAgent.localUfrag+":"+aAgent.remoteUfrag, aAgent.localPwd, c.Priority()).Raw, bAgent.getSelectedPair().Remote) + require.NoError(t, err) + } + } + + time.Sleep(1 * time.Second) + select { + case selected := <-selectedCh: + assert.True(t, selected.Equal(expectNewSelectedCandidate)) + default: + t.Fatal("No selected candidate pair") + } + + assert.NoError(t, wan.Stop()) + if !closePipe(t, aConn, bConn) { + return + } +} diff --git a/selection.go b/selection.go index df43f03..459d3ed 100644 --- a/selection.go +++ b/selection.go @@ -252,8 +252,11 @@ func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote // previously sent by this pair produced a successful response and // generated a valid pair (Section 7.2.5.3.2). The agent sets the // nominated flag value of the valid pair to true. - if selectedPair := s.agent.getSelectedPair(); selectedPair == nil { + if selectedPair := s.agent.getSelectedPair(); selectedPair == nil || + (s.agent.acceptAggressiveNomination && selectedPair.priority() < p.priority()) { s.agent.setSelectedPair(p) + } else if selectedPair != p { + s.log.Tracef("ignore nominate new pair %s, already nominated pair %s", p, selectedPair) } } else { // If the received Binding request triggered a new check to be