diff --git a/gather.go b/gather.go index 77ce51a..24723bf 100644 --- a/gather.go +++ b/gather.go @@ -2,12 +2,10 @@ package ice import ( "fmt" - "math/rand" "net" "sync" "time" - "github.com/pion/transport/vnet" "github.com/pion/turn/v2" ) @@ -15,106 +13,6 @@ const ( stunGatherTimeout = time.Second * 5 ) -func (a *Agent) localInterfaces(networkTypes []NetworkType) ([]net.IP, error) { - ips := []net.IP{} - ifaces, err := a.net.Interfaces() - if err != nil { - return ips, err - } - - var IPv4Requested, IPv6Requested bool - for _, typ := range networkTypes { - if typ.IsIPv4() { - IPv4Requested = true - } - - if typ.IsIPv6() { - IPv6Requested = true - } - } - - for _, iface := range ifaces { - if iface.Flags&net.FlagUp == 0 { - continue // interface down - } - if iface.Flags&net.FlagLoopback != 0 { - continue // loopback interface - } - - if a.interfaceFilter != nil && !a.interfaceFilter(iface.Name) { - continue - } - - addrs, err := iface.Addrs() - if err != nil { - continue - } - - for _, addr := range addrs { - var ip net.IP - switch addr := addr.(type) { - case *net.IPNet: - ip = addr.IP - case *net.IPAddr: - ip = addr.IP - } - if ip == nil || ip.IsLoopback() { - continue - } - - if ipv4 := ip.To4(); ipv4 == nil { - if !IPv6Requested { - continue - } else if !isSupportedIPv6(ip) { - continue - } - } else if !IPv4Requested { - continue - } - - ips = append(ips, ip) - } - } - return ips, nil -} - -func (a *Agent) listenUDP(portMax, portMin int, network string, laddr *net.UDPAddr) (vnet.UDPPacketConn, error) { - if (laddr.Port != 0) || ((portMin == 0) && (portMax == 0)) { - return a.net.ListenUDP(network, laddr) - } - var i, j int - i = portMin - if i == 0 { - i = 1 - } - j = portMax - if j == 0 { - j = 0xFFFF - } - if i > j { - return nil, ErrPort - } - - portStart := rand.Intn(j-i+1) + i - portCurrent := portStart - for { - laddr = &net.UDPAddr{IP: laddr.IP, Port: portCurrent} - c, e := a.net.ListenUDP(network, laddr) - if e == nil { - return c, e - } - a.log.Debugf("failed to listen %s: %v", laddr.String(), e) - portCurrent++ - if portCurrent > j { - portCurrent = i - } - if portCurrent == portStart { - break - } - } - return nil, ErrPort -} - // GatherCandidates initiates the trickle based gathering process. func (a *Agent) GatherCandidates() error { gatherErrChan := make(chan error, 1) @@ -183,7 +81,7 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) { var wg sync.WaitGroup defer wg.Wait() - localIPs, err := a.localInterfaces(networkTypes) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, networkTypes) if err != nil { a.log.Warnf("failed to iterate local interfaces, host candidates will not be gathered %s", err) return @@ -203,7 +101,7 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) { for _, network := range supportedNetworks { go func(network string, ip, mappedIP net.IP) { defer wg.Done() - conn, err := a.listenUDP(int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: ip, Port: 0}) + conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: ip, Port: 0}) if err != nil { a.log.Warnf("could not listen %s %s\n", network, ip) return @@ -274,7 +172,7 @@ func (a *Agent) gatherCandidatesSrflx(urls []*URL, networkTypes []NetworkType) { continue } - conn, err := a.listenUDP(int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) + conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) if err != nil { a.log.Warnf("Failed to listen for %s: %v\n", serverAddr.String(), err) continue @@ -318,7 +216,7 @@ func (a *Agent) gatherCandidatesSrflx(urls []*URL, networkTypes []NetworkType) { } } } else if a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeServerReflexive { - conn, err := a.listenUDP(int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) + conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) if err != nil { a.log.Warnf("Failed to listen %s: %v\n", network, err) continue diff --git a/gather_test.go b/gather_test.go index 40e9bea..6f06e72 100644 --- a/gather_test.go +++ b/gather_test.go @@ -16,20 +16,20 @@ func TestListenUDP(t *testing.T) { a, err := NewAgent(&AgentConfig{}) assert.NoError(t, err) - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) assert.NotEqual(t, len(localIPs), 0, "localInterfaces found no interfaces, unable to test") assert.NoError(t, err) ip := localIPs[0] - conn, err := a.listenUDP(0, 0, udp, &net.UDPAddr{IP: ip, Port: 0}) + conn, err := listenUDPInPortRange(a.net, a.log, 0, 0, udp, &net.UDPAddr{IP: ip, Port: 0}) assert.NoError(t, err, "listenUDP error with no port restriction") assert.NotNil(t, conn, "listenUDP error with no port restriction return a nil conn") - _, err = a.listenUDP(4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) + _, err = listenUDPInPortRange(a.net, a.log, 4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) assert.Equal(t, err, ErrPort, "listenUDP with invalid port range did not return ErrPort") - conn, err = a.listenUDP(5000, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) + conn, err = listenUDPInPortRange(a.net, a.log, 5000, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) assert.NoError(t, err, "listenUDP error with no port restriction") assert.NotNil(t, conn, "listenUDP error with no port restriction return a nil conn") @@ -43,7 +43,7 @@ func TestListenUDP(t *testing.T) { result := make([]int, 0, total) portRange := make([]int, 0, total) for i := 0; i < total; i++ { - conn, err = a.listenUDP(portMax, portMin, udp, &net.UDPAddr{IP: ip, Port: 0}) + conn, err = listenUDPInPortRange(a.net, a.log, portMax, portMin, udp, &net.UDPAddr{IP: ip, Port: 0}) assert.NoError(t, err, "listenUDP error with no port restriction") assert.NotNil(t, conn, "listenUDP error with no port restriction return a nil conn") @@ -65,7 +65,7 @@ func TestListenUDP(t *testing.T) { if !reflect.DeepEqual(result, portRange) { t.Fatalf("listenUDP with port restriction [%d, %d], got:%v, want:%v", portMin, portMax, result, portRange) } - _, err = a.listenUDP(portMax, portMin, udp, &net.UDPAddr{IP: ip, Port: 0}) + _, err = listenUDPInPortRange(a.net, a.log, portMax, portMin, udp, &net.UDPAddr{IP: ip, Port: 0}) assert.Equal(t, err, ErrPort, "listenUDP with port restriction [%d, %d], did not return ErrPort", portMin, portMax) assert.NoError(t, a.Close()) diff --git a/gather_vnet_test.go b/gather_vnet_test.go index 6ad9678..aaea4b1 100644 --- a/gather_vnet_test.go +++ b/gather_vnet_test.go @@ -26,7 +26,7 @@ func TestVNetGather(t *testing.T) { }) assert.NoError(t, err) - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if len(localIPs) > 0 { t.Fatal("should return no local IP") } else if err != nil { @@ -66,7 +66,7 @@ func TestVNetGather(t *testing.T) { }) assert.NoError(t, err) - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if len(localIPs) == 0 { t.Fatal("should have one local IP") } else if err != nil { @@ -109,7 +109,7 @@ func TestVNetGather(t *testing.T) { t.Fatalf("Failed to create agent: %s", err) } - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + 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 { @@ -118,7 +118,7 @@ func TestVNetGather(t *testing.T) { ip := localIPs[0] - conn, err := a.listenUDP(0, 0, udp, &net.UDPAddr{IP: ip, Port: 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 { @@ -129,12 +129,12 @@ func TestVNetGather(t *testing.T) { t.Fatalf("failed to close conn") } - _, err = a.listenUDP(4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) + _, err = listenUDPInPortRange(a.net, a.log, 4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) if err != ErrPort { t.Fatal("listenUDP with invalid port range did not return ErrPort") } - conn, err = a.listenUDP(5000, 5000, udp, &net.UDPAddr{IP: ip, Port: 0}) + 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 { @@ -384,7 +384,7 @@ func TestVNetGatherWithInterfaceFilter(t *testing.T) { }) assert.NoError(t, err) - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if err != nil { t.Fatal(err) } else if len(localIPs) != 0 { @@ -404,7 +404,7 @@ func TestVNetGatherWithInterfaceFilter(t *testing.T) { }) assert.NoError(t, err) - localIPs, err := a.localInterfaces([]NetworkType{NetworkTypeUDP4}) + localIPs, err := localInterfaces(a.net, a.interfaceFilter, []NetworkType{NetworkTypeUDP4}) if err != nil { t.Fatal(err) } else if len(localIPs) == 0 { diff --git a/util.go b/util.go index d9afff0..fae3b87 100644 --- a/util.go +++ b/util.go @@ -7,7 +7,9 @@ import ( "sync/atomic" "time" + "github.com/pion/logging" "github.com/pion/stun" + "github.com/pion/transport/vnet" ) type atomicError struct{ v atomic.Value } @@ -144,3 +146,103 @@ func stunRequest(read func([]byte) (int, error), write func([]byte) (int, error) } return res, nil } + +func localInterfaces(vnet *vnet.Net, interfaceFilter func(string) bool, networkTypes []NetworkType) ([]net.IP, error) { + ips := []net.IP{} + ifaces, err := vnet.Interfaces() + if err != nil { + return ips, err + } + + var IPv4Requested, IPv6Requested bool + for _, typ := range networkTypes { + if typ.IsIPv4() { + IPv4Requested = true + } + + if typ.IsIPv6() { + IPv6Requested = true + } + } + + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + + if interfaceFilter != nil && !interfaceFilter(iface.Name) { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + var ip net.IP + switch addr := addr.(type) { + case *net.IPNet: + ip = addr.IP + case *net.IPAddr: + ip = addr.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + + if ipv4 := ip.To4(); ipv4 == nil { + if !IPv6Requested { + continue + } else if !isSupportedIPv6(ip) { + continue + } + } else if !IPv4Requested { + continue + } + + ips = append(ips, ip) + } + } + return ips, nil +} + +func listenUDPInPortRange(vnet *vnet.Net, log logging.LeveledLogger, portMax, portMin int, network string, laddr *net.UDPAddr) (vnet.UDPPacketConn, error) { + if (laddr.Port != 0) || ((portMin == 0) && (portMax == 0)) { + return vnet.ListenUDP(network, laddr) + } + var i, j int + i = portMin + if i == 0 { + i = 1 + } + j = portMax + if j == 0 { + j = 0xFFFF + } + if i > j { + return nil, ErrPort + } + + portStart := rand.Intn(j-i+1) + i + portCurrent := portStart + for { + laddr = &net.UDPAddr{IP: laddr.IP, Port: portCurrent} + c, e := vnet.ListenUDP(network, laddr) + if e == nil { + return c, e + } + log.Debugf("failed to listen %s: %v", laddr.String(), e) + portCurrent++ + if portCurrent > j { + portCurrent = i + } + if portCurrent == portStart { + break + } + } + return nil, ErrPort +}