diff --git a/agent.go b/agent.go index 2e41bcf..76f66f6 100644 --- a/agent.go +++ b/agent.go @@ -54,7 +54,7 @@ type bindingRequest struct { // Agent represents the ICE agent type Agent struct { onConnectionStateChangeHdlr func(ConnectionState) - onSelectedCandidatePairChangeHdlr func(*Candidate, *Candidate) + onSelectedCandidatePairChangeHdlr func(Candidate, Candidate) // Used to block double Dial/Accept opened bool @@ -91,15 +91,15 @@ type Agent struct { localUfrag string localPwd string - localCandidates map[NetworkType][]*Candidate + localCandidates map[NetworkType][]Candidate remoteUfrag string remotePwd string - remoteCandidates map[NetworkType][]*Candidate + remoteCandidates map[NetworkType][]Candidate selector pairCandidateSelector selectedPair *candidatePair - validPairs []*candidatePair + validPairs candidatePairs buffer *packetio.Buffer @@ -175,8 +175,8 @@ func NewAgent(config *AgentConfig) (*Agent, error) { tieBreaker: rand.New(rand.NewSource(time.Now().UnixNano())).Uint64(), gatheringState: GatheringStateComplete, // TODO trickle-ice connectionState: ConnectionStateNew, - localCandidates: make(map[NetworkType][]*Candidate), - remoteCandidates: make(map[NetworkType][]*Candidate), + localCandidates: make(map[NetworkType][]Candidate), + remoteCandidates: make(map[NetworkType][]Candidate), pendingBindingRequests: make([]bindingRequest, 0, maxPendingBindingRequests), localUfrag: randSeq(16), @@ -235,7 +235,7 @@ func (a *Agent) OnConnectionStateChange(f func(ConnectionState)) error { // OnSelectedCandidatePairChange sets a handler that is fired when the final candidate // pair is selected -func (a *Agent) OnSelectedCandidatePairChange(f func(*Candidate, *Candidate)) error { +func (a *Agent) OnSelectedCandidatePairChange(f func(Candidate, Candidate)) error { return a.run(func(agent *Agent) { agent.onSelectedCandidatePairChangeHdlr = f }) @@ -296,7 +296,7 @@ func (a *Agent) updateConnectionState(newState ConnectionState) { } } -func (a *Agent) findValidPair(local, remote *Candidate) *candidatePair { +func (a *Agent) findValidPair(local, remote Candidate) *candidatePair { for _, p := range a.validPairs { if p.local == local && p.remote == remote { return p @@ -305,7 +305,7 @@ func (a *Agent) findValidPair(local, remote *Candidate) *candidatePair { return nil } -func (a *Agent) addValidPair(local, remote *Candidate) *candidatePair { +func (a *Agent) addValidPair(local, remote Candidate) *candidatePair { p := a.findValidPair(local, remote) if p != nil { a.log.Tracef("Candidate pair is already valid: %s", p) @@ -436,16 +436,15 @@ func (a *Agent) pingAllCandidates() { } // AddRemoteCandidate adds a new remote candidate -func (a *Agent) AddRemoteCandidate(c *Candidate) error { +func (a *Agent) AddRemoteCandidate(c Candidate) error { return a.run(func(agent *Agent) { agent.addRemoteCandidate(c) }) } // addRemoteCandidate assumes you are holding the lock (must be execute using a.run) -func (a *Agent) addRemoteCandidate(c *Candidate) { - networkType := c.NetworkType - set := a.remoteCandidates[networkType] +func (a *Agent) addRemoteCandidate(c Candidate) { + set := a.remoteCandidates[c.NetworkType()] for _, candidate := range set { if candidate.Equal(c) { @@ -454,15 +453,15 @@ func (a *Agent) addRemoteCandidate(c *Candidate) { } set = append(set, c) - a.remoteCandidates[networkType] = set + a.remoteCandidates[c.NetworkType()] = set } // GetLocalCandidates returns the local candidates -func (a *Agent) GetLocalCandidates() ([]*Candidate, error) { - res := make(chan []*Candidate) +func (a *Agent) GetLocalCandidates() ([]Candidate, error) { + res := make(chan []Candidate) err := a.run(func(agent *Agent) { - var candidates []*Candidate + var candidates []Candidate for _, set := range agent.localCandidates { candidates = append(candidates, set...) } @@ -525,7 +524,7 @@ func (a *Agent) Close() error { return nil } -func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) *Candidate { +func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) Candidate { var ip net.IP var port int @@ -543,16 +542,14 @@ func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) *Can set := a.remoteCandidates[networkType] for _, c := range set { - base := c - if base.IP.Equal(ip) && - base.Port == port { + if c.IP().Equal(ip) && c.Port() == port { return c } } return nil } -func (a *Agent) sendBindingRequest(m *stun.Message, local, remote *Candidate) { +func (a *Agent) sendBindingRequest(m *stun.Message, local, remote Candidate) { a.log.Tracef("ping STUN from %s to %s\n", local.String(), remote.String()) if overflow := len(a.pendingBindingRequests) - (maxPendingBindingRequests - 1); overflow > 0 { @@ -571,12 +568,12 @@ func (a *Agent) sendBindingRequest(m *stun.Message, local, remote *Candidate) { a.sendSTUN(m, local, remote) } -func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote *Candidate) { +func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) { base := remote if out, err := stun.Build(m, stun.BindingSuccess, &stun.XORMappedAddress{ - IP: base.IP, - Port: base.Port, + IP: base.IP(), + Port: base.Port(), }, stun.NewShortTermIntegrity(a.localPwd), stun.Fingerprint, @@ -601,7 +598,7 @@ func (a *Agent) handleInboundBindingSuccess(id [stun.TransactionIDSize]byte) (bo } // handleInbound processes STUN traffic from a remote candidate -func (a *Agent) handleInbound(m *stun.Message, local *Candidate, remote net.Addr) { +func (a *Agent) handleInbound(m *stun.Message, local Candidate, remote net.Addr) { var err error if m == nil || local == nil { return @@ -630,7 +627,7 @@ func (a *Agent) handleInbound(m *stun.Message, local *Candidate, remote net.Addr } } - remoteCandidate := a.findRemoteCandidate(local.NetworkType, remote) + remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote) if m.Type.Class == stun.ClassSuccessResponse { if err = assertInboundMessageIntegrity(m, []byte(a.remotePwd)); err != nil { a.log.Warnf("discard message from (%s), %v", remote, err) @@ -659,7 +656,7 @@ func (a *Agent) handleInbound(m *stun.Message, local *Candidate, remote net.Addr return } - prflxCandidate, err := NewCandidatePeerReflexive(networkType.String(), ip, port, local.Component, "", 0) + prflxCandidate, err := NewCandidatePeerReflexive(networkType.String(), ip, port, local.Component(), "", 0) if err != nil { a.log.Errorf("Failed to create new remote prflx candidate (%s)", err) return @@ -682,8 +679,8 @@ func (a *Agent) handleInbound(m *stun.Message, local *Candidate, remote net.Addr // noSTUNSeen processes non STUN traffic from a remote candidate, // and returns true if it is an actual remote candidate -func (a *Agent) noSTUNSeen(local *Candidate, remote net.Addr) bool { - remoteCandidate := a.findRemoteCandidate(local.NetworkType, remote) +func (a *Agent) noSTUNSeen(local Candidate, remote net.Addr) bool { + remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote) if remoteCandidate == nil { return false } diff --git a/agent_test.go b/agent_test.go index 62ef5ff..b7cf04a 100644 --- a/agent_test.go +++ b/agent_test.go @@ -108,7 +108,7 @@ func TestPairPriority(t *testing.T) { t.Fatalf("Failed to construct remote host candidate: %s", err) } - for _, remote := range []*Candidate{relayRemote, srflxRemote, prflxRemote, hostRemote} { + for _, remote := range []Candidate{relayRemote, srflxRemote, prflxRemote, hostRemote} { a.addValidPair(hostLocal, remote) bestPair := a.getBestValidPair() if bestPair.String() != (&candidatePair{remote: remote, local: hostLocal}).String() { @@ -130,7 +130,7 @@ func TestOnSelectedCandidatePairChange(t *testing.T) { t.Fatalf("Failed to create agent: %s", err) } callbackCalled := make(chan struct{}, 1) - if err = a.OnSelectedCandidatePairChange(func(local, remote *Candidate) { + if err = a.OnSelectedCandidatePairChange(func(local, remote Candidate) { close(callbackCalled) }); err != nil { t.Fatalf("Failed to set agent OnCandidatePairChange callback: %s", err) @@ -226,22 +226,22 @@ func TestHandlePeerReflexive(t *testing.T) { } // length of remote candidate list for a network type must be 1 - set := a.remoteCandidates[local.NetworkType] + 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 { + if c.Type() != CandidateTypePeerReflexive { t.Fatal("candidate type must be prflx") } - if !c.IP.Equal(net.ParseIP("172.17.0.3")) { + if !c.IP().Equal(net.ParseIP("172.17.0.3")) { t.Fatal("IP address mismatch") } - if c.Port != 999 { + if c.Port() != 999 { t.Fatal("Port number mismatch") } diff --git a/candidate.go b/candidate.go index bf9ff37..9609081 100644 --- a/candidate.go +++ b/candidate.go @@ -1,12 +1,8 @@ package ice import ( - "fmt" "net" - "sync" "time" - - "github.com/pion/stun" ) const ( @@ -20,260 +16,26 @@ const ( ) // Candidate represents an ICE candidate -type Candidate struct { - NetworkType +type Candidate interface { + start(a *Agent, conn net.PacketConn) + addr() net.Addr - Type CandidateType - LocalPreference uint16 - Component uint16 - IP net.IP - Port int - RelatedAddress *CandidateRelatedAddress + setLastSent(t time.Time) + seen(outbound bool) + LastSent() time.Time + setLastReceived(t time.Time) + LastReceived() time.Time + String() string + Equal(other Candidate) bool + Priority() uint32 + writeTo(raw []byte, dst Candidate) (int, error) + close() error - lock sync.RWMutex - lastSent time.Time - lastReceived time.Time + IP() net.IP + Port() int + Component() uint16 + NetworkType() NetworkType - agent *Agent - conn net.PacketConn - closeCh chan struct{} - closedCh chan struct{} -} - -// NewCandidateHost creates a new host candidate -func NewCandidateHost(network string, ip net.IP, port int, component uint16) (*Candidate, error) { - networkType, err := determineNetworkType(network, ip) - if err != nil { - return nil, err - } - - return &Candidate{ - Type: CandidateTypeHost, - NetworkType: networkType, - IP: ip, - Port: port, - LocalPreference: defaultLocalPreference, - Component: component, - }, nil -} - -// NewCandidateServerReflexive creates a new server reflective candidate -func NewCandidateServerReflexive(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*Candidate, error) { - networkType, err := determineNetworkType(network, ip) - if err != nil { - return nil, err - } - return &Candidate{ - Type: CandidateTypeServerReflexive, - NetworkType: networkType, - IP: ip, - Port: port, - LocalPreference: defaultLocalPreference, - Component: component, - RelatedAddress: &CandidateRelatedAddress{ - Address: relAddr, - Port: relPort, - }, - }, nil -} - -// NewCandidatePeerReflexive creates a new peer reflective candidate -func NewCandidatePeerReflexive(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*Candidate, error) { - networkType, err := determineNetworkType(network, ip) - if err != nil { - return nil, err - } - return &Candidate{ - Type: CandidateTypePeerReflexive, - NetworkType: networkType, - IP: ip, - Port: port, - LocalPreference: defaultLocalPreference, - Component: component, - RelatedAddress: &CandidateRelatedAddress{ - Address: relAddr, - Port: relPort, - }, - }, nil -} - -// NewCandidateRelay creates a new relay candidate -func NewCandidateRelay(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*Candidate, error) { - networkType, err := determineNetworkType(network, ip) - if err != nil { - return nil, err - } - return &Candidate{ - Type: CandidateTypeRelay, - NetworkType: networkType, - IP: ip, - Port: port, - LocalPreference: defaultLocalPreference, - Component: component, - RelatedAddress: &CandidateRelatedAddress{ - Address: relAddr, - Port: relPort, - }, - }, nil -} - -// start runs the candidate using the provided connection -func (c *Candidate) start(a *Agent, conn net.PacketConn) { - c.agent = a - c.conn = conn - c.closeCh = make(chan struct{}) - c.closedCh = make(chan struct{}) - - go c.recvLoop() -} - -func (c *Candidate) recvLoop() { - defer func() { - close(c.closedCh) - }() - - log := c.agent.log - buffer := make([]byte, receiveMTU) - for { - n, srcAddr, err := c.conn.ReadFrom(buffer) - if err != nil { - return - } - - if stun.IsMessage(buffer[:n]) { - m := &stun.Message{ - Raw: make([]byte, n), - } - // Explicitly copy raw buffer so Message can own the memory. - copy(m.Raw, buffer[:n]) - if err = m.Decode(); err != nil { - log.Warnf("Failed to handle decode ICE from %s to %s: %v", c.addr(), srcAddr, err) - continue - } - err = c.agent.run(func(agent *Agent) { - agent.handleInbound(m, c, srcAddr) - }) - if err != nil { - log.Warnf("Failed to handle message: %v", err) - } - - continue - } - - isValidRemoteCandidate := make(chan bool, 1) - err = c.agent.run(func(agent *Agent) { - isValidRemoteCandidate <- agent.noSTUNSeen(c, srcAddr) - }) - - if err != nil { - log.Warnf("Failed to handle message: %v", err) - } else if !<-isValidRemoteCandidate { - log.Warnf("Discarded message from %s, not a valid remote candidate", c.addr()) - } - - // NOTE This will return packetio.ErrFull if the buffer ever manages to fill up. - _, err = c.agent.buffer.Write(buffer[:n]) - if err != nil { - log.Warnf("failed to write packet") - } - } -} - -// close stops the recvLoop -func (c *Candidate) close() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.conn != nil { - // Unblock recvLoop - close(c.closeCh) - // Close the conn - err := c.conn.Close() - if err != nil { - return err - } - - // Wait until the recvLoop is closed - <-c.closedCh - } - - return nil -} - -func (c *Candidate) writeTo(raw []byte, dst *Candidate) (int, error) { - n, err := c.conn.WriteTo(raw, dst.addr()) - if err != nil { - return n, fmt.Errorf("failed to send packet: %v", err) - } - c.seen(true) - return n, nil -} - -// Priority computes the priority for this ICE Candidate -func (c *Candidate) Priority() uint32 { - // The local preference MUST be an integer from 0 (lowest preference) to - // 65535 (highest preference) inclusive. When there is only a single IP - // address, this value SHOULD be set to 65535. If there are multiple - // candidates for a particular component for a particular data stream - // that have the same type, the local preference MUST be unique for each - // one. - return (1<<24)*uint32(c.Type.Preference()) + - (1<<8)*uint32(c.LocalPreference) + - uint32(256-c.Component) -} - -// Equal is used to compare two CandidateBases -func (c *Candidate) Equal(other *Candidate) bool { - return c.NetworkType == other.NetworkType && - c.Type == other.Type && - c.IP.Equal(other.IP) && - c.Port == other.Port && - c.RelatedAddress.Equal(other.RelatedAddress) -} - -// String makes the CandidateHost printable -func (c *Candidate) String() string { - return fmt.Sprintf("%s %s:%d%s", c.Type, c.IP, c.Port, c.RelatedAddress) -} - -// LastReceived returns a time.Time indicating the last time -// this candidate was received -func (c *Candidate) LastReceived() time.Time { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lastReceived -} - -func (c *Candidate) setLastReceived(t time.Time) { - c.lock.Lock() - defer c.lock.Unlock() - c.lastReceived = t -} - -// LastSent returns a time.Time indicating the last time -// this candidate was sent -func (c *Candidate) LastSent() time.Time { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lastSent -} - -func (c *Candidate) setLastSent(t time.Time) { - c.lock.Lock() - defer c.lock.Unlock() - c.lastSent = t -} - -func (c *Candidate) seen(outbound bool) { - if outbound { - c.setLastSent(time.Now()) - } else { - c.setLastReceived(time.Now()) - } -} - -func (c *Candidate) addr() net.Addr { - return &net.UDPAddr{ - IP: c.IP, - Port: c.Port, - } + Type() CandidateType + RelatedAddress() *CandidateRelatedAddress } diff --git a/candidate_base.go b/candidate_base.go new file mode 100644 index 0000000..c394c01 --- /dev/null +++ b/candidate_base.go @@ -0,0 +1,225 @@ +package ice + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/stun" +) + +type candidateBase struct { + networkType NetworkType + candidateType CandidateType + + component uint16 + ip net.IP + port int + relatedAddress *CandidateRelatedAddress + + lock sync.RWMutex + lastSent time.Time + lastReceived time.Time + + agent *Agent + conn net.PacketConn + closeCh chan struct{} + closedCh chan struct{} +} + +// IP returns Candidate IP +func (c *candidateBase) IP() net.IP { + return c.ip +} + +// Port returns Candidate Port +func (c *candidateBase) Port() int { + return c.port +} + +// Type returns candidate type +func (c *candidateBase) Type() CandidateType { + return c.candidateType +} + +// NetworkType returns candidate NetworkType +func (c *candidateBase) NetworkType() NetworkType { + return c.networkType +} + +// Component returns candidate component +func (c *candidateBase) Component() uint16 { + return c.component +} + +// LocalPreference returns the local preference for this candidate +func (c *candidateBase) LocalPreference() uint16 { + return defaultLocalPreference +} + +// RelatedAddress returns *CandidateRelatedAddress +func (c *candidateBase) RelatedAddress() *CandidateRelatedAddress { + return c.relatedAddress +} + +// start runs the candidate using the provided connection +func (c *candidateBase) start(a *Agent, conn net.PacketConn) { + c.agent = a + c.conn = conn + c.closeCh = make(chan struct{}) + c.closedCh = make(chan struct{}) + + go c.recvLoop() +} + +func (c *candidateBase) recvLoop() { + defer func() { + close(c.closedCh) + }() + + log := c.agent.log + buffer := make([]byte, receiveMTU) + for { + n, srcAddr, err := c.conn.ReadFrom(buffer) + if err != nil { + return + } + + if stun.IsMessage(buffer[:n]) { + m := &stun.Message{ + Raw: make([]byte, n), + } + // Explicitly copy raw buffer so Message can own the memory. + copy(m.Raw, buffer[:n]) + if err = m.Decode(); err != nil { + log.Warnf("Failed to handle decode ICE from %s to %s: %v", c.addr(), srcAddr, err) + continue + } + err = c.agent.run(func(agent *Agent) { + agent.handleInbound(m, c, srcAddr) + }) + if err != nil { + log.Warnf("Failed to handle message: %v", err) + } + + continue + } + + isValidRemoteCandidate := make(chan bool, 1) + err = c.agent.run(func(agent *Agent) { + isValidRemoteCandidate <- agent.noSTUNSeen(c, srcAddr) + }) + + if err != nil { + log.Warnf("Failed to handle message: %v", err) + } else if !<-isValidRemoteCandidate { + log.Warnf("Discarded message from %s, not a valid remote candidate", c.addr()) + } + + // NOTE This will return packetio.ErrFull if the buffer ever manages to fill up. + _, err = c.agent.buffer.Write(buffer[:n]) + if err != nil { + log.Warnf("failed to write packet") + } + } +} + +// close stops the recvLoop +func (c *candidateBase) close() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.conn != nil { + // Unblock recvLoop + close(c.closeCh) + // Close the conn + err := c.conn.Close() + if err != nil { + return err + } + + // Wait until the recvLoop is closed + <-c.closedCh + } + + return nil +} + +func (c *candidateBase) writeTo(raw []byte, dst Candidate) (int, error) { + n, err := c.conn.WriteTo(raw, dst.addr()) + if err != nil { + return n, fmt.Errorf("failed to send packet: %v", err) + } + c.seen(true) + return n, nil +} + +// Priority computes the priority for this ICE Candidate +func (c *candidateBase) Priority() uint32 { + // The local preference MUST be an integer from 0 (lowest preference) to + // 65535 (highest preference) inclusive. When there is only a single IP + // address, this value SHOULD be set to 65535. If there are multiple + // candidates for a particular component for a particular data stream + // that have the same type, the local preference MUST be unique for each + // one. + return (1<<24)*uint32(c.Type().Preference()) + + (1<<8)*uint32(c.LocalPreference()) + + uint32(256-c.Component()) +} + +// Equal is used to compare two candidateBases +func (c *candidateBase) Equal(other Candidate) bool { + return c.NetworkType() == other.NetworkType() && + c.Type() == other.Type() && + c.IP().Equal(other.IP()) && + c.Port() == other.Port() && + c.RelatedAddress().Equal(other.RelatedAddress()) +} + +// String makes the candidateBase printable +func (c *candidateBase) String() string { + return fmt.Sprintf("%s %s:%d%s", c.Type(), c.IP(), c.Port(), c.relatedAddress) +} + +// LastReceived returns a time.Time indicating the last time +// this candidate was received +func (c *candidateBase) LastReceived() time.Time { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lastReceived +} + +func (c *candidateBase) setLastReceived(t time.Time) { + c.lock.Lock() + defer c.lock.Unlock() + c.lastReceived = t +} + +// LastSent returns a time.Time indicating the last time +// this candidate was sent +func (c *candidateBase) LastSent() time.Time { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lastSent +} + +func (c *candidateBase) setLastSent(t time.Time) { + c.lock.Lock() + defer c.lock.Unlock() + c.lastSent = t +} + +func (c *candidateBase) seen(outbound bool) { + if outbound { + c.setLastSent(time.Now()) + } else { + c.setLastReceived(time.Now()) + } +} + +func (c *candidateBase) addr() net.Addr { + return &net.UDPAddr{ + IP: c.IP(), + Port: c.Port(), + } +} diff --git a/candidate_host.go b/candidate_host.go new file mode 100644 index 0000000..0834bea --- /dev/null +++ b/candidate_host.go @@ -0,0 +1,28 @@ +package ice + +import ( + "net" +) + +// CandidateHost is a candidate of type host +type CandidateHost struct { + candidateBase +} + +// NewCandidateHost creates a new host candidate +func NewCandidateHost(network string, ip net.IP, port int, component uint16) (*CandidateHost, error) { + networkType, err := determineNetworkType(network, ip) + if err != nil { + return nil, err + } + + return &CandidateHost{ + candidateBase: candidateBase{ + networkType: networkType, + candidateType: CandidateTypeHost, + ip: ip, + port: port, + component: component, + }, + }, nil +} diff --git a/candidate_peer_reflexive.go b/candidate_peer_reflexive.go new file mode 100644 index 0000000..ba26fc7 --- /dev/null +++ b/candidate_peer_reflexive.go @@ -0,0 +1,30 @@ +package ice + +import "net" + +// CandidatePeerReflexive ... +type CandidatePeerReflexive struct { + candidateBase +} + +// NewCandidatePeerReflexive creates a new peer reflective candidate +func NewCandidatePeerReflexive(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*CandidatePeerReflexive, error) { + networkType, err := determineNetworkType(network, ip) + if err != nil { + return nil, err + } + + return &CandidatePeerReflexive{ + candidateBase: candidateBase{ + networkType: networkType, + candidateType: CandidateTypePeerReflexive, + ip: ip, + port: port, + component: component, + relatedAddress: &CandidateRelatedAddress{ + Address: relAddr, + Port: relPort, + }, + }, + }, nil +} diff --git a/candidate_relay.go b/candidate_relay.go new file mode 100644 index 0000000..2264361 --- /dev/null +++ b/candidate_relay.go @@ -0,0 +1,32 @@ +package ice + +import ( + "net" +) + +// CandidateRelay ... +type CandidateRelay struct { + candidateBase +} + +// NewCandidateRelay creates a new relay candidate +func NewCandidateRelay(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*CandidateRelay, error) { + networkType, err := determineNetworkType(network, ip) + if err != nil { + return nil, err + } + + return &CandidateRelay{ + candidateBase: candidateBase{ + networkType: networkType, + candidateType: CandidateTypeRelay, + ip: ip, + port: port, + component: component, + relatedAddress: &CandidateRelatedAddress{ + Address: relAddr, + Port: relPort, + }, + }, + }, nil +} diff --git a/candidate_server_reflexive.go b/candidate_server_reflexive.go new file mode 100644 index 0000000..be27f54 --- /dev/null +++ b/candidate_server_reflexive.go @@ -0,0 +1,30 @@ +package ice + +import "net" + +// CandidateServerReflexive ... +type CandidateServerReflexive struct { + candidateBase +} + +// NewCandidateServerReflexive creates a new server reflective candidate +func NewCandidateServerReflexive(network string, ip net.IP, port int, component uint16, relAddr string, relPort int) (*CandidateServerReflexive, error) { + networkType, err := determineNetworkType(network, ip) + if err != nil { + return nil, err + } + + return &CandidateServerReflexive{ + candidateBase: candidateBase{ + networkType: networkType, + candidateType: CandidateTypeServerReflexive, + ip: ip, + port: port, + component: component, + relatedAddress: &CandidateRelatedAddress{ + Address: relAddr, + Port: relPort, + }, + }, + }, nil +} diff --git a/candidate_test.go b/candidate_test.go index aeb8955..16c9900 100644 --- a/candidate_test.go +++ b/candidate_test.go @@ -4,38 +4,42 @@ import "testing" func TestCandidatePriority(t *testing.T) { for _, test := range []struct { - Candidate *Candidate + Candidate Candidate WantPriority uint32 }{ { - Candidate: &Candidate{ - Type: CandidateTypeHost, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + Candidate: &CandidateHost{ + candidateBase: candidateBase{ + candidateType: CandidateTypeHost, + component: ComponentRTP, + }, }, WantPriority: 2130706431, }, { - Candidate: &Candidate{ - Type: CandidateTypePeerReflexive, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + Candidate: &CandidatePeerReflexive{ + candidateBase: candidateBase{ + candidateType: CandidateTypePeerReflexive, + component: ComponentRTP, + }, }, WantPriority: 1862270975, }, { - Candidate: &Candidate{ - Type: CandidateTypeServerReflexive, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + Candidate: &CandidateServerReflexive{ + candidateBase: candidateBase{ + candidateType: CandidateTypeServerReflexive, + component: ComponentRTP, + }, }, WantPriority: 1694498815, }, { - Candidate: &Candidate{ - Type: CandidateTypeRelay, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + Candidate: &CandidateRelay{ + candidateBase: candidateBase{ + candidateType: CandidateTypeRelay, + component: ComponentRTP, + }, }, WantPriority: 16777215, }, diff --git a/candidatepair.go b/candidatepair.go index 6f5ac72..82a9a24 100644 --- a/candidatepair.go +++ b/candidatepair.go @@ -6,7 +6,7 @@ import ( "github.com/pion/stun" ) -func newCandidatePair(local, remote *Candidate, controlling bool) *candidatePair { +func newCandidatePair(local, remote Candidate, controlling bool) *candidatePair { return &candidatePair{ iceRoleControlling: controlling, remote: remote, @@ -17,8 +17,8 @@ func newCandidatePair(local, remote *Candidate, controlling bool) *candidatePair // candidatePair represents a combination of a local and remote candidate type candidatePair struct { iceRoleControlling bool - remote *Candidate - local *Candidate + remote Candidate + local Candidate } func (p *candidatePair) String() string { @@ -83,7 +83,7 @@ func (p *candidatePair) Write(b []byte) (int, error) { } // keepaliveCandidate sends a STUN Binding Indication to the remote candidate -func (a *Agent) keepaliveCandidate(local, remote *Candidate) { +func (a *Agent) keepaliveCandidate(local, remote Candidate) { msg, err := stun.Build(stun.NewType(stun.MethodBinding, stun.ClassIndication), stun.TransactionID, stun.NewUsername(a.remoteUfrag+":"+a.localUfrag), stun.NewShortTermIntegrity(a.remotePwd), @@ -98,7 +98,7 @@ func (a *Agent) keepaliveCandidate(local, remote *Candidate) { a.sendSTUN(msg, local, remote) } -func (a *Agent) sendSTUN(msg *stun.Message, local, remote *Candidate) { +func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) { _, err := local.writeTo(msg.Raw, remote) if err != nil { a.log.Tracef("failed to send STUN message: %s", err) diff --git a/candidatepair_test.go b/candidatepair_test.go index 313e684..636cad2 100644 --- a/candidatepair_test.go +++ b/candidatepair_test.go @@ -3,25 +3,29 @@ package ice import "testing" var ( - hostCandidate = &Candidate{ - Type: CandidateTypeHost, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + hostCandidate = &CandidateHost{ + candidateBase: candidateBase{ + candidateType: CandidateTypeHost, + component: ComponentRTP, + }, } - prflxCandidate = &Candidate{ - Type: CandidateTypePeerReflexive, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + prflxCandidate = &CandidatePeerReflexive{ + candidateBase: candidateBase{ + candidateType: CandidateTypePeerReflexive, + component: ComponentRTP, + }, } - srflxCandidate = &Candidate{ - Type: CandidateTypeServerReflexive, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + srflxCandidate = &CandidateServerReflexive{ + candidateBase: candidateBase{ + candidateType: CandidateTypeServerReflexive, + component: ComponentRTP, + }, } - relayCandidate = &Candidate{ - Type: CandidateTypeRelay, - LocalPreference: defaultLocalPreference, - Component: ComponentRTP, + relayCandidate = &CandidateRelay{ + candidateBase: candidateBase{ + candidateType: CandidateTypeRelay, + component: ComponentRTP, + }, } ) diff --git a/gather.go b/gather.go index b6c7b56..32bb484 100644 --- a/gather.go +++ b/gather.go @@ -107,10 +107,9 @@ func gatherCandidatesLocal(a *Agent, networkTypes []NetworkType) { continue } - networkType := c.NetworkType - set := a.localCandidates[networkType] + set := a.localCandidates[c.NetworkType()] set = append(set, c) - a.localCandidates[networkType] = set + a.localCandidates[c.NetworkType()] = set c.start(a, conn) } @@ -154,10 +153,9 @@ func gatherCandidatesReflective(a *Agent, urls []*URL, networkTypes []NetworkTyp continue } - networkType := c.NetworkType - set := a.localCandidates[networkType] + set := a.localCandidates[c.NetworkType()] set = append(set, c) - a.localCandidates[networkType] = set + a.localCandidates[c.NetworkType()] = set c.start(a, conn) diff --git a/selection.go b/selection.go index 182237e..8cfa305 100644 --- a/selection.go +++ b/selection.go @@ -10,9 +10,9 @@ import ( type pairCandidateSelector interface { Start() ContactCandidates() - PingCandidate(local, remote *Candidate) - HandleSucessResponse(m *stun.Message, local, remote *Candidate, remoteAddr net.Addr) - HandleBindingRequest(m *stun.Message, local, remote *Candidate) + PingCandidate(local, remote Candidate) + HandleSucessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) + HandleBindingRequest(m *stun.Message, local, remote Candidate) } type controllingSelector struct { @@ -62,7 +62,7 @@ func (s *controllingSelector) nominatePair(pair *candidatePair) { s.agent.sendBindingRequest(msg, pair.local, pair.remote) } -func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remote *Candidate) { +func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) { s.agent.sendBindingSuccess(m, local, remote) p := s.agent.findValidPair(local, remote) @@ -72,7 +72,7 @@ func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remot } } -func (s *controllingSelector) HandleSucessResponse(m *stun.Message, local, remote *Candidate, remoteAddr net.Addr) { +func (s *controllingSelector) HandleSucessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) { ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID) if !ok { s.log.Errorf("discard message from (%s), invalid TransactionID %s", remote, m.TransactionID) @@ -96,7 +96,7 @@ func (s *controllingSelector) HandleSucessResponse(m *stun.Message, local, remot } } -func (s *controllingSelector) PingCandidate(local, remote *Candidate) { +func (s *controllingSelector) PingCandidate(local, remote Candidate) { msg, err := stun.Build(stun.BindingRequest, stun.TransactionID, stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag), AttrControlling(s.agent.tieBreaker), @@ -132,7 +132,7 @@ func (s *controlledSelector) ContactCandidates() { } } -func (s *controlledSelector) PingCandidate(local, remote *Candidate) { +func (s *controlledSelector) PingCandidate(local, remote Candidate) { msg, err := stun.Build(stun.BindingRequest, stun.TransactionID, stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag), AttrControlled(s.agent.tieBreaker), @@ -149,7 +149,7 @@ func (s *controlledSelector) PingCandidate(local, remote *Candidate) { s.agent.sendBindingRequest(msg, local, remote) } -func (s *controlledSelector) HandleSucessResponse(m *stun.Message, local, remote *Candidate, remoteAddr net.Addr) { +func (s *controlledSelector) HandleSucessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) { // TODO according to the standard we should specifically answer a failed nomination: // https://tools.ietf.org/html/rfc8445#section-7.3.1.5 // If the controlled agent does not accept the request from the @@ -176,7 +176,7 @@ func (s *controlledSelector) HandleSucessResponse(m *stun.Message, local, remote s.agent.addValidPair(local, remote) } -func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote *Candidate) { +func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) { if m.Contains(stun.AttrUseCandidate) { // https://tools.ietf.org/html/rfc8445#section-7.3.1.5 p := s.agent.findValidPair(local, remote) diff --git a/transport_test.go b/transport_test.go index c263634..4759f7b 100644 --- a/transport_test.go +++ b/transport_test.go @@ -269,22 +269,20 @@ func pipeWithTimeout(iceTimeout time.Duration, iceKeepalive time.Duration) (*Con return aConn, bConn } -func copyCandidate(orig *Candidate) *Candidate { - c := &Candidate{ - Type: orig.Type, - NetworkType: orig.NetworkType, - IP: orig.IP, - Port: orig.Port, - } - - if orig.RelatedAddress != nil { - c.RelatedAddress = &CandidateRelatedAddress{ - Address: orig.RelatedAddress.Address, - Port: orig.RelatedAddress.Port, +func copyCandidate(o Candidate) Candidate { + switch orig := o.(type) { + case *CandidateHost: + return &CandidateHost{ + candidateBase{ + networkType: orig.networkType, + ip: orig.ip, + port: orig.port, + component: orig.component, + }, } + default: + return nil } - - return c } func onConnected() (func(ConnectionState), chan struct{}) {