Add more configuration to AgentOptions

This commit is contained in:
Kevin Wang
2025-08-25 11:08:31 -04:00
parent 4b50bc8078
commit 71de7d2b18
2 changed files with 273 additions and 0 deletions

View File

@@ -67,3 +67,88 @@ func WithNominationAttribute(attrType uint16) AgentOption {
return nil
}
}
// WithIncludeLoopback includes loopback addresses in the candidate list.
// By default, loopback addresses are excluded.
//
// Example:
//
// agent, err := NewAgentWithOptions(WithIncludeLoopback())
func WithIncludeLoopback() AgentOption {
return func(a *Agent) error {
a.includeLoopback = true
return nil
}
}
// WithTCPPriorityOffset sets a number which is subtracted from the default (UDP) candidate type preference
// for host, srflx and prfx candidate types. It helps to configure relative preference of UDP candidates
// against TCP ones. Relay candidates for TCP and UDP are always 0 and not affected by this setting.
// When not set, defaultTCPPriorityOffset (27) is used.
//
// Example:
//
// agent, err := NewAgentWithOptions(WithTCPPriorityOffset(50))
func WithTCPPriorityOffset(offset uint16) AgentOption {
return func(a *Agent) error {
a.tcpPriorityOffset = offset
return nil
}
}
// WithDisableActiveTCP disables Active TCP candidates.
// When TCP is enabled, Active TCP candidates will be created when a new passive TCP remote candidate is added
// unless this option is used.
//
// Example:
//
// agent, err := NewAgentWithOptions(WithDisableActiveTCP())
func WithDisableActiveTCP() AgentOption {
return func(a *Agent) error {
a.disableActiveTCP = true
return nil
}
}
// WithBindingRequestHandler sets a handler to allow applications to perform logic on incoming STUN Binding Requests.
// This was implemented to allow users to:
// - Log incoming Binding Requests for debugging
// - Implement draft-thatcher-ice-renomination
// - Implement custom CandidatePair switching logic
//
// Example:
//
// handler := func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool {
// log.Printf("Binding request from %s to %s", remote.Address(), local.Address())
// return true // Accept the request
// }
// agent, err := NewAgentWithOptions(WithBindingRequestHandler(handler))
func WithBindingRequestHandler(
handler func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool,
) AgentOption {
return func(a *Agent) error {
a.userBindingRequestHandler = handler
return nil
}
}
// WithEnableUseCandidateCheckPriority enables checking for equal or higher priority when
// switching selected candidate pair if the peer requests USE-CANDIDATE and agent is a lite agent.
// This is disabled by default, i.e. when peer requests USE-CANDIDATE, the selected pair will be
// switched to that irrespective of relative priority between current selected pair
// and priority of the pair being switched to.
//
// Example:
//
// agent, err := NewAgentWithOptions(WithEnableUseCandidateCheckPriority())
func WithEnableUseCandidateCheckPriority() AgentOption {
return func(a *Agent) error {
a.enableUseCandidateCheckPriority = true
return nil
}
}

View File

