// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT //go:build !js // +build !js package ice import ( "context" "fmt" "net" "testing" "github.com/pion/logging" "github.com/pion/stun/v3" "github.com/pion/transport/v3/test" "github.com/pion/transport/v3/vnet" "github.com/stretchr/testify/require" ) func TestVNetGather(t *testing.T) { //nolint:cyclop defer test.CheckRoutines(t)() loggerFactory := logging.NewDefaultLoggerFactory() t.Run("No local IP address", func(t *testing.T) { n, err := vnet.NewNet(&vnet.NetConfig{}) require.NoError(t, err) a, err := NewAgent(&AgentConfig{ Net: n, }) require.NoError(t, err) defer func() { require.NoError(t, a.Close()) }() _, localIPs, err := localInterfaces(a.net, a.interfaceFilter, a.ipFilter, []NetworkType{NetworkTypeUDP4}, false) require.Len(t, localIPs, 0) require.NoError(t, err) }) t.Run("Gather a dynamic IP address", func(t *testing.T) { cider := "1.2.3.0/24" _, ipNet, err := net.ParseCIDR(cider) require.NoError(t, err) router, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: cider, LoggerFactory: loggerFactory, }) require.NoError(t, err) nw, err := vnet.NewNet(&vnet.NetConfig{}) require.NoError(t, err) require.NoError(t, router.AddNet(nw)) a, err := NewAgent(&AgentConfig{ Net: nw, }) require.NoError(t, err) defer func() { require.NoError(t, a.Close()) }() _, localAddrs, err := localInterfaces(a.net, a.interfaceFilter, a.ipFilter, []NetworkType{NetworkTypeUDP4}, false) require.Len(t, localAddrs, 1) require.NoError(t, err) for _, addr := range localAddrs { require.False(t, addr.IsLoopback()) require.True(t, ipNet.Contains(addr.AsSlice())) } }) t.Run("listenUDP", func(t *testing.T) { router, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) require.NoError(t, err) nw, err := vnet.NewNet(&vnet.NetConfig{}) require.NoError(t, err) require.NoError(t, router.AddNet(nw)) agent, err := NewAgent(&AgentConfig{Net: nw}) require.NoError(t, err) defer func() { require.NoError(t, agent.Close()) }() _, localAddrs, err := localInterfaces( agent.net, agent.interfaceFilter, agent.ipFilter, []NetworkType{NetworkTypeUDP4}, false, ) require.NotEqual(t, 0, len(localAddrs)) require.NoError(t, err) ip := localAddrs[0].AsSlice() conn, err := listenUDPInPortRange(agent.net, agent.log, 0, 0, udp, &net.UDPAddr{IP: ip, Port: 0}) require.NoError(t, err) require.NotNil(t, conn) require.NoError(t, conn.Close()) _, err = listenUDPInPortRange(agent.net, agent.log, 4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) require.ErrorIs(t, ErrPort, err) conn, err = listenUDPInPortRange(agent.net, agent.log, 5000, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) require.NoError(t, err) require.NotNil(t, conn) defer func() { require.NoError(t, conn.Close()) }() _, port, err := net.SplitHostPort(conn.LocalAddr().String()) require.NoError(t, err) require.Equal(t, "5000", port) }) } func TestVNetGatherWithNAT1To1(t *testing.T) { //nolint:cyclop defer test.CheckRoutines(t)() loggerFactory := logging.NewDefaultLoggerFactory() log := loggerFactory.NewLogger("test") t.Run("gather 1:1 NAT external IPs as host candidates", func(t *testing.T) { externalIP0 := "1.2.3.4" externalIP1 := "1.2.3.5" localIP0 := "10.0.0.1" localIP1 := "10.0.0.2" map0 := fmt.Sprintf("%s/%s", externalIP0, localIP0) map1 := fmt.Sprintf("%s/%s", externalIP1, localIP1) wan, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) require.NoError(t, err, "should succeed") lan, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "10.0.0.0/24", StaticIPs: []string{map0, map1}, NATType: &vnet.NATType{ Mode: vnet.NATModeNAT1To1, }, LoggerFactory: loggerFactory, }) require.NoError(t, err, "should succeed") err = wan.AddRouter(lan) require.NoError(t, err, "should succeed") nw, err := vnet.NewNet(&vnet.NetConfig{ StaticIPs: []string{localIP0, localIP1}, }) require.NoError(t, err) err = lan.AddNet(nw) require.NoError(t, err, "should succeed") agent, err := NewAgent(&AgentConfig{ NetworkTypes: []NetworkType{ NetworkTypeUDP4, }, NAT1To1IPs: []string{map0, map1}, Net: nw, }) require.NoError(t, err, "should succeed") defer func() { require.NoError(t, agent.Close()) }() done := make(chan struct{}) err = agent.OnCandidate(func(c Candidate) { if c == nil { close(done) } }) require.NoError(t, err, "should succeed") err = agent.GatherCandidates() require.NoError(t, err, "should succeed") log.Debug("Wait until gathering is complete...") <-done log.Debug("Gathering is done") candidates, err := agent.GetLocalCandidates() require.NoError(t, err, "should succeed") require.Len(t, candidates, 2) lAddr := [2]*net.UDPAddr{nil, nil} for i, candi := range candidates { lAddr[i] = candi.(*CandidateHost).conn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert require.Equal(t, candi.Port(), lAddr[i].Port) } if candidates[0].Address() == externalIP0 { //nolint:nestif require.Equal(t, candidates[1].Address(), externalIP1) require.Equal(t, lAddr[0].IP.String(), localIP0) require.Equal(t, lAddr[1].IP.String(), localIP1) } else if candidates[0].Address() == externalIP1 { require.Equal(t, candidates[1].Address(), externalIP0) require.Equal(t, lAddr[0].IP.String(), localIP1) require.Equal(t, lAddr[1].IP.String(), localIP0) } }) t.Run("gather 1:1 NAT external IPs as srflx candidates", func(t *testing.T) { wan, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) require.NoError(t, err, "should succeed") lan, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "10.0.0.0/24", StaticIPs: []string{ "1.2.3.4/10.0.0.1", }, NATType: &vnet.NATType{ Mode: vnet.NATModeNAT1To1, }, LoggerFactory: loggerFactory, }) require.NoError(t, err, "should succeed") err = wan.AddRouter(lan) require.NoError(t, err, "should succeed") nw, err := vnet.NewNet(&vnet.NetConfig{ StaticIPs: []string{ "10.0.0.1", }, }) require.NoError(t, err) err = lan.AddNet(nw) require.NoError(t, err, "should succeed") agent, err := NewAgent(&AgentConfig{ NetworkTypes: []NetworkType{ NetworkTypeUDP4, }, NAT1To1IPs: []string{ "1.2.3.4", }, NAT1To1IPCandidateType: CandidateTypeServerReflexive, Net: nw, }) require.NoError(t, err, "should succeed") defer func() { require.NoError(t, agent.Close()) }() done := make(chan struct{}) err = agent.OnCandidate(func(c Candidate) { if c == nil { close(done) } }) require.NoError(t, err, "should succeed") err = agent.GatherCandidates() require.NoError(t, err, "should succeed") log.Debug("Wait until gathering is complete...") <-done log.Debug("Gathering is done") candidates, err := agent.GetLocalCandidates() require.NoError(t, err, "should succeed") require.Len(t, candidates, 2) var candiHost *CandidateHost var candiSrflx *CandidateServerReflexive for _, candidate := range candidates { switch candi := candidate.(type) { case *CandidateHost: candiHost = candi case *CandidateServerReflexive: candiSrflx = candi default: t.Fatal("Unexpected candidate type") // nolint } } require.NotNil(t, candiHost, "should not be nil") require.Equal(t, "10.0.0.1", candiHost.Address(), "should match") require.NotNil(t, candiSrflx, "should not be nil") require.Equal(t, "1.2.3.4", candiSrflx.Address(), "should match") }) } func TestVNetGatherWithInterfaceFilter(t *testing.T) { defer test.CheckRoutines(t)() loggerFactory := logging.NewDefaultLoggerFactory() router, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) require.NoError(t, err) nw, err := vnet.NewNet(&vnet.NetConfig{}) require.NoError(t, err) require.NoError(t, router.AddNet(nw)) t.Run("InterfaceFilter should exclude the interface", func(t *testing.T) { agent, err := NewAgent(&AgentConfig{ Net: nw, InterfaceFilter: func(interfaceName string) (keep bool) { require.Equal(t, "eth0", interfaceName) return false }, }) require.NoError(t, err) defer func() { require.NoError(t, agent.Close()) }() _, localIPs, err := localInterfaces( agent.net, agent.interfaceFilter, agent.ipFilter, []NetworkType{NetworkTypeUDP4}, false, ) require.NoError(t, err) require.Len(t, localIPs, 0) }) t.Run("IPFilter should exclude the IP", func(t *testing.T) { agent, err := NewAgent(&AgentConfig{ Net: nw, IPFilter: func(ip net.IP) (keep bool) { require.Equal(t, net.IP{1, 2, 3, 1}, ip) return false }, }) require.NoError(t, err) defer func() { require.NoError(t, agent.Close()) }() _, localIPs, err := localInterfaces( agent.net, agent.interfaceFilter, agent.ipFilter, []NetworkType{NetworkTypeUDP4}, false, ) require.NoError(t, err) require.Len(t, localIPs, 0) }) t.Run("InterfaceFilter should not exclude the interface", func(t *testing.T) { agent, err := NewAgent(&AgentConfig{ Net: nw, InterfaceFilter: func(interfaceName string) (keep bool) { require.Equal(t, "eth0", interfaceName) return true }, }) require.NoError(t, err) defer func() { require.NoError(t, agent.Close()) }() _, localIPs, err := localInterfaces( agent.net, agent.interfaceFilter, agent.ipFilter, []NetworkType{NetworkTypeUDP4}, false, ) require.NoError(t, err) require.Len(t, localIPs, 1) }) } func TestVNetGather_TURNConnectionLeak(t *testing.T) { defer test.CheckRoutines(t)() turnServerURL := &stun.URI{ Scheme: stun.SchemeTypeTURN, Host: vnetSTUNServerIP, Port: vnetSTUNServerPort, Username: "user", Password: "pass", Proto: stun.ProtoTypeUDP, } // buildVNet with a Symmetric NATs for both LANs natType := &vnet.NATType{ MappingBehavior: vnet.EndpointAddrPortDependent, FilteringBehavior: vnet.EndpointAddrPortDependent, } v, err := buildVNet(natType, natType) require.NoError(t, err, "should succeed") defer v.close() cfg0 := &AgentConfig{ Urls: []*stun.URI{ turnServerURL, }, NetworkTypes: supportedNetworkTypes(), MulticastDNSMode: MulticastDNSModeDisabled, NAT1To1IPs: []string{vnetGlobalIPA}, Net: v.net0, } aAgent, err := NewAgent(cfg0) require.NoError(t, err, "should succeed") defer func() { // Assert relay conn leak on close. require.NoError(t, aAgent.Close()) }() aAgent.gatherCandidatesRelay(context.Background(), []*stun.URI{turnServerURL}) }