Skip to content

Commit

Permalink
Add config to run Agent in Lite mode
Browse files Browse the repository at this point in the history
Allow the agent to run in Lite mode. This is useful in
cases where you never want connectivity checks, and reduces
possible attacks surfaces when you have a public IP.
  • Loading branch information
seppo0010 authored and Sean-Der committed Sep 2, 2019
1 parent 45cb33e commit 953f36f
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ env:
- GO111MODULE=on

before_script:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.17.1

script:
- golangci-lint run ./...
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
* [Yutaka Takeda](https://github.com/enobufs)
* [Atsushi Watanabe](https://github.com/at-wat)
* [Robert Eperjesi](https://github.com/epes)
* [Sebastian Waisbrot](https://github.com/seppo0010)

### License
MIT License - see [LICENSE](LICENSE) for full text
92 changes: 62 additions & 30 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type Agent struct {

trickle bool
tieBreaker uint64
lite bool
connectionState ConnectionState
gatheringState GatheringState

Expand Down Expand Up @@ -218,6 +219,9 @@ type AgentConfig struct {
// or mark the connection as failed if no valid candidate is available
CandidateSelectionTimeout *time.Duration

// Lite agents do not perform connectivity check and only provide host candidates.
Lite bool

// HostAcceptanceMinWait specify a minimum wait time before selecting host candidates
HostAcceptanceMinWait *time.Duration
// HostAcceptanceMinWait specify a minimum wait time before selecting srflx candidates
Expand All @@ -232,6 +236,49 @@ type AgentConfig struct {
Net *vnet.Net
}

func containsCandidateType(candidateType CandidateType, candidateTypeList []CandidateType) bool {
if candidateTypeList == nil {
return false
}
for _, ct := range candidateTypeList {
if ct == candidateType {
return true
}
}
return false
}

func createMulticastDNS(mDNSMode MulticastDNSMode, mDNSName string, log logging.LeveledLogger) (*mdns.Conn, MulticastDNSMode, error) {
if mDNSMode == MulticastDNSModeDisabled {
return nil, mDNSMode, nil
}

addr, mdnsErr := net.ResolveUDPAddr("udp4", mdns.DefaultAddress)
if mdnsErr != nil {
return nil, mDNSMode, mdnsErr
}

l, mdnsErr := net.ListenUDP("udp4", addr)
if mdnsErr != nil {
// If ICE fails to start MulticastDNS server just warn the user and continue
log.Errorf("Failed to enable mDNS, continuing in mDNS disabled mode: (%s)", mdnsErr)
return nil, MulticastDNSModeDisabled, nil
}

switch mDNSMode {
case MulticastDNSModeQueryOnly:
conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{})
return conn, mDNSMode, err
case MulticastDNSModeQueryAndGather:
conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{
LocalNames: []string{mDNSName},
})
return conn, mDNSMode, err
default:
return nil, mDNSMode, nil
}
}

// NewAgent creates a new Agent
func NewAgent(config *AgentConfig) (*Agent, error) {
if config.PortMax < config.PortMin {
Expand All @@ -255,41 +302,14 @@ func NewAgent(config *AgentConfig) (*Agent, error) {
log := loggerFactory.NewLogger("ice")

var mDNSConn *mdns.Conn
mDNSConn, err = func() (*mdns.Conn, error) {
if mDNSMode == MulticastDNSModeDisabled {
return nil, nil
}

addr, mdnsErr := net.ResolveUDPAddr("udp4", mdns.DefaultAddress)
if mdnsErr != nil {
return nil, mdnsErr
}

l, mdnsErr := net.ListenUDP("udp4", addr)
if mdnsErr != nil {
// If ICE fails to start MulticastDNS server just warn the user and continue
log.Errorf("Failed to enable mDNS, continuing in mDNS disabled mode: (%s)", mdnsErr)
mDNSMode = MulticastDNSModeDisabled
return nil, nil
}

switch mDNSMode {
case MulticastDNSModeQueryOnly:
return mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{})
case MulticastDNSModeQueryAndGather:
return mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{
LocalNames: []string{mDNSName},
})
default:
return nil, nil
}
}()
mDNSConn, mDNSMode, err = createMulticastDNS(mDNSMode, mDNSName, log)
if err != nil {
return nil, err
}

a := &Agent{
tieBreaker: rand.New(rand.NewSource(time.Now().UnixNano())).Uint64(),
lite: config.Lite,
gatheringState: GatheringStateNew,
connectionState: ConnectionStateNew,
localCandidates: make(map[NetworkType][]Candidate),
Expand Down Expand Up @@ -395,6 +415,14 @@ func NewAgent(config *AgentConfig) (*Agent, error) {
a.candidateTypes = config.CandidateTypes
}

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
}

go a.taskLoop()

// Initialize local candidates
Expand Down Expand Up @@ -459,6 +487,10 @@ func (a *Agent) startConnectivityChecks(isControlling bool, remoteUfrag, remoteP
a.selector = &controlledSelector{agent: a, log: a.log}
}

if a.lite {
a.selector = &liteSelector{pairCandidateSelector: a.selector}
}

a.selector.Start()

agent.updateConnectionState(ConnectionStateChecking)
Expand Down Expand Up @@ -967,7 +999,7 @@ func (a *Agent) handleInbound(m *stun.Message, local Candidate, remote net.Addr)
}
remoteCandidate = prflxCandidate

a.log.Debugf("adding a new peer-reflexive candiate: %s ", remote)
a.log.Debugf("adding a new peer-reflexive candidate: %s ", remote)
a.addRemoteCandidate(remoteCandidate)
}

Expand Down
75 changes: 75 additions & 0 deletions agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,81 @@ func TestConnectivityOnStartup(t *testing.T) {
}
}

func TestConnectivityLite(t *testing.T) {
stunServerURL := &URL{
Scheme: SchemeTypeSTUN,
Host: "1.2.3.4",
Port: 3478,
Proto: ProtoTypeUDP,
}

v, err := buildVNet(&vnet.NATType{
MappingBehavior: vnet.EndpointIndependent,
FilteringBehavior: vnet.EndpointIndependent,
})
require.NoError(t, err, "should succeed")
defer v.close()

aNotifier, aConnected := onConnected()
bNotifier, bConnected := onConnected()

var wg sync.WaitGroup
wg.Add(2)

cfg0 := &AgentConfig{
Urls: []*URL{stunServerURL},
Trickle: true,
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
Net: v.net0,

taskLoopInterval: time.Hour,
}

aAgent, err := NewAgent(cfg0)
require.NoError(t, err)
require.NoError(t, aAgent.OnConnectionStateChange(aNotifier))
require.NoError(t, aAgent.OnCandidate(func(candidate Candidate) {
if candidate == nil {
wg.Done()
}
}))
require.NoError(t, aAgent.GatherCandidates())

cfg1 := &AgentConfig{
Urls: []*URL{},
Trickle: true,
Lite: true,
CandidateTypes: []CandidateType{CandidateTypeHost},
NetworkTypes: supportedNetworkTypes,
MulticastDNSMode: MulticastDNSModeDisabled,
Net: v.net1,
taskLoopInterval: time.Hour,
}

bAgent, err := NewAgent(cfg1)
require.NoError(t, err)
require.NoError(t, bAgent.OnConnectionStateChange(bNotifier))
require.NoError(t, bAgent.OnCandidate(func(candidate Candidate) {
if candidate == nil {
wg.Done()
}
}))
require.NoError(t, bAgent.GatherCandidates())

wg.Wait()
aConn, bConn := connectWithVNet(aAgent, bAgent)

// Ensure pair selected
// Note: this assumes ConnectionStateConnected is thrown after selecting the final pair
<-aConnected
<-bConnected

if !closePipe(t, aConn, bConn) {
return
}
}

func TestInboundValidity(t *testing.T) {
buildMsg := func(class stun.MessageClass, username, key string) *stun.Message {
msg, err := stun.Build(stun.NewType(stun.MethodBinding, class), stun.TransactionID,
Expand Down
2 changes: 1 addition & 1 deletion candidatepair_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (c CandidatePairState) String() string {
case CandidatePairStateFailed:
return "failed"
case CandidatePairStateSucceeded:
return "succeded"
return "succeeded"
}
return "Unknown candidate pair state"
}
7 changes: 7 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ var (

// ErrAddressParseFailed indicates we were unable to parse a candidate address
ErrAddressParseFailed = errors.New("failed to parse address")

// ErrLiteUsingNonHostCandidates indicates non host candidates were selected for a lite agent
ErrLiteUsingNonHostCandidates = errors.New("lite agents must only use host candidates")

// 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")
)
14 changes: 14 additions & 0 deletions selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,17 @@ func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote
s.PingCandidate(local, remote)
}
}

type liteSelector struct {
pairCandidateSelector
}

// A lite selector should not contact candidates
func (s *liteSelector) ContactCandidates() {
if _, ok := s.pairCandidateSelector.(*controllingSelector); ok {
// pion/ice#96
// TODO: implement lite controlling agent. For now falling back to full agent.
// This only happens if both peers are lite. See RFC 8445 S6.1.1 and S6.2
s.pairCandidateSelector.ContactCandidates()
}
}

0 comments on commit 953f36f

Please sign in to comment.