| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801 |
- // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
- // SPDX-License-Identifier: MIT
- //go:build !js
- // +build !js
- package ice
- import (
- "context"
- "errors"
- "net"
- "strconv"
- "sync"
- "testing"
- "time"
- "github.com/pion/ice/v2/internal/fakenet"
- "github.com/pion/logging"
- "github.com/pion/stun"
- "github.com/pion/transport/v2/test"
- "github.com/pion/transport/v2/vnet"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- type BadAddr struct{}
- func (ba *BadAddr) Network() string {
- return "xxx"
- }
- func (ba *BadAddr) String() string {
- return "yyy"
- }
- func runAgentTest(t *testing.T, config *AgentConfig, task func(ctx context.Context, a *Agent)) {
- a, err := NewAgent(config)
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- if err := a.run(context.Background(), task); err != nil {
- t.Fatalf("Agent run failure: %v", err)
- }
- assert.NoError(t, a.Close())
- }
- func TestHandlePeerReflexive(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // Limit runtime in case of deadlocks
- lim := test.TimeOut(time.Second * 2)
- defer lim.Stop()
- t.Run("UDP prflx candidate from handleInbound()", func(t *testing.T) {
- var config AgentConfig
- runAgentTest(t, &config, func(ctx context.Context, a *Agent) {
- a.selector = &controllingSelector{agent: a, log: a.log}
- hostConfig := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 777,
- Component: 1,
- }
- local, err := NewCandidateHost(&hostConfig)
- local.conn = &fakenet.MockPacketConn{}
- if err != nil {
- t.Fatalf("failed to create a new candidate: %v", err)
- }
- remote := &net.UDPAddr{IP: net.ParseIP("172.17.0.3"), Port: 999}
- msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
- stun.NewUsername(a.localUfrag+":"+a.remoteUfrag),
- UseCandidate(),
- AttrControlling(a.tieBreaker),
- PriorityAttr(local.Priority()),
- stun.NewShortTermIntegrity(a.localPwd),
- stun.Fingerprint,
- )
- if err != nil {
- t.Fatal(err)
- }
- // nolint: contextcheck
- a.handleInbound(msg, local, remote)
- // Length of remote candidate list must be one now
- if len(a.remoteCandidates) != 1 {
- t.Fatal("failed to add a network type to the remote candidate list")
- }
- // Length of remote candidate list for a network type must be 1
- set := a.remoteCandidates[local.NetworkType()]
- if len(set) != 1 {
- t.Fatal("failed to add prflx candidate to remote candidate list")
- }
- c := set[0]
- if c.Type() != CandidateTypePeerReflexive {
- t.Fatal("candidate type must be prflx")
- }
- if c.Address() != "172.17.0.3" {
- t.Fatal("IP address mismatch")
- }
- if c.Port() != 999 {
- t.Fatal("Port number mismatch")
- }
- })
- })
- t.Run("Bad network type with handleInbound()", func(t *testing.T) {
- var config AgentConfig
- runAgentTest(t, &config, func(ctx context.Context, a *Agent) {
- a.selector = &controllingSelector{agent: a, log: a.log}
- hostConfig := CandidateHostConfig{
- Network: "tcp",
- Address: "192.168.0.2",
- Port: 777,
- Component: 1,
- }
- local, err := NewCandidateHost(&hostConfig)
- if err != nil {
- t.Fatalf("failed to create a new candidate: %v", err)
- }
- remote := &BadAddr{}
- // nolint: contextcheck
- a.handleInbound(nil, local, remote)
- if len(a.remoteCandidates) != 0 {
- t.Fatal("bad address should not be added to the remote candidate list")
- }
- })
- })
- t.Run("Success from unknown remote, prflx candidate MUST only be created via Binding Request", func(t *testing.T) {
- var config AgentConfig
- runAgentTest(t, &config, func(ctx context.Context, a *Agent) {
- a.selector = &controllingSelector{agent: a, log: a.log}
- tID := [stun.TransactionIDSize]byte{}
- copy(tID[:], "ABC")
- a.pendingBindingRequests = []bindingRequest{
- {time.Now(), tID, &net.UDPAddr{}, false},
- }
- hostConfig := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 777,
- Component: 1,
- }
- local, err := NewCandidateHost(&hostConfig)
- local.conn = &fakenet.MockPacketConn{}
- if err != nil {
- t.Fatalf("failed to create a new candidate: %v", err)
- }
- remote := &net.UDPAddr{IP: net.ParseIP("172.17.0.3"), Port: 999}
- msg, err := stun.Build(stun.BindingSuccess, stun.NewTransactionIDSetter(tID),
- stun.NewShortTermIntegrity(a.remotePwd),
- stun.Fingerprint,
- )
- if err != nil {
- t.Fatal(err)
- }
- // nolint: contextcheck
- a.handleInbound(msg, local, remote)
- if len(a.remoteCandidates) != 0 {
- t.Fatal("unknown remote was able to create a candidate")
- }
- })
- })
- }
- // Assert that Agent on startup sends message, and doesn't wait for connectivityTicker to fire
- // https://github.com/pion/ice/issues/15
- func TestConnectivityOnStartup(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, err := vnet.NewNet(&vnet.NetConfig{
- StaticIPs: []string{"192.168.0.1"},
- })
- assert.NoError(t, err)
- assert.NoError(t, wan.AddNet(net0))
- net1, err := vnet.NewNet(&vnet.NetConfig{
- StaticIPs: []string{"192.168.0.2"},
- })
- assert.NoError(t, err)
- 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,
- }
- 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 := func(aAgent, bAgent *Agent) (*Conn, *Conn) {
- // Manual signaling
- aUfrag, aPwd, err := aAgent.GetLocalUserCredentials()
- assert.NoError(t, err)
- bUfrag, bPwd, err := bAgent.GetLocalUserCredentials()
- assert.NoError(t, err)
- gatherAndExchangeCandidates(aAgent, bAgent)
- accepted := make(chan struct{})
- accepting := make(chan struct{})
- var aConn *Conn
- origHdlr := aAgent.onConnectionStateChangeHdlr.Load()
- if origHdlr != nil {
- defer check(aAgent.OnConnectionStateChange(origHdlr.(func(ConnectionState)))) //nolint:forcetypeassert
- }
- check(aAgent.OnConnectionStateChange(func(s ConnectionState) {
- if s == ConnectionStateChecking {
- close(accepting)
- }
- if origHdlr != nil {
- origHdlr.(func(ConnectionState))(s) //nolint:forcetypeassert
- }
- }))
- go func() {
- var acceptErr error
- aConn, acceptErr = aAgent.Accept(context.TODO(), bUfrag, bPwd)
- check(acceptErr)
- close(accepted)
- }()
- <-accepting
- bConn, err := bAgent.Dial(context.TODO(), aUfrag, aPwd)
- check(err)
- // Ensure accepted
- <-accepted
- return aConn, bConn
- }(aAgent, bAgent)
- // Ensure pair selected
- // Note: this assumes ConnectionStateConnected is thrown after selecting the final pair
- <-aConnected
- <-bConnected
- assert.NoError(t, wan.Stop())
- if !closePipe(t, aConn, bConn) {
- return
- }
- }
- func TestConnectivityLite(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 30)
- defer lim.Stop()
- stunServerURL := &stun.URI{
- Scheme: SchemeTypeSTUN,
- Host: "1.2.3.4",
- Port: 3478,
- Proto: stun.ProtoTypeUDP,
- }
- natType := &vnet.NATType{
- MappingBehavior: vnet.EndpointIndependent,
- FilteringBehavior: vnet.EndpointIndependent,
- }
- v, err := buildVNet(natType, natType)
- require.NoError(t, err, "should succeed")
- defer v.close()
- aNotifier, aConnected := onConnected()
- bNotifier, bConnected := onConnected()
- cfg0 := &AgentConfig{
- Urls: []*stun.URI{stunServerURL},
- NetworkTypes: supportedNetworkTypes(),
- MulticastDNSMode: MulticastDNSModeDisabled,
- Net: v.net0,
- }
- aAgent, err := NewAgent(cfg0)
- require.NoError(t, err)
- require.NoError(t, aAgent.OnConnectionStateChange(aNotifier))
- cfg1 := &AgentConfig{
- Urls: []*stun.URI{},
- Lite: true,
- CandidateTypes: []CandidateType{CandidateTypeHost},
- NetworkTypes: supportedNetworkTypes(),
- MulticastDNSMode: MulticastDNSModeDisabled,
- Net: v.net1,
- }
- bAgent, err := NewAgent(cfg1)
- require.NoError(t, err)
- require.NoError(t, bAgent.OnConnectionStateChange(bNotifier))
- aConn, bConn := connectWithVNet(aAgent, bAgent)
- // Ensure pair selected
- // Note: this assumes ConnectionStateConnected is thrown after selecting the final pair
- <-aConnected
- <-bConnected
- if !closePipe(t, aConn, bConn) {
- return
- }
- }
- func TestInboundValidity(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- buildMsg := func(class stun.MessageClass, username, key string) *stun.Message {
- msg, err := stun.Build(stun.NewType(stun.MethodBinding, class), stun.TransactionID,
- stun.NewUsername(username),
- stun.NewShortTermIntegrity(key),
- stun.Fingerprint,
- )
- if err != nil {
- t.Fatal(err)
- }
- return msg
- }
- remote := &net.UDPAddr{IP: net.ParseIP("172.17.0.3"), Port: 999}
- hostConfig := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 777,
- Component: 1,
- }
- local, err := NewCandidateHost(&hostConfig)
- local.conn = &fakenet.MockPacketConn{}
- if err != nil {
- t.Fatalf("failed to create a new candidate: %v", err)
- }
- t.Run("Invalid Binding requests should be discarded", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- a.handleInbound(buildMsg(stun.ClassRequest, "invalid", a.localPwd), local, remote)
- if len(a.remoteCandidates) == 1 {
- t.Fatal("Binding with invalid Username was able to create prflx candidate")
- }
- a.handleInbound(buildMsg(stun.ClassRequest, a.localUfrag+":"+a.remoteUfrag, "Invalid"), local, remote)
- if len(a.remoteCandidates) == 1 {
- t.Fatal("Binding with invalid MessageIntegrity was able to create prflx candidate")
- }
- assert.NoError(t, a.Close())
- })
- t.Run("Invalid Binding success responses should be discarded", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- a.handleInbound(buildMsg(stun.ClassSuccessResponse, a.localUfrag+":"+a.remoteUfrag, "Invalid"), local, remote)
- if len(a.remoteCandidates) == 1 {
- t.Fatal("Binding with invalid MessageIntegrity was able to create prflx candidate")
- }
- assert.NoError(t, a.Close())
- })
- t.Run("Discard non-binding messages", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- a.handleInbound(buildMsg(stun.ClassErrorResponse, a.localUfrag+":"+a.remoteUfrag, "Invalid"), local, remote)
- if len(a.remoteCandidates) == 1 {
- t.Fatal("non-binding message was able to create prflxRemote")
- }
- assert.NoError(t, a.Close())
- })
- t.Run("Valid bind request", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- err = a.run(context.Background(), func(ctx context.Context, a *Agent) {
- a.selector = &controllingSelector{agent: a, log: a.log}
- // nolint: contextcheck
- a.handleInbound(buildMsg(stun.ClassRequest, a.localUfrag+":"+a.remoteUfrag, a.localPwd), local, remote)
- if len(a.remoteCandidates) != 1 {
- t.Fatal("Binding with valid values was unable to create prflx candidate")
- }
- })
- assert.NoError(t, err)
- assert.NoError(t, a.Close())
- })
- t.Run("Valid bind without fingerprint", func(t *testing.T) {
- var config AgentConfig
- runAgentTest(t, &config, func(ctx context.Context, a *Agent) {
- a.selector = &controllingSelector{agent: a, log: a.log}
- msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
- stun.NewUsername(a.localUfrag+":"+a.remoteUfrag),
- stun.NewShortTermIntegrity(a.localPwd),
- )
- if err != nil {
- t.Fatal(err)
- }
- // nolint: contextcheck
- a.handleInbound(msg, local, remote)
- if len(a.remoteCandidates) != 1 {
- t.Fatal("Binding with valid values (but no fingerprint) was unable to create prflx candidate")
- }
- })
- })
- t.Run("Success with invalid TransactionID", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- hostConfig := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 777,
- Component: 1,
- }
- local, err := NewCandidateHost(&hostConfig)
- local.conn = &fakenet.MockPacketConn{}
- if err != nil {
- t.Fatalf("failed to create a new candidate: %v", err)
- }
- remote := &net.UDPAddr{IP: net.ParseIP("172.17.0.3"), Port: 999}
- tID := [stun.TransactionIDSize]byte{}
- copy(tID[:], "ABC")
- msg, err := stun.Build(stun.BindingSuccess, stun.NewTransactionIDSetter(tID),
- stun.NewShortTermIntegrity(a.remotePwd),
- stun.Fingerprint,
- )
- assert.NoError(t, err)
- a.handleInbound(msg, local, remote)
- if len(a.remoteCandidates) != 0 {
- t.Fatal("unknown remote was able to create a candidate")
- }
- assert.NoError(t, a.Close())
- })
- }
- func TestInvalidAgentStarts(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- a, err := NewAgent(&AgentConfig{})
- assert.NoError(t, err)
- ctx := context.Background()
- ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
- defer cancel()
- if _, err = a.Dial(ctx, "", "bar"); err != nil && !errors.Is(err, ErrRemoteUfragEmpty) {
- t.Fatal(err)
- }
- if _, err = a.Dial(ctx, "foo", ""); err != nil && !errors.Is(err, ErrRemotePwdEmpty) {
- t.Fatal(err)
- }
- if _, err = a.Dial(ctx, "foo", "bar"); err != nil && !errors.Is(err, ErrCanceledByCaller) {
- t.Fatal(err)
- }
- if _, err = a.Dial(context.TODO(), "foo", "bar"); err != nil && !errors.Is(err, ErrMultipleStart) {
- t.Fatal(err)
- }
- assert.NoError(t, a.Close())
- }
- // Assert that Agent emits Connecting/Connected/Disconnected/Failed/Closed messages
- func TestConnectionStateCallback(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- disconnectedDuration := time.Second
- failedDuration := time.Second
- KeepaliveInterval := time.Duration(0)
- cfg := &AgentConfig{
- Urls: []*stun.URI{},
- NetworkTypes: supportedNetworkTypes(),
- DisconnectedTimeout: &disconnectedDuration,
- FailedTimeout: &failedDuration,
- KeepaliveInterval: &KeepaliveInterval,
- }
- aAgent, err := NewAgent(cfg)
- if err != nil {
- t.Error(err)
- }
- bAgent, err := NewAgent(cfg)
- if err != nil {
- t.Error(err)
- }
- isChecking := make(chan interface{})
- isConnected := make(chan interface{})
- isDisconnected := make(chan interface{})
- isFailed := make(chan interface{})
- isClosed := make(chan interface{})
- err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
- switch c {
- case ConnectionStateChecking:
- close(isChecking)
- case ConnectionStateConnected:
- close(isConnected)
- case ConnectionStateDisconnected:
- close(isDisconnected)
- case ConnectionStateFailed:
- close(isFailed)
- case ConnectionStateClosed:
- close(isClosed)
- default:
- }
- })
- if err != nil {
- t.Error(err)
- }
- connect(aAgent, bAgent)
- <-isChecking
- <-isConnected
- <-isDisconnected
- <-isFailed
- assert.NoError(t, aAgent.Close())
- assert.NoError(t, bAgent.Close())
- <-isClosed
- }
- func TestInvalidGather(t *testing.T) {
- t.Run("Gather with no OnCandidate should error", func(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Error constructing ice.Agent")
- }
- err = a.GatherCandidates()
- if !errors.Is(err, ErrNoOnCandidateHandler) {
- t.Fatal("trickle GatherCandidates succeeded without OnCandidate")
- }
- assert.NoError(t, a.Close())
- })
- }
- func TestCandidatePairStats(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // Avoid deadlocks?
- defer test.TimeOut(1 * time.Second).Stop()
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Failed to create agent: %s", err)
- }
- hostConfig := &CandidateHostConfig{
- Network: "udp",
- Address: "192.168.1.1",
- Port: 19216,
- Component: 1,
- }
- hostLocal, err := NewCandidateHost(hostConfig)
- if err != nil {
- t.Fatalf("Failed to construct local host candidate: %s", err)
- }
- relayConfig := &CandidateRelayConfig{
- Network: "udp",
- Address: "1.2.3.4",
- Port: 2340,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43210,
- }
- relayRemote, err := NewCandidateRelay(relayConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote relay candidate: %s", err)
- }
- srflxConfig := &CandidateServerReflexiveConfig{
- Network: "udp",
- Address: "10.10.10.2",
- Port: 19218,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43212,
- }
- srflxRemote, err := NewCandidateServerReflexive(srflxConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote srflx candidate: %s", err)
- }
- prflxConfig := &CandidatePeerReflexiveConfig{
- Network: "udp",
- Address: "10.10.10.2",
- Port: 19217,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43211,
- }
- prflxRemote, err := NewCandidatePeerReflexive(prflxConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote prflx candidate: %s", err)
- }
- hostConfig = &CandidateHostConfig{
- Network: "udp",
- Address: "1.2.3.5",
- Port: 12350,
- Component: 1,
- }
- hostRemote, err := NewCandidateHost(hostConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote host candidate: %s", err)
- }
- for _, remote := range []Candidate{relayRemote, srflxRemote, prflxRemote, hostRemote} {
- p := a.findPair(hostLocal, remote)
- if p == nil {
- a.addPair(hostLocal, remote)
- }
- }
- p := a.findPair(hostLocal, prflxRemote)
- p.state = CandidatePairStateFailed
- stats := a.GetCandidatePairsStats()
- if len(stats) != 4 {
- t.Fatal("expected 4 candidate pairs stats")
- }
- var relayPairStat, srflxPairStat, prflxPairStat, hostPairStat CandidatePairStats
- for _, cps := range stats {
- if cps.LocalCandidateID != hostLocal.ID() {
- t.Fatal("invalid local candidate id")
- }
- switch cps.RemoteCandidateID {
- case relayRemote.ID():
- relayPairStat = cps
- case srflxRemote.ID():
- srflxPairStat = cps
- case prflxRemote.ID():
- prflxPairStat = cps
- case hostRemote.ID():
- hostPairStat = cps
- default:
- t.Fatal("invalid remote candidate ID")
- }
- }
- if relayPairStat.RemoteCandidateID != relayRemote.ID() {
- t.Fatal("missing host-relay pair stat")
- }
- if srflxPairStat.RemoteCandidateID != srflxRemote.ID() {
- t.Fatal("missing host-srflx pair stat")
- }
- if prflxPairStat.RemoteCandidateID != prflxRemote.ID() {
- t.Fatal("missing host-prflx pair stat")
- }
- if hostPairStat.RemoteCandidateID != hostRemote.ID() {
- t.Fatal("missing host-host pair stat")
- }
- if prflxPairStat.State != CandidatePairStateFailed {
- t.Fatalf("expected host-prflx pair to have state failed, it has state %s instead",
- prflxPairStat.State.String())
- }
- assert.NoError(t, a.Close())
- }
- func TestLocalCandidateStats(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // Avoid deadlocks?
- defer test.TimeOut(1 * time.Second).Stop()
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Failed to create agent: %s", err)
- }
- hostConfig := &CandidateHostConfig{
- Network: "udp",
- Address: "192.168.1.1",
- Port: 19216,
- Component: 1,
- }
- hostLocal, err := NewCandidateHost(hostConfig)
- if err != nil {
- t.Fatalf("Failed to construct local host candidate: %s", err)
- }
- srflxConfig := &CandidateServerReflexiveConfig{
- Network: "udp",
- Address: "192.168.1.1",
- Port: 19217,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43212,
- }
- srflxLocal, err := NewCandidateServerReflexive(srflxConfig)
- if err != nil {
- t.Fatalf("Failed to construct local srflx candidate: %s", err)
- }
- a.localCandidates[NetworkTypeUDP4] = []Candidate{hostLocal, srflxLocal}
- localStats := a.GetLocalCandidatesStats()
- if len(localStats) != 2 {
- t.Fatalf("expected 2 local candidates stats, got %d instead", len(localStats))
- }
- var hostLocalStat, srflxLocalStat CandidateStats
- for _, stats := range localStats {
- var candidate Candidate
- switch stats.ID {
- case hostLocal.ID():
- hostLocalStat = stats
- candidate = hostLocal
- case srflxLocal.ID():
- srflxLocalStat = stats
- candidate = srflxLocal
- default:
- t.Fatal("invalid local candidate ID")
- }
- if stats.CandidateType != candidate.Type() {
- t.Fatal("invalid stats CandidateType")
- }
- if stats.Priority != candidate.Priority() {
- t.Fatal("invalid stats CandidateType")
- }
- if stats.IP != candidate.Address() {
- t.Fatal("invalid stats IP")
- }
- }
- if hostLocalStat.ID != hostLocal.ID() {
- t.Fatal("missing host local stat")
- }
- if srflxLocalStat.ID != srflxLocal.ID() {
- t.Fatal("missing srflx local stat")
- }
- assert.NoError(t, a.Close())
- }
- func TestRemoteCandidateStats(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // Avoid deadlocks?
- defer test.TimeOut(1 * time.Second).Stop()
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Failed to create agent: %s", err)
- }
- relayConfig := &CandidateRelayConfig{
- Network: "udp",
- Address: "1.2.3.4",
- Port: 12340,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43210,
- }
- relayRemote, err := NewCandidateRelay(relayConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote relay candidate: %s", err)
- }
- srflxConfig := &CandidateServerReflexiveConfig{
- Network: "udp",
- Address: "10.10.10.2",
- Port: 19218,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43212,
- }
- srflxRemote, err := NewCandidateServerReflexive(srflxConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote srflx candidate: %s", err)
- }
- prflxConfig := &CandidatePeerReflexiveConfig{
- Network: "udp",
- Address: "10.10.10.2",
- Port: 19217,
- Component: 1,
- RelAddr: "4.3.2.1",
- RelPort: 43211,
- }
- prflxRemote, err := NewCandidatePeerReflexive(prflxConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote prflx candidate: %s", err)
- }
- hostConfig := &CandidateHostConfig{
- Network: "udp",
- Address: "1.2.3.5",
- Port: 12350,
- Component: 1,
- }
- hostRemote, err := NewCandidateHost(hostConfig)
- if err != nil {
- t.Fatalf("Failed to construct remote host candidate: %s", err)
- }
- a.remoteCandidates[NetworkTypeUDP4] = []Candidate{relayRemote, srflxRemote, prflxRemote, hostRemote}
- remoteStats := a.GetRemoteCandidatesStats()
- if len(remoteStats) != 4 {
- t.Fatalf("expected 4 remote candidates stats, got %d instead", len(remoteStats))
- }
- var relayRemoteStat, srflxRemoteStat, prflxRemoteStat, hostRemoteStat CandidateStats
- for _, stats := range remoteStats {
- var candidate Candidate
- switch stats.ID {
- case relayRemote.ID():
- relayRemoteStat = stats
- candidate = relayRemote
- case srflxRemote.ID():
- srflxRemoteStat = stats
- candidate = srflxRemote
- case prflxRemote.ID():
- prflxRemoteStat = stats
- candidate = prflxRemote
- case hostRemote.ID():
- hostRemoteStat = stats
- candidate = hostRemote
- default:
- t.Fatal("invalid remote candidate ID")
- }
- if stats.CandidateType != candidate.Type() {
- t.Fatal("invalid stats CandidateType")
- }
- if stats.Priority != candidate.Priority() {
- t.Fatal("invalid stats CandidateType")
- }
- if stats.IP != candidate.Address() {
- t.Fatal("invalid stats IP")
- }
- }
- if relayRemoteStat.ID != relayRemote.ID() {
- t.Fatal("missing relay remote stat")
- }
- if srflxRemoteStat.ID != srflxRemote.ID() {
- t.Fatal("missing srflx remote stat")
- }
- if prflxRemoteStat.ID != prflxRemote.ID() {
- t.Fatal("missing prflx remote stat")
- }
- if hostRemoteStat.ID != hostRemote.ID() {
- t.Fatal("missing host remote stat")
- }
- assert.NoError(t, a.Close())
- }
- func TestInitExtIPMapping(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // a.extIPMapper should be nil by default
- a, err := NewAgent(&AgentConfig{})
- if err != nil {
- t.Fatalf("Failed to create agent: %v", err)
- }
- if a.extIPMapper != nil {
- t.Fatal("a.extIPMapper should be nil by default")
- }
- assert.NoError(t, a.Close())
- // a.extIPMapper should be nil when NAT1To1IPs is a non-nil empty array
- a, err = NewAgent(&AgentConfig{
- NAT1To1IPs: []string{},
- NAT1To1IPCandidateType: CandidateTypeHost,
- })
- if err != nil {
- t.Fatalf("Failed to create agent: %v", err)
- }
- if a.extIPMapper != nil {
- t.Fatal("a.extIPMapper should be nil by default")
- }
- assert.NoError(t, a.Close())
- // NewAgent should return an error when 1:1 NAT for host candidate is enabled
- // but the candidate type does not appear in the CandidateTypes.
- _, err = NewAgent(&AgentConfig{
- NAT1To1IPs: []string{"1.2.3.4"},
- NAT1To1IPCandidateType: CandidateTypeHost,
- CandidateTypes: []CandidateType{CandidateTypeRelay},
- })
- if !errors.Is(err, ErrIneffectiveNAT1To1IPMappingHost) {
- t.Fatalf("Unexpected error: %v", err)
- }
- // NewAgent should return an error when 1:1 NAT for srflx candidate is enabled
- // but the candidate type does not appear in the CandidateTypes.
- _, err = NewAgent(&AgentConfig{
- NAT1To1IPs: []string{"1.2.3.4"},
- NAT1To1IPCandidateType: CandidateTypeServerReflexive,
- CandidateTypes: []CandidateType{CandidateTypeRelay},
- })
- if !errors.Is(err, ErrIneffectiveNAT1To1IPMappingSrflx) {
- t.Fatalf("Unexpected error: %v", err)
- }
- // NewAgent should return an error when 1:1 NAT for host candidate is enabled
- // along with mDNS with MulticastDNSModeQueryAndGather
- _, err = NewAgent(&AgentConfig{
- NAT1To1IPs: []string{"1.2.3.4"},
- NAT1To1IPCandidateType: CandidateTypeHost,
- MulticastDNSMode: MulticastDNSModeQueryAndGather,
- })
- if !errors.Is(err, ErrMulticastDNSWithNAT1To1IPMapping) {
- t.Fatalf("Unexpected error: %v", err)
- }
- // NewAgent should return if newExternalIPMapper() returns an error.
- _, err = NewAgent(&AgentConfig{
- NAT1To1IPs: []string{"bad.2.3.4"}, // Bad IP
- NAT1To1IPCandidateType: CandidateTypeHost,
- })
- if !errors.Is(err, ErrInvalidNAT1To1IPMapping) {
- t.Fatalf("Unexpected error: %v", err)
- }
- }
- func TestBindingRequestTimeout(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- const expectedRemovalCount = 2
- a, err := NewAgent(&AgentConfig{})
- assert.NoError(t, err)
- now := time.Now()
- a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
- timestamp: now, // Valid
- })
- a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
- timestamp: now.Add(-3900 * time.Millisecond), // Valid
- })
- a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
- timestamp: now.Add(-4100 * time.Millisecond), // Invalid
- })
- a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
- timestamp: now.Add(-75 * time.Hour), // Invalid
- })
- a.invalidatePendingBindingRequests(now)
- assert.Equal(t, expectedRemovalCount, len(a.pendingBindingRequests), "Binding invalidation due to timeout did not remove the correct number of binding requests")
- assert.NoError(t, a.Close())
- }
- // TestAgentCredentials checks if local username fragments and passwords (if set) meet RFC standard
- // and ensure it's backwards compatible with previous versions of the pion/ice
- func TestAgentCredentials(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- // Make sure to pass Travis check by disabling the logs
- log := logging.NewDefaultLoggerFactory()
- log.DefaultLogLevel = logging.LogLevelDisabled
- // Agent should not require any of the usernames and password to be set
- // If set, they should follow the default 16/128 bits random number generator strategy
- agent, err := NewAgent(&AgentConfig{LoggerFactory: log})
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len([]rune(agent.localUfrag))*8, 24)
- assert.GreaterOrEqual(t, len([]rune(agent.localPwd))*8, 128)
- assert.NoError(t, agent.Close())
- // Should honor RFC standards
- // Local values MUST be unguessable, with at least 128 bits of
- // random number generator output used to generate the password, and
- // at least 24 bits of output to generate the username fragment.
- _, err = NewAgent(&AgentConfig{LocalUfrag: "xx", LoggerFactory: log})
- assert.EqualError(t, err, ErrLocalUfragInsufficientBits.Error())
- _, err = NewAgent(&AgentConfig{LocalPwd: "xxxxxx", LoggerFactory: log})
- assert.EqualError(t, err, ErrLocalPwdInsufficientBits.Error())
- }
- // Assert that Agent on Failure deletes all existing candidates
- // User can then do an ICE Restart to bring agent back
- func TestConnectionStateFailedDeleteAllCandidates(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- oneSecond := time.Second
- KeepaliveInterval := time.Duration(0)
- cfg := &AgentConfig{
- NetworkTypes: supportedNetworkTypes(),
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- KeepaliveInterval: &KeepaliveInterval,
- }
- aAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- bAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- isFailed := make(chan interface{})
- assert.NoError(t, aAgent.OnConnectionStateChange(func(c ConnectionState) {
- if c == ConnectionStateFailed {
- close(isFailed)
- }
- }))
- connect(aAgent, bAgent)
- <-isFailed
- done := make(chan struct{})
- assert.NoError(t, aAgent.run(context.Background(), func(ctx context.Context, agent *Agent) {
- assert.Equal(t, len(aAgent.remoteCandidates), 0)
- assert.Equal(t, len(aAgent.localCandidates), 0)
- close(done)
- }))
- <-done
- assert.NoError(t, aAgent.Close())
- assert.NoError(t, bAgent.Close())
- }
- // Assert that the ICE Agent can go directly from Connecting -> Failed on both sides
- func TestConnectionStateConnectingToFailed(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- oneSecond := time.Second
- KeepaliveInterval := time.Duration(0)
- cfg := &AgentConfig{
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- KeepaliveInterval: &KeepaliveInterval,
- }
- aAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- bAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- var isFailed sync.WaitGroup
- var isChecking sync.WaitGroup
- isFailed.Add(2)
- isChecking.Add(2)
- connectionStateCheck := func(c ConnectionState) {
- switch c {
- case ConnectionStateFailed:
- isFailed.Done()
- case ConnectionStateChecking:
- isChecking.Done()
- case ConnectionStateCompleted:
- t.Errorf("Unexpected ConnectionState: %v", c)
- default:
- }
- }
- assert.NoError(t, aAgent.OnConnectionStateChange(connectionStateCheck))
- assert.NoError(t, bAgent.OnConnectionStateChange(connectionStateCheck))
- go func() {
- _, err := aAgent.Accept(context.TODO(), "InvalidFrag", "InvalidPwd")
- assert.Error(t, err)
- }()
- go func() {
- _, err := bAgent.Dial(context.TODO(), "InvalidFrag", "InvalidPwd")
- assert.Error(t, err)
- }()
- isChecking.Wait()
- isFailed.Wait()
- assert.NoError(t, aAgent.Close())
- assert.NoError(t, bAgent.Close())
- }
- func TestAgentRestart(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 30)
- defer lim.Stop()
- oneSecond := time.Second
- t.Run("Restart During Gather", func(t *testing.T) {
- connA, connB := pipe(&AgentConfig{
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- })
- ctx, cancel := context.WithCancel(context.Background())
- assert.NoError(t, connB.agent.OnConnectionStateChange(func(c ConnectionState) {
- if c == ConnectionStateFailed || c == ConnectionStateDisconnected {
- cancel()
- }
- }))
- connA.agent.gatheringState = GatheringStateGathering
- assert.NoError(t, connA.agent.Restart("", ""))
- <-ctx.Done()
- assert.NoError(t, connA.agent.Close())
- assert.NoError(t, connB.agent.Close())
- })
- t.Run("Restart When Closed", func(t *testing.T) {
- agent, err := NewAgent(&AgentConfig{})
- assert.NoError(t, err)
- assert.NoError(t, agent.Close())
- assert.Equal(t, ErrClosed, agent.Restart("", ""))
- })
- t.Run("Restart One Side", func(t *testing.T) {
- connA, connB := pipe(&AgentConfig{
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- })
- ctx, cancel := context.WithCancel(context.Background())
- assert.NoError(t, connB.agent.OnConnectionStateChange(func(c ConnectionState) {
- if c == ConnectionStateFailed || c == ConnectionStateDisconnected {
- cancel()
- }
- }))
- assert.NoError(t, connA.agent.Restart("", ""))
- <-ctx.Done()
- assert.NoError(t, connA.agent.Close())
- assert.NoError(t, connB.agent.Close())
- })
- t.Run("Restart Both Sides", func(t *testing.T) {
- // Get all addresses of candidates concatenated
- generateCandidateAddressStrings := func(candidates []Candidate, err error) (out string) {
- assert.NoError(t, err)
- for _, c := range candidates {
- out += c.Address() + ":"
- out += strconv.Itoa(c.Port())
- }
- return
- }
- // Store the original candidates, confirm that after we reconnect we have new pairs
- connA, connB := pipe(&AgentConfig{
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- })
- connAFirstCandidates := generateCandidateAddressStrings(connA.agent.GetLocalCandidates())
- connBFirstCandidates := generateCandidateAddressStrings(connB.agent.GetLocalCandidates())
- aNotifier, aConnected := onConnected()
- assert.NoError(t, connA.agent.OnConnectionStateChange(aNotifier))
- bNotifier, bConnected := onConnected()
- assert.NoError(t, connB.agent.OnConnectionStateChange(bNotifier))
- // Restart and Re-Signal
- assert.NoError(t, connA.agent.Restart("", ""))
- assert.NoError(t, connB.agent.Restart("", ""))
- // Exchange Candidates and Credentials
- ufrag, pwd, err := connB.agent.GetLocalUserCredentials()
- assert.NoError(t, err)
- assert.NoError(t, connA.agent.SetRemoteCredentials(ufrag, pwd))
- ufrag, pwd, err = connA.agent.GetLocalUserCredentials()
- assert.NoError(t, err)
- assert.NoError(t, connB.agent.SetRemoteCredentials(ufrag, pwd))
- gatherAndExchangeCandidates(connA.agent, connB.agent)
- // Wait until both have gone back to connected
- <-aConnected
- <-bConnected
- // Assert that we have new candidates each time
- assert.NotEqual(t, connAFirstCandidates, generateCandidateAddressStrings(connA.agent.GetLocalCandidates()))
- assert.NotEqual(t, connBFirstCandidates, generateCandidateAddressStrings(connB.agent.GetLocalCandidates()))
- assert.NoError(t, connA.agent.Close())
- assert.NoError(t, connB.agent.Close())
- })
- }
- func TestGetRemoteCredentials(t *testing.T) {
- var config AgentConfig
- a, err := NewAgent(&config)
- if err != nil {
- t.Fatalf("Error constructing ice.Agent: %v", err)
- }
- a.remoteUfrag = "remoteUfrag"
- a.remotePwd = "remotePwd"
- actualUfrag, actualPwd, err := a.GetRemoteUserCredentials()
- assert.NoError(t, err)
- assert.Equal(t, actualUfrag, a.remoteUfrag)
- assert.Equal(t, actualPwd, a.remotePwd)
- assert.NoError(t, a.Close())
- }
- func TestGetRemoteCandidates(t *testing.T) {
- var config AgentConfig
- a, err := NewAgent(&config)
- if err != nil {
- t.Fatalf("Error constructing ice.Agent: %v", err)
- }
- expectedCandidates := []Candidate{}
- for i := 0; i < 5; i++ {
- cfg := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 1000 + i,
- Component: 1,
- }
- cand, errCand := NewCandidateHost(&cfg)
- assert.NoError(t, errCand)
- expectedCandidates = append(expectedCandidates, cand)
- a.addRemoteCandidate(cand)
- }
- actualCandidates, err := a.GetRemoteCandidates()
- assert.NoError(t, err)
- assert.ElementsMatch(t, expectedCandidates, actualCandidates)
- assert.NoError(t, a.Close())
- }
- func TestGetLocalCandidates(t *testing.T) {
- var config AgentConfig
- a, err := NewAgent(&config)
- if err != nil {
- t.Fatalf("Error constructing ice.Agent: %v", err)
- }
- dummyConn := &net.UDPConn{}
- expectedCandidates := []Candidate{}
- for i := 0; i < 5; i++ {
- cfg := CandidateHostConfig{
- Network: "udp",
- Address: "192.168.0.2",
- Port: 1000 + i,
- Component: 1,
- }
- cand, errCand := NewCandidateHost(&cfg)
- assert.NoError(t, errCand)
- expectedCandidates = append(expectedCandidates, cand)
- err = a.addCandidate(context.Background(), cand, dummyConn)
- assert.NoError(t, err)
- }
- actualCandidates, err := a.GetLocalCandidates()
- assert.NoError(t, err)
- assert.ElementsMatch(t, expectedCandidates, actualCandidates)
- assert.NoError(t, a.Close())
- }
- func TestCloseInConnectionStateCallback(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- disconnectedDuration := time.Second
- failedDuration := time.Second
- KeepaliveInterval := time.Duration(0)
- CheckInterval := 500 * time.Millisecond
- cfg := &AgentConfig{
- Urls: []*stun.URI{},
- NetworkTypes: supportedNetworkTypes(),
- DisconnectedTimeout: &disconnectedDuration,
- FailedTimeout: &failedDuration,
- KeepaliveInterval: &KeepaliveInterval,
- CheckInterval: &CheckInterval,
- }
- aAgent, err := NewAgent(cfg)
- if err != nil {
- t.Error(err)
- }
- bAgent, err := NewAgent(cfg)
- if err != nil {
- t.Error(err)
- }
- isClosed := make(chan interface{})
- isConnected := make(chan interface{})
- err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
- switch c {
- case ConnectionStateConnected:
- <-isConnected
- assert.NoError(t, aAgent.Close())
- case ConnectionStateClosed:
- close(isClosed)
- default:
- }
- })
- if err != nil {
- t.Error(err)
- }
- connect(aAgent, bAgent)
- close(isConnected)
- <-isClosed
- assert.NoError(t, bAgent.Close())
- }
- func TestRunTaskInConnectionStateCallback(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- oneSecond := time.Second
- KeepaliveInterval := time.Duration(0)
- CheckInterval := 50 * time.Millisecond
- cfg := &AgentConfig{
- Urls: []*stun.URI{},
- NetworkTypes: supportedNetworkTypes(),
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- KeepaliveInterval: &KeepaliveInterval,
- CheckInterval: &CheckInterval,
- }
- aAgent, err := NewAgent(cfg)
- check(err)
- bAgent, err := NewAgent(cfg)
- check(err)
- isComplete := make(chan interface{})
- err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
- if c == ConnectionStateConnected {
- _, _, errCred := aAgent.GetLocalUserCredentials()
- assert.NoError(t, errCred)
- assert.NoError(t, aAgent.Restart("", ""))
- close(isComplete)
- }
- })
- if err != nil {
- t.Error(err)
- }
- connect(aAgent, bAgent)
- <-isComplete
- assert.NoError(t, aAgent.Close())
- assert.NoError(t, bAgent.Close())
- }
- func TestRunTaskInSelectedCandidatePairChangeCallback(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 5)
- defer lim.Stop()
- oneSecond := time.Second
- KeepaliveInterval := time.Duration(0)
- CheckInterval := 50 * time.Millisecond
- cfg := &AgentConfig{
- Urls: []*stun.URI{},
- NetworkTypes: supportedNetworkTypes(),
- DisconnectedTimeout: &oneSecond,
- FailedTimeout: &oneSecond,
- KeepaliveInterval: &KeepaliveInterval,
- CheckInterval: &CheckInterval,
- }
- aAgent, err := NewAgent(cfg)
- check(err)
- bAgent, err := NewAgent(cfg)
- check(err)
- isComplete := make(chan interface{})
- isTested := make(chan interface{})
- if err = aAgent.OnSelectedCandidatePairChange(func(Candidate, Candidate) {
- go func() {
- _, _, errCred := aAgent.GetLocalUserCredentials()
- assert.NoError(t, errCred)
- close(isTested)
- }()
- }); err != nil {
- t.Error(err)
- }
- if err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
- if c == ConnectionStateConnected {
- close(isComplete)
- }
- }); err != nil {
- t.Error(err)
- }
- connect(aAgent, bAgent)
- <-isComplete
- <-isTested
- assert.NoError(t, aAgent.Close())
- assert.NoError(t, bAgent.Close())
- }
- // Assert that a Lite agent goes to disconnected and failed
- func TestLiteLifecycle(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 30)
- defer lim.Stop()
- aNotifier, aConnected := onConnected()
- aAgent, err := NewAgent(&AgentConfig{
- NetworkTypes: supportedNetworkTypes(),
- MulticastDNSMode: MulticastDNSModeDisabled,
- })
- require.NoError(t, err)
- require.NoError(t, aAgent.OnConnectionStateChange(aNotifier))
- disconnectedDuration := time.Second
- failedDuration := time.Second
- KeepaliveInterval := time.Duration(0)
- CheckInterval := 500 * time.Millisecond
- bAgent, err := NewAgent(&AgentConfig{
- Lite: true,
- CandidateTypes: []CandidateType{CandidateTypeHost},
- NetworkTypes: supportedNetworkTypes(),
- MulticastDNSMode: MulticastDNSModeDisabled,
- DisconnectedTimeout: &disconnectedDuration,
- FailedTimeout: &failedDuration,
- KeepaliveInterval: &KeepaliveInterval,
- CheckInterval: &CheckInterval,
- })
- require.NoError(t, err)
- bConnected := make(chan interface{})
- bDisconnected := make(chan interface{})
- bFailed := make(chan interface{})
- require.NoError(t, bAgent.OnConnectionStateChange(func(c ConnectionState) {
- switch c {
- case ConnectionStateConnected:
- close(bConnected)
- case ConnectionStateDisconnected:
- close(bDisconnected)
- case ConnectionStateFailed:
- close(bFailed)
- default:
- }
- }))
- connectWithVNet(bAgent, aAgent)
- <-aConnected
- <-bConnected
- assert.NoError(t, aAgent.Close())
- <-bDisconnected
- <-bFailed
- assert.NoError(t, bAgent.Close())
- }
- func TestNilCandidate(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- assert.NoError(t, err)
- assert.NoError(t, a.AddRemoteCandidate(nil))
- assert.NoError(t, a.Close())
- }
- func TestNilCandidatePair(t *testing.T) {
- a, err := NewAgent(&AgentConfig{})
- assert.NoError(t, err)
- a.setSelectedPair(nil)
- assert.NoError(t, a.Close())
- }
- func TestGetSelectedCandidatePair(t *testing.T) {
- report := test.CheckRoutines(t)
- defer report()
- lim := test.TimeOut(time.Second * 30)
- defer lim.Stop()
- wan, err := vnet.NewRouter(&vnet.RouterConfig{
- CIDR: "0.0.0.0/0",
- LoggerFactory: logging.NewDefaultLoggerFactory(),
- })
- assert.NoError(t, err)
- net, err := vnet.NewNet(&vnet.NetConfig{
- StaticIPs: []string{"192.168.0.1"},
- })
- assert.NoError(t, err)
- assert.NoError(t, wan.AddNet(net))
- assert.NoError(t, wan.Start())
- cfg := &AgentConfig{
- NetworkTypes: supportedNetworkTypes(),
- Net: net,
- }
- aAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- bAgent, err := NewAgent(cfg)
- assert.NoError(t, err)
- aAgentPair, err := aAgent.GetSelectedCandidatePair()
- assert.NoError(t, err)
- assert.Nil(t, aAgentPair)
- bAgentPair, err := bAgent.GetSelectedCandidatePair()
- assert.NoError(t, err)
- assert.Nil(t, bAgentPair)
- connect(aAgent, bAgent)
- aAgentPair, err = aAgent.GetSelectedCandidatePair()
- assert.NoError(t, err)
- assert.NotNil(t, aAgentPair)
- bAgentPair, err = bAgent.GetSelectedCandidatePair()
- assert.NoError(t, err)
- assert.NotNil(t, bAgentPair)
- assert.True(t, bAgentPair.Local.Equal(aAgentPair.Remote))
- assert.True(t, bAgentPair.Remote.Equal(aAgentPair.Local))
- assert.NoError(t, wan.Stop())
- 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, err := vnet.NewNet(&vnet.NetConfig{
- StaticIPs: []string{"192.168.0.1"},
- })
- assert.NoError(t, err)
- assert.NoError(t, wan.AddNet(net0))
- net1, err := vnet.NewNet(&vnet.NetConfig{
- StaticIPs: []string{"192.168.0.2", "192.168.0.3", "192.168.0.4"},
- })
- assert.NoError(t, err)
- assert.NoError(t, wan.AddNet(net1))
- assert.NoError(t, wan.Start())
- aNotifier, aConnected := onConnected()
- bNotifier, bConnected := onConnected()
- KeepaliveInterval := time.Hour
- cfg0 := &AgentConfig{
- NetworkTypes: []NetworkType{NetworkTypeUDP4, NetworkTypeUDP6},
- 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: []NetworkType{NetworkTypeUDP4, NetworkTypeUDP6},
- 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 //nolint:forcetypeassert
- 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
- }
- }
|