Support 1:1 NAT option

Relates to pion/webrtc#835
This commit is contained in:
Yutaka Takeda
2019-09-24 11:51:07 -07:00
parent bd60240fdc
commit 867a355033
11 changed files with 1129 additions and 104 deletions
+86 -20
View File
@@ -83,9 +83,10 @@ type Agent struct {
// force candidate to be contacted immediately (instead of waiting for connectivityTicker)
forceCandidateContact chan bool
trickle bool
tieBreaker uint64
lite bool
trickle bool
tieBreaker uint64
lite bool
connectionState ConnectionState
gatheringState GatheringState
@@ -142,6 +143,9 @@ type Agent struct {
// LRU of outbound Binding request Transaction IDs
pendingBindingRequests []bindingRequest
// 1:1 D-NAT IP address mapping
extIPMapper *externalIPMapper
// State for closing
done chan struct{}
err atomicError
@@ -222,6 +226,20 @@ type AgentConfig struct {
// Lite agents do not perform connectivity check and only provide host candidates.
Lite bool
// NAT1To1IPCandidateType is used along with NAT1To1IPs to specify which candidate type
// the 1:1 NAT IP addresses should be mapped to.
// If unspecified or CandidateTypeHost, NAT1To1IPs are used to replace host candidate IPs.
// If CandidateTypeServerReflexive, it will insert a srflx candidate (as if it was dervied
// from a STUN server) with its port number being the one for the actual host candidate.
// Other values will result in an error.
NAT1To1IPCandidateType CandidateType
// NAT1To1IPs contains a list of public IP addresses that are to be used as a host
// candidate or srflx candidate. This is used typically for servers that are behind
// 1:1 D-NAT (e.g. AWS EC2 instances) and to eliminate the need of server reflexisive
// candidate gathering.
NAT1To1IPs []string
// HostAcceptanceMinWait specify a minimum wait time before selecting host candidates
HostAcceptanceMinWait *time.Duration
// HostAcceptanceMinWait specify a minimum wait time before selecting srflx candidates
@@ -342,13 +360,43 @@ func NewAgent(config *AgentConfig) (*Agent, error) {
if a.net == nil {
a.net = vnet.NewNet(nil)
} else {
} else if a.net.IsVirtual() {
a.log.Warn("vnet is enabled")
if a.mDNSMode != MulticastDNSModeDisabled {
a.log.Warn("vnet does not support mDNS yet")
}
}
a.initWithDefaults(config)
// Make sure the buffer doesn't grow indefinitely.
// NOTE: We actually won't get anywhere close to this limit.
// SRTP will constantly read from the endpoint and drop packets if it's full.
a.buffer.SetLimitSize(maxBufferSize)
if a.lite && (len(a.candidateTypes) != 1 || a.candidateTypes[0] != CandidateTypeHost) {
return nil, ErrLiteUsingNonHostCandidates
}
if config.Urls != nil && len(config.Urls) > 0 && !containsCandidateType(CandidateTypeServerReflexive, a.candidateTypes) && !containsCandidateType(CandidateTypeRelay, a.candidateTypes) {
return nil, ErrUselessUrlsProvided
}
if err = a.initExtIPMapping(config); err != nil {
return nil, err
}
go a.taskLoop()
// Initialize local candidates
if !a.trickle {
a.gatherCandidates()
}
return a, nil
}
// a sSeparate init routine called by NewAgent() to overcome gocyclo error with golangci-lint
func (a *Agent) initWithDefaults(config *AgentConfig) {
if config.MaxBindingRequests == nil {
a.maxBindingRequests = defaultMaxBindingRequests
} else {
@@ -385,11 +433,6 @@ func NewAgent(config *AgentConfig) (*Agent, error) {
a.relayAcceptanceMinWait = *config.RelayAcceptanceMinWait
}
// Make sure the buffer doesn't grow indefinitely.
// NOTE: We actually won't get anywhere close to this limit.
// SRTP will constantly read from the endpoint and drop packets if it's full.
a.buffer.SetLimitSize(maxBufferSize)
// connectionTimeout used to declare a connection dead
if config.ConnectionTimeout == nil {
a.connectionTimeout = defaultConnectionTimeout
@@ -414,22 +457,45 @@ func NewAgent(config *AgentConfig) (*Agent, error) {
} else {
a.candidateTypes = config.CandidateTypes
}
}
if a.lite && (len(a.candidateTypes) != 1 || a.candidateTypes[0] != CandidateTypeHost) {
return nil, ErrLiteUsingNonHostCandidates
func (a *Agent) initExtIPMapping(config *AgentConfig) error {
var err error
a.extIPMapper, err = newExternalIPMapper(config.NAT1To1IPCandidateType, config.NAT1To1IPs)
if err != nil {
return err
}
if config.Urls != nil && len(config.Urls) > 0 && !containsCandidateType(CandidateTypeServerReflexive, a.candidateTypes) && !containsCandidateType(CandidateTypeRelay, a.candidateTypes) {
return nil, ErrUselessUrlsProvided
if a.extIPMapper == nil {
return nil // this may happen when config.NAT1To1IPs is an empty array
}
if a.extIPMapper.candidateType == CandidateTypeHost {
if a.mDNSMode == MulticastDNSModeQueryAndGather {
return ErrMulticastDNSWithNAT1To1IPMapping
}
candiHostEnabled := false
for _, candiType := range a.candidateTypes {
if candiType == CandidateTypeHost {
candiHostEnabled = true
break
}
}
if !candiHostEnabled {
return ErrIneffectiveNAT1To1IPMappingHost
}
go a.taskLoop()
// Initialize local candidates
if !a.trickle {
a.gatherCandidates()
} else if a.extIPMapper.candidateType == CandidateTypeServerReflexive {
candiSrflxEnabled := false
for _, candiType := range a.candidateTypes {
if candiType == CandidateTypeServerReflexive {
candiSrflxEnabled = true
break
}
}
if !candiSrflxEnabled {
return ErrIneffectiveNAT1To1IPMappingSrflx
}
}
return a, nil
return nil
}
// OnConnectionStateChange sets a handler that is fired when the connection state changes
+91 -4
View File
@@ -367,10 +367,11 @@ func TestConnectivityOnStartup(t *testing.T) {
Proto: ProtoTypeUDP,
}
v, err := buildVNet(&vnet.NATType{
natType := &vnet.NATType{
MappingBehavior: vnet.EndpointIndependent,
FilteringBehavior: vnet.EndpointIndependent,
})
}
v, err := buildVNet(natType, natType)
require.NoError(t, err, "should succeed")
defer v.close()
@@ -440,10 +441,11 @@ func TestConnectivityLite(t *testing.T) {
Proto: ProtoTypeUDP,
}
v, err := buildVNet(&vnet.NATType{
natType := &vnet.NATType{
MappingBehavior: vnet.EndpointIndependent,
FilteringBehavior: vnet.EndpointIndependent,
})
}
v, err := buildVNet(natType, natType)
require.NoError(t, err, "should succeed")
defer v.close()
@@ -1113,3 +1115,88 @@ func TestRemoteCandidateStats(t *testing.T) {
t.Fatalf("Error on agent.Close(): %s", err)
}
}
func TestInitExtIPMapping(t *testing.T) {
var a *Agent
var err error
var closeAgent = func() {
if a != nil {
if err = a.Close(); err != nil {
t.Fatalf("failed to close agent: %v", err)
}
}
}
// a.extIPMapper should be nil by default
a, err = NewAgent(&AgentConfig{
Trickle: true, // to avoid starting gathering candidates
})
if err != nil {
t.Fatalf("Failed to create agent: %v", err)
}
if a.extIPMapper != nil {
t.Fatal("a.extIPMapper should be nil by default")
}
closeAgent()
// a.extIPMapper should be nil when NAT1To1IPs is a non-nil empty array
a, err = NewAgent(&AgentConfig{
NAT1To1IPs: []string{},
NAT1To1IPCandidateType: CandidateTypeHost,
Trickle: true, // to avoid starting gathering candidates
})
if err != nil {
t.Fatalf("Failed to create agent: %v", err)
}
if a.extIPMapper != nil {
t.Fatal("a.extIPMapper should be nil by default")
}
closeAgent()
// 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},
Trickle: true, // to avoid starting gathering candidates
})
if 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},
Trickle: true, // to avoid starting gathering candidates
})
if 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,
Trickle: true, // to avoid starting gathering candidates
})
if 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,
Trickle: true, // to avoid starting gathering candidates
})
if err != ErrInvalidNAT1To1IPMapping {
t.Fatalf("Unexpected error: %v", err)
}
}
+2 -1
View File
@@ -5,7 +5,8 @@ type CandidateType byte
// CandidateType enum
const (
CandidateTypeHost CandidateType = iota + 1
CandidateTypeUnspecified CandidateType = iota
CandidateTypeHost
CandidateTypeServerReflexive
CandidateTypePeerReflexive
CandidateTypeRelay
+161 -38
View File
@@ -25,7 +25,7 @@ func (v *virtualNet) close() {
v.wan.Stop() // nolint:errcheck,gosec
}
func buildVNet(natType *vnet.NATType) (*virtualNet, error) {
func buildVNet(natType0, natType1 *vnet.NATType) (*virtualNet, error) {
loggerFactory := logging.NewDefaultLoggerFactory()
// WAN
@@ -48,16 +48,27 @@ func buildVNet(natType *vnet.NATType) (*virtualNet, error) {
// LAN 0
lan0, err := vnet.NewRouter(&vnet.RouterConfig{
StaticIP: "27.1.1.1", // this router's external IP on eth0
StaticIPs: func() []string {
if natType0.Mode == vnet.NATModeNAT1To1 {
return []string{
"27.1.1.1/192.168.0.1",
}
}
return []string{
"27.1.1.1",
}
}(),
CIDR: "192.168.0.0/24",
NATType: natType,
NATType: natType0,
LoggerFactory: loggerFactory,
})
if err != nil {
return nil, err
}
net0 := vnet.NewNet(&vnet.NetConfig{})
net0 := vnet.NewNet(&vnet.NetConfig{
StaticIPs: []string{"192.168.0.1"},
})
err = lan0.AddNet(net0)
if err != nil {
return nil, err
@@ -70,16 +81,27 @@ func buildVNet(natType *vnet.NATType) (*virtualNet, error) {
// LAN 1
lan1, err := vnet.NewRouter(&vnet.RouterConfig{
StaticIP: "28.1.1.1", // this router's external IP on eth0
StaticIPs: func() []string {
if natType1.Mode == vnet.NATModeNAT1To1 {
return []string{
"28.1.1.1/10.2.0.1",
}
}
return []string{
"28.1.1.1",
}
}(),
CIDR: "10.2.0.0/24",
NATType: natType,
NATType: natType1,
LoggerFactory: loggerFactory,
})
if err != nil {
return nil, err
}
net1 := vnet.NewNet(&vnet.NetConfig{})
net1 := vnet.NewNet(&vnet.NetConfig{
StaticIPs: []string{"10.2.0.1"},
})
err = lan1.AddNet(net1)
if err != nil {
return nil, err
@@ -168,19 +190,33 @@ func connectWithVNet(aAgent, bAgent *Agent) (*Conn, *Conn) {
return aConn, bConn
}
func pipeWithVNet(v *virtualNet, urls0, urls1 []*URL) (*Conn, *Conn) {
type agentTestConfig struct {
urls []*URL
nat1To1IPCandidateType CandidateType
}
func pipeWithVNet(v *virtualNet, a0TestConfig, a1TestConfig *agentTestConfig) (*Conn, *Conn) {
aNotifier, aConnected := onConnected()
bNotifier, bConnected := onConnected()
var wg sync.WaitGroup
wg.Add(2)
var nat1To1IPs []string
if a0TestConfig.nat1To1IPCandidateType != CandidateTypeUnspecified {
nat1To1IPs = []string{
"27.1.1.1",
}
}
cfg0 := &AgentConfig{
Urls: urls0,
Trickle: true,
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
Net: v.net0,
Urls: a0TestConfig.urls,
Trickle: true,
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
NAT1To1IPs: nat1To1IPs,
NAT1To1IPCandidateType: a0TestConfig.nat1To1IPCandidateType,
Net: v.net0,
}
aAgent, err := NewAgent(cfg0)
@@ -204,12 +240,19 @@ func pipeWithVNet(v *virtualNet, urls0, urls1 []*URL) (*Conn, *Conn) {
panic(err)
}
if a1TestConfig.nat1To1IPCandidateType != CandidateTypeUnspecified {
nat1To1IPs = []string{
"28.1.1.1",
}
}
cfg1 := &AgentConfig{
Urls: urls1,
Trickle: true,
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
Net: v.net1,
Urls: a1TestConfig.urls,
Trickle: true,
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
NAT1To1IPs: nat1To1IPs,
NAT1To1IPCandidateType: a1TestConfig.nat1To1IPCandidateType,
Net: v.net1,
}
bAgent, err := NewAgent(cfg1)
@@ -270,15 +313,16 @@ func TestConnectivityVNet(t *testing.T) {
Proto: ProtoTypeUDP,
}
t.Run("Full-cone NATs", func(t *testing.T) {
t.Run("Full-cone NATs on both ends", func(t *testing.T) {
loggerFactory := logging.NewDefaultLoggerFactory()
log := loggerFactory.NewLogger("test")
// buildVNet with Full-cone NATs
v, err := buildVNet(&vnet.NATType{
// buildVNet with a Full-cone NATs both LANs
natType := &vnet.NATType{
MappingBehavior: vnet.EndpointIndependent,
FilteringBehavior: vnet.EndpointIndependent,
})
}
v, err := buildVNet(natType, natType)
if !assert.NoError(t, err, "should succeed") {
return
@@ -286,14 +330,17 @@ func TestConnectivityVNet(t *testing.T) {
defer v.close()
log.Debug("Connecting...")
urls0 := []*URL{
stunServerURL,
a0TestConfig := &agentTestConfig{
urls: []*URL{
stunServerURL,
},
}
urls1 := []*URL{
stunServerURL,
a1TestConfig := &agentTestConfig{
urls: []*URL{
stunServerURL,
},
}
ca, cb := pipeWithVNet(v, urls0, urls1)
ca, cb := pipeWithVNet(v, a0TestConfig, a1TestConfig)
time.Sleep(1 * time.Second)
@@ -303,15 +350,16 @@ func TestConnectivityVNet(t *testing.T) {
}
})
t.Run("Symmetric NATs", func(t *testing.T) {
t.Run("Symmetric NATs on both ends", func(t *testing.T) {
loggerFactory := logging.NewDefaultLoggerFactory()
log := loggerFactory.NewLogger("test")
// buildVNet with Symmetric NATs
v, err := buildVNet(&vnet.NATType{
// 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
@@ -319,15 +367,90 @@ func TestConnectivityVNet(t *testing.T) {
defer v.close()
log.Debug("Connecting...")
urls0 := []*URL{
stunServerURL,
turnServerURL,
a0TestConfig := &agentTestConfig{
urls: []*URL{
stunServerURL,
turnServerURL,
},
}
a1TestConfig := &agentTestConfig{
urls: []*URL{
stunServerURL,
},
}
ca, cb := pipeWithVNet(v, a0TestConfig, a1TestConfig)
urls1 := []*URL{
stunServerURL,
log.Debug("Closing...")
if !closePipe(t, ca, cb) {
return
}
ca, cb := pipeWithVNet(v, urls0, urls1)
})
t.Run("1:1 NAT with host candidate vs Symmetric NATs", func(t *testing.T) {
loggerFactory := logging.NewDefaultLoggerFactory()
log := loggerFactory.NewLogger("test")
// Agent0 is behind 1:1 NAT
natType0 := &vnet.NATType{
Mode: vnet.NATModeNAT1To1,
}
// Agent1 is behind a symmetric NAT
natType1 := &vnet.NATType{
MappingBehavior: vnet.EndpointAddrPortDependent,
FilteringBehavior: vnet.EndpointAddrPortDependent,
}
v, err := buildVNet(natType0, natType1)
if !assert.NoError(t, err, "should succeed") {
return
}
defer v.close()
log.Debug("Connecting...")
a0TestConfig := &agentTestConfig{
urls: []*URL{},
nat1To1IPCandidateType: CandidateTypeHost, // Use 1:1 NAT IP as a host candidate
}
a1TestConfig := &agentTestConfig{
urls: []*URL{},
}
ca, cb := pipeWithVNet(v, a0TestConfig, a1TestConfig)
log.Debug("Closing...")
if !closePipe(t, ca, cb) {
return
}
})
t.Run("1:1 NAT with srflx candidate vs Symmetric NATs", func(t *testing.T) {
loggerFactory := logging.NewDefaultLoggerFactory()
log := loggerFactory.NewLogger("test")
// Agent0 is behind 1:1 NAT
natType0 := &vnet.NATType{
Mode: vnet.NATModeNAT1To1,
}
// Agent1 is behind a symmetric NAT
natType1 := &vnet.NATType{
MappingBehavior: vnet.EndpointAddrPortDependent,
FilteringBehavior: vnet.EndpointAddrPortDependent,
}
v, err := buildVNet(natType0, natType1)
if !assert.NoError(t, err, "should succeed") {
return
}
defer v.close()
log.Debug("Connecting...")
a0TestConfig := &agentTestConfig{
urls: []*URL{},
nat1To1IPCandidateType: CandidateTypeServerReflexive, // Use 1:1 NAT IP as a srflx candidate
}
a1TestConfig := &agentTestConfig{
urls: []*URL{},
}
ca, cb := pipeWithVNet(v, a0TestConfig, a1TestConfig)
log.Debug("Closing...")
if !closePipe(t, ca, cb) {
+22
View File
@@ -64,4 +64,26 @@ var (
// ErrUselessUrlsProvided indicates that one or more URL was provided to the agent but no host
// candidate required them
ErrUselessUrlsProvided = errors.New("agent does not need URL with selected candidate types")
// ErrUnsupportedNAT1To1IPCandidateType indicates that the specified NAT1To1IPCandidateType is
// unsupported
ErrUnsupportedNAT1To1IPCandidateType = errors.New("unsupported 1:1 NAT IP candidate type")
// ErrInvalidNAT1To1IPMapping indicates that the given 1:1 NAT IP mapping is invalid
ErrInvalidNAT1To1IPMapping = errors.New("invalid 1:1 NAT IP mapping")
// ErrExternalMappedIPNotFound in NAT1To1IPMapping
ErrExternalMappedIPNotFound = errors.New("external mapped IP not found")
// ErrMulticastDNSWithNAT1To1IPMapping indicates that the mDNS gathering cannot be used along
// with 1:1 NAT IP mapping for host candidate.
ErrMulticastDNSWithNAT1To1IPMapping = errors.New("mDNS gathering cannot be used with 1:1 NAT IP mapping for host candidate")
// ErrIneffectiveNAT1To1IPMappingHost indicates that 1:1 NAT IP mapping for host candidate is
// requested, but the host candidate type is disabled.
ErrIneffectiveNAT1To1IPMappingHost = errors.New("1:1 NAT IP mapping for host candidate ineffective")
// ErrIneffectiveNAT1To1IPMappingSrflx indicates that 1:1 NAT IP mapping for srflx candidate is
// requested, but the srflx candidate type is disabled.
ErrIneffectiveNAT1To1IPMappingSrflx = errors.New("1:1 NAT IP mapping for srflx candidate ineffective")
)
+143
View File
@@ -0,0 +1,143 @@
package ice
import (
"net"
"strings"
)
func validateIPString(ipStr string) (net.IP, bool, error) {
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, false, ErrInvalidNAT1To1IPMapping
}
return ip, (ip.To4() != nil), nil
}
// ipMapping holds the mapping of local and external IP address for a particular IP family
type ipMapping struct {
ipSole net.IP // when non-nil, this is the sole external IP for one local IP assumed
ipMap map[string]net.IP // local-to-external IP mapping (k: local, v: external)
}
func (m *ipMapping) setSoleIP(ip net.IP) error {
if m.ipSole != nil || len(m.ipMap) > 0 {
return ErrInvalidNAT1To1IPMapping
}
m.ipSole = ip
return nil
}
func (m *ipMapping) addIPMapping(locIP, extIP net.IP) error {
if m.ipSole != nil {
return ErrInvalidNAT1To1IPMapping
}
locIPStr := locIP.String()
// check if dup of local IP
if _, ok := m.ipMap[locIPStr]; ok {
return ErrInvalidNAT1To1IPMapping
}
m.ipMap[locIPStr] = extIP
return nil
}
func (m *ipMapping) findExternalIP(locIP net.IP) (net.IP, error) {
if m.ipSole != nil {
return m.ipSole, nil
}
extIP, ok := m.ipMap[locIP.String()]
if !ok {
return nil, ErrExternalMappedIPNotFound
}
return extIP, nil
}
type externalIPMapper struct {
ipv4Mapping ipMapping
ipv6Mapping ipMapping
candidateType CandidateType
}
func newExternalIPMapper(candidateType CandidateType, ips []string) (*externalIPMapper, error) {
if len(ips) == 0 {
return nil, nil
}
if candidateType == CandidateTypeUnspecified {
candidateType = CandidateTypeHost // defaults to host
} else if candidateType != CandidateTypeHost && candidateType != CandidateTypeServerReflexive {
return nil, ErrUnsupportedNAT1To1IPCandidateType
}
m := &externalIPMapper{
ipv4Mapping: ipMapping{ipMap: map[string]net.IP{}},
ipv6Mapping: ipMapping{ipMap: map[string]net.IP{}},
candidateType: candidateType,
}
for _, extIPStr := range ips {
ipPair := strings.Split(extIPStr, "/")
if len(ipPair) == 0 || len(ipPair) > 2 {
return nil, ErrInvalidNAT1To1IPMapping
}
extIP, isExtIPv4, err := validateIPString(ipPair[0])
if err != nil {
return nil, err
}
if len(ipPair) == 1 {
if isExtIPv4 {
if err := m.ipv4Mapping.setSoleIP(extIP); err != nil {
return nil, err
}
} else {
if err := m.ipv6Mapping.setSoleIP(extIP); err != nil {
return nil, err
}
}
} else {
locIP, isLocIPv4, err := validateIPString(ipPair[1])
if err != nil {
return nil, err
}
if isExtIPv4 {
if !isLocIPv4 {
return nil, ErrInvalidNAT1To1IPMapping
}
if err := m.ipv4Mapping.addIPMapping(locIP, extIP); err != nil {
return nil, err
}
} else {
if isLocIPv4 {
return nil, ErrInvalidNAT1To1IPMapping
}
if err := m.ipv6Mapping.addIPMapping(locIP, extIP); err != nil {
return nil, err
}
}
}
}
return m, nil
}
func (m *externalIPMapper) findExternalIP(localIPStr string) (net.IP, error) {
locIP, isLocIPv4, err := validateIPString(localIPStr)
if err != nil {
return nil, err
}
if isLocIPv4 {
return m.ipv4Mapping.findExternalIP(locIP)
}
return m.ipv6Mapping.findExternalIP(locIP)
}
+304
View File
@@ -0,0 +1,304 @@
package ice
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestExternalIPMapper(t *testing.T) {
t.Run("validateIPString", func(t *testing.T) {
var ip net.IP
var isIPv4 bool
var err error
ip, isIPv4, err = validateIPString("1.2.3.4")
assert.NoError(t, err, "should succeed")
assert.True(t, isIPv4, "should be true")
assert.Equal(t, "1.2.3.4", ip.String(), "should be true")
ip, isIPv4, err = validateIPString("2601:4567::5678")
assert.NoError(t, err, "should succeed")
assert.False(t, isIPv4, "should be false")
assert.Equal(t, "2601:4567::5678", ip.String(), "should be true")
_, _, err = validateIPString("bad.6.6.6")
assert.Error(t, err, "should fail")
})
t.Run("newExternalIPMapper", func(t *testing.T) {
var m *externalIPMapper
var err error
// ips being nil should succeed but mapper will be nil also
m, err = newExternalIPMapper(CandidateTypeUnspecified, nil)
assert.NoError(t, err, "should succeed")
assert.Nil(t, m, "should be nil")
// ips being empty should succeed but mapper will still be nil
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{})
assert.NoError(t, err, "should succeed")
assert.Nil(t, m, "should be nil")
// IPv4 with no explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.Equal(t, CandidateTypeHost, m.candidateType, "should match")
assert.NotNil(t, m.ipv4Mapping.ipSole)
assert.Nil(t, m.ipv6Mapping.ipSole)
assert.Equal(t, 0, len(m.ipv4Mapping.ipMap), "should match")
assert.Equal(t, 0, len(m.ipv6Mapping.ipMap), "should match")
// IPv4 with no explicit local IP, using CandidateTypeServerReflexive
m, err = newExternalIPMapper(CandidateTypeServerReflexive, []string{
"1.2.3.4",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.Equal(t, CandidateTypeServerReflexive, m.candidateType, "should match")
assert.NotNil(t, m.ipv4Mapping.ipSole)
assert.Nil(t, m.ipv6Mapping.ipSole)
assert.Equal(t, 0, len(m.ipv4Mapping.ipMap), "should match")
assert.Equal(t, 0, len(m.ipv6Mapping.ipMap), "should match")
// IPv4 with no explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2601:4567::5678",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.Equal(t, CandidateTypeHost, m.candidateType, "should match")
assert.Nil(t, m.ipv4Mapping.ipSole)
assert.NotNil(t, m.ipv6Mapping.ipSole)
assert.Equal(t, 0, len(m.ipv4Mapping.ipMap), "should match")
assert.Equal(t, 0, len(m.ipv6Mapping.ipMap), "should match")
// IPv4 and IPv6 in the mix
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4",
"2601:4567::5678",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.Equal(t, CandidateTypeHost, m.candidateType, "should match")
assert.NotNil(t, m.ipv4Mapping.ipSole)
assert.NotNil(t, m.ipv6Mapping.ipSole)
assert.Equal(t, 0, len(m.ipv4Mapping.ipMap), "should match")
assert.Equal(t, 0, len(m.ipv6Mapping.ipMap), "should match")
// Unsupported candidate type - CandidateTypePeerReflexive
m, err = newExternalIPMapper(CandidateTypePeerReflexive, []string{
"1.2.3.4",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Unsupported candidate type - CandidateTypeRelay
m, err = newExternalIPMapper(CandidateTypePeerReflexive, []string{
"1.2.3.4",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Cannot duplicate mapping IPv4 family
m, err = newExternalIPMapper(CandidateTypeServerReflexive, []string{
"1.2.3.4",
"5.6.7.8",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Cannot duplicate mapping IPv6 family
m, err = newExternalIPMapper(CandidateTypeServerReflexive, []string{
"2201::1",
"2201::0002",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Invalide external IP string
m, err = newExternalIPMapper(CandidateTypeServerReflexive, []string{
"bad.2.3.4",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Invalide local IP string
m, err = newExternalIPMapper(CandidateTypeServerReflexive, []string{
"1.2.3.4/10.0.0.bad",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
})
t.Run("newExternalIPMapper with explicit local IP", func(t *testing.T) {
var m *externalIPMapper
var err error
// IPv4 with explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4/10.0.0.1",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.Equal(t, CandidateTypeHost, m.candidateType, "should match")
assert.Nil(t, m.ipv4Mapping.ipSole)
assert.Nil(t, m.ipv6Mapping.ipSole)
assert.Equal(t, 1, len(m.ipv4Mapping.ipMap), "should match")
assert.Equal(t, 0, len(m.ipv6Mapping.ipMap), "should match")
// Cannot assign two ext IPs for one local IPv4
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4/10.0.0.1",
"1.2.3.5/10.0.0.1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Cannot assign two ext IPs for one local IPv6
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2200::1/fe80::1",
"2200::0002/fe80::1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Cannot mix different IP family in a pair (1)
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2200::1/10.0.0.1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Cannot mix different IP family in a pair (2)
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4/fe80::1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
// Invalid pair
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4/192.168.0.2/10.0.0.1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
})
t.Run("newExternalIPMapper with inplicit and explicit local IP", func(t *testing.T) {
// Mixing inpicit and explicit local IPs not allowed
_, err := newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4",
"1.2.3.5/10.0.0.1",
})
assert.Error(t, err, "should fail")
// Mixing inpicit and explicit local IPs not allowed
_, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.5/10.0.0.1",
"1.2.3.4",
})
assert.Error(t, err, "should fail")
})
t.Run("findExternalIP without explicit local IP", func(t *testing.T) {
var m *externalIPMapper
var err error
var extIP net.IP
// IPv4 with explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4",
"2200::1",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
assert.NotNil(t, m.ipv4Mapping.ipSole)
assert.NotNil(t, m.ipv6Mapping.ipSole)
// find external IPv4
extIP, err = m.findExternalIP("10.0.0.1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.4", extIP.String(), "should match")
// find external IPv6
extIP, err = m.findExternalIP("fe80::0001") // use '0001' instead of '1' on purpse
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::1", extIP.String(), "should match")
// Bad local IP string
_, err = m.findExternalIP("really.bad")
assert.Error(t, err, "should fail")
})
t.Run("findExternalIP with explicit local IP", func(t *testing.T) {
var m *externalIPMapper
var err error
var extIP net.IP
// IPv4 with explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4/10.0.0.1",
"1.2.3.5/10.0.0.2",
"2200::1/fe80::1",
"2200::2/fe80::2",
})
assert.NoError(t, err, "should succeed")
assert.NotNil(t, m, "should not be nil")
// find external IPv4
extIP, err = m.findExternalIP("10.0.0.1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.4", extIP.String(), "should match")
extIP, err = m.findExternalIP("10.0.0.2")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.5", extIP.String(), "should match")
_, err = m.findExternalIP("10.0.0.3")
assert.Error(t, err, "should fail")
// find external IPv6
extIP, err = m.findExternalIP("fe80::0001") // use '0001' instead of '1' on purpse
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::1", extIP.String(), "should match")
extIP, err = m.findExternalIP("fe80::0002") // use '0002' instead of '2' on purpse
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::2", extIP.String(), "should match")
_, err = m.findExternalIP("fe80::3")
assert.Error(t, err, "should fail")
// Bad local IP string
_, err = m.findExternalIP("really.bad")
assert.Error(t, err, "should fail")
})
t.Run("findExternalIP with empty map", func(t *testing.T) {
var m *externalIPMapper
var err error
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"1.2.3.4",
})
assert.NoError(t, err, "should succeed")
// attempt to find IPv6 that does not exist in the map
_, err = m.findExternalIP("fe80::1")
assert.Error(t, err, "should fail")
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2200::1",
})
assert.NoError(t, err, "should succeed")
// attempt to find IPv4 that does not exist in the map
_, err = m.findExternalIP("10.0.0.1")
assert.Error(t, err, "should fail")
})
}
+91 -31
View File
@@ -176,8 +176,17 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) {
wg.Add(len(localIPs) * len(supportedNetworks))
for _, ip := range localIPs {
mappedIP := ip
if a.mDNSMode != MulticastDNSModeQueryAndGather && a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeHost {
if _mappedIP, err := a.extIPMapper.findExternalIP(ip.String()); err == nil {
mappedIP = _mappedIP
} else {
a.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s\n", ip.String())
}
}
for _, network := range supportedNetworks {
go func(network string, ip net.IP) {
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})
if err != nil {
@@ -185,7 +194,7 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) {
return
}
address := ip.String()
address := mappedIP.String()
if a.mDNSMode == MulticastDNSModeQueryAndGather {
address = a.mDNSName
}
@@ -201,13 +210,13 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) {
c, err := NewCandidateHost(&hostConfig)
if err != nil {
a.log.Warnf("Failed to create host candidate: %s %s %d: %v\n", network, ip, port, err)
a.log.Warnf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err)
return
}
if a.mDNSMode == MulticastDNSModeQueryAndGather {
if err = c.setIP(ip); err != nil {
a.log.Warnf("Failed to create host candidate: %s %s %d: %v\n", network, ip, port, err)
a.log.Warnf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err)
return
}
}
@@ -222,55 +231,106 @@ func (a *Agent) gatherCandidatesLocal(networkTypes []NetworkType) {
}); err != nil {
a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
}
}(network, ip)
}(network, ip, mappedIP)
}
}
}
func (a *Agent) gatherCandidatesSrflx(urls []*URL, networkTypes []NetworkType) {
var stunURLs []*URL
for _, url := range urls {
if url.Scheme == SchemeTypeSTUN {
stunURLs = append(stunURLs, url)
}
}
for _, networkType := range networkTypes {
network := networkType.String()
for _, url := range urls {
if url.Scheme != SchemeTypeSTUN {
continue
}
if len(stunURLs) > 0 {
for _, url := range stunURLs {
if url.Scheme != SchemeTypeSTUN {
continue
}
hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port)
serverAddr, err := a.net.ResolveUDPAddr(network, hostPort)
if err != nil {
a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err)
continue
}
hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port)
serverAddr, err := a.net.ResolveUDPAddr(network, hostPort)
if err != nil {
a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err)
continue
}
conn, err := a.listenUDP(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
}
xoraddr, err := getXORMappedAddr(conn, serverAddr, stunGatherTimeout)
if err != nil {
a.log.Warnf("could not get server reflexive address %s %s: %v\n", network, url, err)
continue
}
laddr := conn.LocalAddr().(*net.UDPAddr)
ip := xoraddr.IP
port := xoraddr.Port
relIP := laddr.IP.String()
relPort := laddr.Port
srflxConfig := CandidateServerReflexiveConfig{
Network: network,
Address: ip.String(),
Port: port,
Component: ComponentRTP,
RelAddr: relIP,
RelPort: relPort,
}
c, err := NewCandidateServerReflexive(&srflxConfig)
if err != nil {
a.log.Warnf("Failed to create server reflexive candidate: %s %s %d: %v\n", network, ip, port, err)
continue
}
if err := a.run(func(agent *Agent) {
c.start(a, conn)
a.addCandidate(c)
if a.onCandidateHdlr != nil {
go a.onCandidateHdlr(c)
}
}); err != nil {
a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
}
}
} 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})
if err != nil {
a.log.Warnf("Failed to listen for %s: %v\n", serverAddr.String(), err)
continue
}
xoraddr, err := getXORMappedAddr(conn, serverAddr, stunGatherTimeout)
if err != nil {
a.log.Warnf("could not get server reflexive address %s %s: %v\n", network, url, err)
a.log.Warnf("Failed to listen %s: %v\n", network, err)
continue
}
laddr := conn.LocalAddr().(*net.UDPAddr)
ip := xoraddr.IP
port := xoraddr.Port
relIP := laddr.IP.String()
relPort := laddr.Port
mappedIP, err := a.extIPMapper.findExternalIP(laddr.IP.String())
if err != nil {
a.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s\n", laddr.IP.String())
continue
}
srflxConfig := CandidateServerReflexiveConfig{
Network: network,
Address: ip.String(),
Port: port,
Address: mappedIP.String(),
Port: laddr.Port,
Component: ComponentRTP,
RelAddr: relIP,
RelPort: relPort,
RelAddr: laddr.IP.String(),
RelPort: laddr.Port,
}
c, err := NewCandidateServerReflexive(&srflxConfig)
if err != nil {
a.log.Warnf("Failed to create server reflexive candidate: %s %s %d: %v\n", network, ip, port, err)
a.log.Warnf("Failed to create server reflexive candidate: %s %s %d: %v\n",
network,
mappedIP.String(),
laddr.Port,
err)
continue
}
+206
View File
@@ -1,11 +1,13 @@
package ice
import (
"fmt"
"net"
"testing"
"github.com/pion/logging"
"github.com/pion/transport/vnet"
"github.com/stretchr/testify/assert"
)
func TestVNetGather(t *testing.T) {
@@ -116,6 +118,10 @@ func TestVNetGather(t *testing.T) {
} 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 = a.listenUDP(4999, 5000, udp, &net.UDPAddr{IP: ip, Port: 0})
if err != ErrPort {
@@ -135,5 +141,205 @@ func TestVNetGather(t *testing.T) {
} else if port != "5000" {
t.Fatalf("listenUDP with port restriction of 5000 listened on incorrect port (%s)", port)
}
err = conn.Close()
if err != nil {
t.Fatalf("failed to close conn")
}
})
}
func TestVNetGatherWithNAT1To1(t *testing.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,
})
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},
Trickle: true,
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,
Trickle: true,
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")
})
}
+4 -4
View File
@@ -5,10 +5,10 @@ go 1.12
require (
github.com/pion/logging v0.2.2
github.com/pion/mdns v0.0.3
github.com/pion/stun v0.3.2
github.com/pion/transport v0.8.7
github.com/pion/turn v1.3.6
github.com/stretchr/testify v1.3.0
github.com/pion/stun v0.3.3
github.com/pion/transport v0.8.9
github.com/pion/turn v1.3.7
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
)
+19 -6
View File
@@ -2,6 +2,11 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pion/logging v0.2.1 h1:LwASkBKZ+2ysGJ+jLv1E/9H1ge0k1nTfi1X+5zirkDk=
github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@@ -10,12 +15,13 @@ github.com/pion/mdns v0.0.3 h1:DxdOYd0pgwLKiDlIIxfU0qdG5iWh1Xn6CsS9vc6cMAY=
github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE=
github.com/pion/stun v0.3.2 h1:Vsy6C+bTbJKEC2TH4vHYOnRKmozNPi5FpeKv9/bX16k=
github.com/pion/stun v0.3.2/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
github.com/pion/transport v0.8.6 h1:xHQq2mxAjB+UrFs90aUBaXwlmIACfQAZnOiVAX3uqMw=
github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno=
github.com/pion/transport v0.8.7 h1:t7uYhWOoljd82rnkLH+H2Lw7/IGA5kV9Bl5sWrmcYSc=
github.com/pion/transport v0.8.7/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8=
github.com/pion/turn v1.3.6 h1:N49o5g3SRI5g5Sg8WVft1RDAFxIHOi5roHHdbC39g7g=
github.com/pion/turn v1.3.6/go.mod h1:D8XaX/CVKLkRozV9baRvhZmwOyKAJCTHrj23DdwMI4g=
github.com/pion/stun v0.3.3 h1:brYuPl9bN9w/VM7OdNzRSLoqsnwlyNvD9MVeJrHjDQw=
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
github.com/pion/transport v0.8.8/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8=
github.com/pion/transport v0.8.9 h1:3PUZULb0WZd/QNfXKKMwcUHzLR+XfNem6lF2M9UrxSU=
github.com/pion/transport v0.8.9/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8=
github.com/pion/turn v1.3.7 h1:/nyM2XrlZILD7KKfnh0oYEBTRG5JlbH21ibjluRoCeo=
github.com/pion/turn v1.3.7/go.mod h1:js0LBFqMcKAlaWAXoYqNjefGI7kfJCrkCBfHGuTToXE=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -23,6 +29,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -33,3 +41,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=