@@ -10,6 +10,46 @@ import (
"github.com/stretchr/testify/assert"
)
// testBooleanOption is a helper function to test boolean agent options.
type booleanOptionTest struct {
optionFunc func() AgentOption
getValue func(*Agent) bool
configSetter func(*AgentConfig, bool)
}
func testBooleanOption(t *testing.T, test booleanOptionTest, optionName string) {
t.Helper()
t.Run("enables "+optionName, func(t *testing.T) {
agent, err := NewAgentWithOptions(test.optionFunc())
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.True(t, test.getValue(agent))
})
t.Run("default is false", func(t *testing.T) {
agent, err := NewAgentWithOptions()
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.False(t, test.getValue(agent))
})
t.Run("works with config", func(t *testing.T) {
config := &AgentConfig{
NetworkTypes: []NetworkType{NetworkTypeUDP4},
}
test.configSetter(config, true)
agent, err := NewAgent(config)
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.True(t, test.getValue(agent))
})
}
func TestDefaultNominationValueGenerator(t *testing.T) {
t.Run("generates incrementing values", func(t *testing.T) {
generator := DefaultNominationValueGenerator()
@@ -108,3 +148,151 @@ func TestWithNominationAttribute(t *testing.T) {
assert.Equal(t, stun.AttrType(0x0030), agent.nominationAttribute)
})
}
func TestWithIncludeLoopback(t *testing.T) {
testBooleanOption(t, booleanOptionTest{
optionFunc: WithIncludeLoopback,
getValue: func(a *Agent) bool { return a.includeLoopback },
configSetter: func(c *AgentConfig, v bool) { c.IncludeLoopback = v },
}, "loopback addresses")
}
func TestWithTCPPriorityOffset(t *testing.T) {
t.Run("sets custom TCP priority offset", func(t *testing.T) {
customOffset := uint16(50)
agent, err := NewAgentWithOptions(WithTCPPriorityOffset(customOffset))
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.Equal(t, customOffset, agent.tcpPriorityOffset)
})
t.Run("default is 27", func(t *testing.T) {
agent, err := NewAgentWithOptions()
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
// The default is set via initWithDefaults
assert.Equal(t, uint16(27), agent.tcpPriorityOffset)
})
t.Run("works with config", func(t *testing.T) {
customOffset := uint16(100)
config := &AgentConfig{
NetworkTypes: []NetworkType{NetworkTypeUDP4},
TCPPriorityOffset: &customOffset,
}
agent, err := NewAgent(config)
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.Equal(t, customOffset, agent.tcpPriorityOffset)
})
}
func TestWithDisableActiveTCP(t *testing.T) {
testBooleanOption(t, booleanOptionTest{
optionFunc: WithDisableActiveTCP,
getValue: func(a *Agent) bool { return a.disableActiveTCP },
configSetter: func(c *AgentConfig, v bool) { c.DisableActiveTCP = v },
}, "active TCP disabling")
}
func TestWithBindingRequestHandler(t *testing.T) {
t.Run("sets binding request handler", func(t *testing.T) {
handlerCalled := false
handler := func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool {
handlerCalled = true
return true
}
agent, err := NewAgentWithOptions(WithBindingRequestHandler(handler))
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.NotNil(t, agent.userBindingRequestHandler)
// Test that the handler is actually the one we set
// We can't directly compare functions, but we can call it
if agent.userBindingRequestHandler != nil {
agent.userBindingRequestHandler(nil, nil, nil, nil)
assert.True(t, handlerCalled)
}
})
t.Run("default is nil", func(t *testing.T) {
agent, err := NewAgentWithOptions()
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.Nil(t, agent.userBindingRequestHandler)
})
t.Run("works with config", func(t *testing.T) {
handlerCalled := false
handler := func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool {
handlerCalled = true
return true
}
config := &AgentConfig{
NetworkTypes: []NetworkType{NetworkTypeUDP4},
BindingRequestHandler: handler,
}
agent, err := NewAgent(config)
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.NotNil(t, agent.userBindingRequestHandler)
if agent.userBindingRequestHandler != nil {
agent.userBindingRequestHandler(nil, nil, nil, nil)
assert.True(t, handlerCalled)
}
})
}
func TestWithEnableUseCandidateCheckPriority(t *testing.T) {
testBooleanOption(t, booleanOptionTest{
optionFunc: WithEnableUseCandidateCheckPriority,
getValue: func(a *Agent) bool { return a.enableUseCandidateCheckPriority },
configSetter: func(c *AgentConfig, v bool) { c.EnableUseCandidateCheckPriority = v },
}, "use candidate check priority")
}
func TestMultipleConfigOptions(t *testing.T) {
t.Run("can apply multiple options", func(t *testing.T) {
customOffset := uint16(100)
handlerCalled := false
handler := func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool {
handlerCalled = true
return true
}
agent, err := NewAgentWithOptions(
WithIncludeLoopback(),
WithTCPPriorityOffset(customOffset),
WithDisableActiveTCP(),
WithBindingRequestHandler(handler),
WithEnableUseCandidateCheckPriority(),
)
assert.NoError(t, err)
defer agent.Close() //nolint:errcheck
assert.True(t, agent.includeLoopback)
assert.Equal(t, customOffset, agent.tcpPriorityOffset)
assert.True(t, agent.disableActiveTCP)
assert.NotNil(t, agent.userBindingRequestHandler)
assert.True(t, agent.enableUseCandidateCheckPriority)
if agent.userBindingRequestHandler != nil {
agent.userBindingRequestHandler(nil, nil, nil, nil)
assert.True(t, handlerCalled)
}
})
}