// +build !js package ice import ( "context" "errors" "fmt" "net" "testing" "github.com/pion/logging" "github.com/pion/transport/test" "github.com/pion/transport/vnet" "github.com/stretchr/testify/assert" ) func TestVNetGather(t *testing.T) { report := test.CheckRoutines(t) defer report() loggerFactory := logging.NewDefaultLoggerFactory() // log := loggerFactory.NewLogger("test") t.Run("No local IP address", func(t *testing.T) { a, err := NewAgent(&AgentConfig{ Net: vnet.NewNet(&vnet.NetConfig{}), }) assert.NoError(t, err) localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if len(localIPs) > 0 { t.Fatal("should return no local IP") } else if err != nil { t.Fatal(err) } assert.NoError(t, a.Close()) }) t.Run("Gather a dynamic IP address", func(t *testing.T) { cider := "1.2.3.0/24" _, ipNet, err := net.ParseCIDR(cider) if err != nil { t.Fatalf("Failed to parse CIDR: %s", err) } r, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: cider, LoggerFactory: loggerFactory, }) if err != nil { t.Fatalf("Failed to create a router: %s", err) } nw := vnet.NewNet(&vnet.NetConfig{}) if nw == nil { t.Fatalf("Failed to create a Net: %s", err) } err = r.AddNet(nw) if err != nil { t.Fatalf("Failed to add a Net to the router: %s", err) } a, err := NewAgent(&AgentConfig{ Net: nw, }) assert.NoError(t, err) localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if len(localIPs) == 0 { t.Fatal("should have one local IP") } else if err != nil { t.Fatal(err) } for _, ip := range localIPs { if ip.IsLoopback() { t.Fatal("should not return loopback IP") } if !ipNet.Contains(ip) { t.Fatal("should be contained in the CIDR") } } assert.NoError(t, a.Close()) }) t.Run("listenUDP", func(t *testing.T) { r, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) if err != nil { t.Fatalf("Failed to create a router: %s", err) } nw := vnet.NewNet(&vnet.NetConfig{}) if nw == nil { t.Fatalf("Failed to create a Net: %s", err) } err = r.AddNet(nw) if err != nil { t.Fatalf("Failed to add a Net to the router: %s", err) } a, err := NewAgent(&AgentConfig{Net: nw}) if err != nil { t.Fatalf("Failed to create agent: %s", err) } localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if len(localIPs) == 0 { t.Fatal("localInterfaces found no interfaces, unable to test") } else if err != nil { t.Fatal(err) } ip := localIPs[0] conn, err := listenUDPInPortRange(a.net, a.log, 0, 0, udp, &net.UDPAddr{IP: ip, Port: 0}) if err != nil { t.Fatalf("listenUDP error with no port restriction %v", err) } else if conn == nil { t.Fatalf("listenUDP error with no port restriction return a nil conn") } err = conn.Close() if err != nil { t.Fatalf("failed to close conn") } _, err = listenUDPInPortRange(a.net, a.log, 4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) if !errors.Is(err, ErrPort) { t.Fatal("listenUDP with invalid port range did not return ErrPort") } conn, err = listenUDPInPortRange(a.net, a.log, 5000, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) if err != nil { t.Fatalf("listenUDP error with no port restriction %v", err) } else if conn == nil { t.Fatalf("listenUDP error with no port restriction return a nil conn") } _, port, err := net.SplitHostPort(conn.LocalAddr().String()) if err != nil { t.Fatal(err) } else if port != "5000" { t.Fatalf("listenUDP with port restriction of 5000 listened on incorrect port (%s)", port) } assert.NoError(t, conn.Close()) assert.NoError(t, a.Close()) }) } func TestVNetGatherWithNAT1To1(t *testing.T) { report := test.CheckRoutines(t) defer report() 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, }) assert.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, }) assert.NoError(t, err, "should succeed") err = wan.AddRouter(lan) assert.NoError(t, err, "should succeed") nw := vnet.NewNet(&vnet.NetConfig{ StaticIPs: []string{localIP0, localIP1}, }) if nw == nil { t.Fatalf("Failed to create a Net: %s", err) } err = lan.AddNet(nw) assert.NoError(t, err, "should succeed") a, err := NewAgent(&AgentConfig{ NetworkTypes: []NetworkType{ NetworkTypeUDP4, }, NAT1To1IPs: []string{map0, map1}, Net: nw, }) assert.NoError(t, err, "should succeed") defer a.Close() // nolint:errcheck done := make(chan struct{}) err = a.OnCandidate(func(c Candidate) { if c == nil { close(done) } }) assert.NoError(t, err, "should succeed") err = a.GatherCandidates() assert.NoError(t, err, "should succeed") log.Debug("wait for gathering is done...") <-done log.Debug("gathering is done") candidates, err := a.GetLocalCandidates() assert.NoError(t, err, "should succeed") if len(candidates) != 2 { t.Fatal("There must be two candidates") } laddr := [2]*net.UDPAddr{nil, nil} for i, candi := range candidates { laddr[i] = candi.(*CandidateHost).conn.LocalAddr().(*net.UDPAddr) if candi.Port() != laddr[i].Port { t.Fatalf("Unexpected candidate port: %d", candi.Port()) } } if candidates[0].Address() == externalIP0 { if candidates[1].Address() != externalIP1 { t.Fatalf("Unexpected candidate IP: %s", candidates[1].Address()) } if laddr[0].IP.String() != localIP0 { t.Fatalf("Unexpected listen IP: %s", laddr[0].IP.String()) } if laddr[1].IP.String() != localIP1 { t.Fatalf("Unexpected listen IP: %s", laddr[1].IP.String()) } } else if candidates[0].Address() == externalIP1 { if candidates[1].Address() != externalIP0 { t.Fatalf("Unexpected candidate IP: %s", candidates[1].Address()) } if laddr[0].IP.String() != localIP1 { t.Fatalf("Unexpected listen IP: %s", laddr[0].IP.String()) } if laddr[1].IP.String() != localIP0 { t.Fatalf("Unexpected listen IP: %s", laddr[1].IP.String()) } } }) 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, }) assert.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, }) assert.NoError(t, err, "should succeed") err = wan.AddRouter(lan) assert.NoError(t, err, "should succeed") nw := vnet.NewNet(&vnet.NetConfig{ StaticIPs: []string{ "10.0.0.1", }, }) if nw == nil { t.Fatalf("Failed to create a Net: %s", err) } err = lan.AddNet(nw) assert.NoError(t, err, "should succeed") a, err := NewAgent(&AgentConfig{ NetworkTypes: []NetworkType{ NetworkTypeUDP4, }, NAT1To1IPs: []string{ "1.2.3.4", }, NAT1To1IPCandidateType: CandidateTypeServerReflexive, Net: nw, }) assert.NoError(t, err, "should succeed") defer a.Close() // nolint:errcheck done := make(chan struct{}) err = a.OnCandidate(func(c Candidate) { if c == nil { close(done) } }) assert.NoError(t, err, "should succeed") err = a.GatherCandidates() assert.NoError(t, err, "should succeed") log.Debug("wait for gathering is done...") <-done log.Debug("gathering is done") candidates, err := a.GetLocalCandidates() assert.NoError(t, err, "should succeed") if len(candidates) != 2 { t.Fatalf("Expected two candidates. actually %d", len(candidates)) } 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") } } assert.NotNil(t, candiHost, "should not be nil") assert.Equal(t, "10.0.0.1", candiHost.Address(), "should match") assert.NotNil(t, candiSrflx, "should not be nil") assert.Equal(t, "1.2.3.4", candiSrflx.Address(), "should match") }) } func TestVNetGatherWithInterfaceFilter(t *testing.T) { report := test.CheckRoutines(t) defer report() loggerFactory := logging.NewDefaultLoggerFactory() r, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "1.2.3.0/24", LoggerFactory: loggerFactory, }) if err != nil { t.Fatalf("Failed to create a router: %s", err) } nw := vnet.NewNet(&vnet.NetConfig{}) if nw == nil { t.Fatalf("Failed to create a Net: %s", err) } if err = r.AddNet(nw); err != nil { t.Fatalf("Failed to add a Net to the router: %s", err) } t.Run("InterfaceFilter should exclude the interface", func(t *testing.T) { a, err := NewAgent(&AgentConfig{ Net: nw, InterfaceFilter: func(interfaceName string) bool { assert.Equal(t, "eth0", interfaceName) return false }, }) assert.NoError(t, err) localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if err != nil { t.Fatal(err) } else if len(localIPs) != 0 { t.Fatal("InterfaceFilter should have excluded everything") } assert.NoError(t, a.Close()) }) t.Run("InterfaceFilter should not exclude the interface", func(t *testing.T) { a, err := NewAgent(&AgentConfig{ Net: nw, InterfaceFilter: func(interfaceName string) bool { assert.Equal(t, "eth0", interfaceName) return true }, }) assert.NoError(t, err) localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if err != nil { t.Fatal(err) } else if len(localIPs) == 0 { t.Fatal("InterfaceFilter should not have excluded anything") } assert.NoError(t, a.Close()) }) } func TestVNetGather_TURNConnectionLeak(t *testing.T) { report := test.CheckRoutines(t) defer report() turnServerURL := &URL{ Scheme: SchemeTypeTURN, Host: vnetSTUNServerIP, Port: vnetSTUNServerPort, Username: "user", Password: "pass", Proto: ProtoTypeUDP, } // buildVNet with a Symmetric NATs for both LANs natType := &vnet.NATType{ MappingBehavior: vnet.EndpointAddrPortDependent, FilteringBehavior: vnet.EndpointAddrPortDependent, } v, err := buildVNet(natType, natType) if !assert.NoError(t, err, "should succeed") { return } defer v.close() cfg0 := &AgentConfig{ Urls: []*URL{ turnServerURL, }, NetworkTypes: supportedNetworkTypes(), MulticastDNSMode: MulticastDNSModeDisabled, NAT1To1IPs: []string{vnetGlobalIPA}, Net: v.net0, } aAgent, err := NewAgent(cfg0) if !assert.NoError(t, err, "should succeed") { return } aAgent.gatherCandidatesRelay(context.Background(), []*URL{turnServerURL}) // Assert relay conn leak on close. assert.NoError(t, aAgent.Close()) }