Skip to content

Follow RFC 8445 to skip connectivity checks in lite mode#909

Open
erivni wants to merge 2 commits intopion:mainfrom
erivni:ice_lite_connectivity
Open

Follow RFC 8445 to skip connectivity checks in lite mode#909
erivni wants to merge 2 commits intopion:mainfrom
erivni:ice_lite_connectivity

Conversation

@erivni
Copy link
Copy Markdown

@erivni erivni commented Apr 14, 2026

Description

Lite ICE agents are STUN-server-only — they must not generate connectivity checks (RFC 8445 §7). This change fixes two gaps in the current lite mode implementation for the full + lite agent scenario:

  • Suppress triggered checks (controlledSelector.HandleBindingRequest): lite agents no longer call PingCandidate in response to an inbound Binding Request, per RFC 8445 §7
  • Direct nomination (controlledSelector.HandleBindingRequest): a lite controlled agent accepts USE-CANDIDATE and sets the selected pair immediately, without requiring the pair to have first reached Succeeded state via a triggered check that was never sent, per RFC 8445 §7.3.2.
    Tests added for both behaviours.

The both-lite controlling path (liteSelector.ContactCandidates) is not changed here and remains tracked in #96.

Reference issue

Fixes #96
See also #907

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.59%. Comparing base (ffbe6d5) to head (ccd9108).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #909      +/-   ##
==========================================
+ Coverage   88.48%   88.59%   +0.10%     
==========================================
  Files          45       45              
  Lines        5811     5811              
==========================================
+ Hits         5142     5148       +6     
+ Misses        460      455       -5     
+ Partials      209      208       -1     
Flag Coverage Δ
go 88.59% <100.00%> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@erivni erivni force-pushed the ice_lite_connectivity branch from 4c3a539 to d43393c Compare April 14, 2026 12:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates ICE Lite behavior to align with RFC 8445 by ensuring lite agents (STUN-server-only) do not generate connectivity checks, while still correctly handling nomination when acting as the controlled agent.

Changes:

  • Suppress triggered checks (PingCandidate) for lite controlled agents on inbound Binding Requests.
  • Allow lite controlled agents to accept USE-CANDIDATE nomination without requiring a prior triggered check.
  • Add unit/integration tests covering lite-mode check suppression and full-to-lite interoperability.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
selection.go Adjust controlled selector to skip triggered checks in lite mode and handle nomination for lite controlled agents.
selection_test.go Add tests validating lite behavior (no triggered checks) and a full-to-lite integration scenario.
Comments suppressed due to low confidence (1)

selection.go:446

  • In the lite-agent nomination path, the pair can be selected even when it hasn’t reached CandidatePairStateSucceeded, but the code doesn’t update pair.state to Succeeded. This can leave the selected (nominated) pair stuck in Waiting/InProgress, which is internally inconsistent (e.g., Conn.WriteToPair rejects non-Succeeded pairs) and makes stats/reporting misleading. Consider marking the pair as CandidatePairStateSucceeded (and/or otherwise adding it to the valid list semantics) when a lite controlled agent accepts nomination.
		if pair.state == CandidatePairStateSucceeded || s.agent.lite {
			// For full agents: pair reached Succeeded via a triggered check (RFC 8445 §7.3.1.5).
			// For lite agents: RFC 8445 §7.3.2 — the lite agent directly constructs the pair,
			// places it in the valid list, and sets the nominated flag; no triggered check needed.
			selectedPair := s.agent.getSelectedPair()
			if s.shouldSwitchSelectedPair(pair, selectedPair, nominationValue) {
				s.log.Tracef("Accepting nomination for pair %s", pair)
				s.agent.setSelectedPair(pair)
			} else {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread selection_test.go
Comment on lines +1583 to +1607
t.Run("NominationStillAccepted", func(t *testing.T) {
// A lite agent must still accept USE-CANDIDATE and set the selected pair
// (RFC 8445 §7.3.2), even though it never sends its own checks.
agent, local, remote, pair := setupAgent(t)
pair.state = CandidatePairStateSucceeded

ls, ok := agent.getSelector().(*liteSelector)
require.True(t, ok)

assert.Nil(t, agent.getSelectedPair(), "no pair selected yet")

msg, err := stun.Build(stun.BindingRequest,
stun.TransactionID,
stun.NewUsername(agent.localUfrag+":"+agent.remoteUfrag),
UseCandidate(),
stun.NewShortTermIntegrity(agent.localPwd),
stun.Fingerprint,
)
require.NoError(t, err)

ls.HandleBindingRequest(msg, local, remote)

assert.Equal(t, pair, agent.getSelectedPair(),
"lite controlled agent must accept nomination and set selected pair")
// Still no triggered check emitted
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NominationStillAccepted sets pair.state = CandidatePairStateSucceeded before calling HandleBindingRequest, so it doesn’t actually verify the new RFC 8445 §7.3.2 behavior described in the PR (accepting USE-CANDIDATE and selecting the pair even when the pair has not reached Succeeded via a triggered check). To cover the intended behavior, keep the pair in Waiting/InProgress and assert that the lite controlled agent still selects it on receiving a USE-CANDIDATE request (while still not emitting any triggered checks).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@erivni erivni changed the title follow RFC 8445 to skip connectivity checks in lite mode Follow RFC 8445 to skip connectivity checks in lite mode Apr 14, 2026
@JoTurk JoTurk requested review from JoTurk and sirzooro April 14, 2026 12:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

selection.go:445

  • In the lite-agent nomination path, setSelectedPair(pair) can be executed while pair.state is still CandidatePairStateWaiting (since the lite controlled agent never sends checks that would transition it to Succeeded). This leaves the selected/nominated pair reported as waiting in GetCandidatePairsStats()/GetCandidatePairsInfo(), and also causes Conn.WriteToPair() to fail with ErrCandidatePairNotSucceeded even though the pair is actively selected and working. Consider explicitly transitioning the pair to CandidatePairStateSucceeded when a lite agent accepts a nomination (RFC 8445 §7.3.2 “places it in the valid list”).
		if pair.state == CandidatePairStateSucceeded || s.agent.lite {
			// For full agents: pair reached Succeeded via a triggered check (RFC 8445 §7.3.1.5).
			// For lite agents: RFC 8445 §7.3.2 — the lite agent directly constructs the pair,
			// places it in the valid list, and sets the nominated flag; no triggered check needed.
			selectedPair := s.agent.getSelectedPair()
			if s.shouldSwitchSelectedPair(pair, selectedPair, nominationValue) {
				s.log.Tracef("Accepting nomination for pair %s", pair)
				s.agent.setSelectedPair(pair)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member

@JoTurk JoTurk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, thank you.
I'll try to look at the rest of the code again, before we merge or tag this to make sure we don't introduce bugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Implement ICE Lite controlling agent

3 participants