Make Candidate an interface

This change will allow us to have custom logic and members
per interface type. Relay candidates will have a completely different
read loop, and candidate specific state.

Relates to #47
This commit is contained in:
Sean DuBois
2019-05-24 15:55:42 -07:00
parent a060774d7a
commit 63b37975b6
14 changed files with 468 additions and 360 deletions
+27 -30
View File
@@ -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
}
+6 -6
View File
@@ -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")
}
+19 -257
View File
@@ -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
}
+225
View File
@@ -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(),
}
}
+28
View File
@@ -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
}
+30
View File
@@ -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
}
+32
View File
@@ -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
}
+30
View File
@@ -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
}
+21 -17
View File
@@ -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,
},
+5 -5
View File
@@ -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)
+20 -16
View File
@@ -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,
},
}
)
+4 -6
View File
@@ -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)
+9 -9
View File
@@ -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)
+12 -14
View File
@@ -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{}) {