Skip to content

Implement SPED (STUN Protocol for Embedding DTLS) - #876

Open
fippo wants to merge 8 commits into
pion:mainfrom
fippo:sped-remainder
Open

Implement SPED (STUN Protocol for Embedding DTLS)#876
fippo wants to merge 8 commits into
pion:mainfrom
fippo:sped-remainder

Conversation

@fippo

@fippo fippo commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

(based on #914 )

Part of pion/webrtc#3335

@codecov

codecov Bot commented Jan 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.89503% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.65%. Comparing base (1229c56) to head (acaa53d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
agent.go 92.85% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #876      +/-   ##
==========================================
+ Coverage   88.25%   88.65%   +0.39%     
==========================================
  Files          46       47       +1     
  Lines        6293     6459     +166     
==========================================
+ Hits         5554     5726     +172     
+ Misses        504      499       -5     
+ Partials      235      234       -1     
Flag Coverage Δ
go 88.65% <98.89%> (+0.39%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@fippo
fippo force-pushed the sped-remainder branch 5 times, most recently from 10ebcd0 to 53df31e Compare January 30, 2026 17:37
@fippo
fippo marked this pull request as ready for review March 26, 2026 18:53
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 24, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit to fippo/pion-ice that referenced this pull request Apr 25, 2026
which will be used in pion#876 where the presence of some
attributes depends on whether they were used in the
original request.
fippo added a commit that referenced this pull request Apr 25, 2026
which will be used in #876 where the presence of some
attributes depends on whether they were used in the
original request.

(split from that to make review simpler, no functional changes)
@fippo
fippo force-pushed the sped-remainder branch 2 times, most recently from 1ea6639 to c7f08eb Compare May 4, 2026 12:55
@fippo
fippo force-pushed the sped-remainder branch from 2f3305b to 95838f7 Compare May 15, 2026 17:49
@JoTurk
JoTurk self-requested a review June 2, 2026 12:59
@cnderrauber

Copy link
Copy Markdown
Member

Thank you for this great feature! I have integrated this into the livekit SFU and made some connectivity checks, found 2 regression cases:

  1. The ice-lite agent doesn't ping the remote actively, so the piggyback packets will be sent in response to the remote peer's connectivity check, cost longer than the non-sped case. Fixing this could conflict with spec, maybe we can mark it as an exception.
  2. When a remote peer sends a done signal after the agent is connected while still having inflight piggyback packets, it will mark the state to complete and doesn't have a chance to flush the packets. And also the Piggyback() will swallow all dtls packets but return true in this state (complete after connected).

Both are fixed in our fork livekit@85afccd, livekit@5eec925. Will merge them back to upstream once this PR is merged if they make sense.

Comment thread piggyback.go
@fippo
fippo force-pushed the sped-remainder branch 4 times, most recently from ec416a1 to 3d4ec7a Compare August 10, 2026 18:47
@fippo

fippo commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

I gave up and resynced with C++ logic ...

@JoTurk
JoTurk requested review from JoTurk and a balanced review from Copilot August 15, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Implements SPED by embedding DTLS handshake packets and acknowledgements in ICE STUN exchanges.

Changes:

  • Adds the SPED piggybacking state machine and public DTLS hooks.
  • Integrates SPED attributes into connectivity checks and responses.
  • Adds state-machine, fallback, and integration tests.

Reviewed changes

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

File Description
agent.go Initializes SPED and flushes fallback packets.
selection.go Processes SPED attributes during ICE selection.
piggyback.go Implements SPED packet and ACK handling.
piggyback_test.go Tests SPED negotiation and fallback behavior.
Suppressed comments (2)

piggyback.go:188

  • This transition can leave an unacknowledged local flight in packets, but Complete prevents it from being embedded again. If ICE is already connected, flushOnConnected has already run, so the flight is stranded and the peer's DTLS handshake can stall. Drain outstanding packets as plain DTLS when completing an already-connected controller, and make flushOnConnected also drain Complete for the opposite event ordering.
	if packet == nil && acks == nil && a.piggyback.state == PiggybackingStatePending {
		a.log.Info("Done with the SPED handshake")
		a.piggyback.acks = nil
		a.piggyback.state = PiggybackingStateComplete

piggyback.go:216

  • A non-nil ACK attribute is treated as final even when it is empty or acknowledges only older packets. After ACK deletion, verify that the outgoing queue is empty before entering Complete; otherwise a reordered connectivity check can permanently stop retransmission of the still-unacknowledged flight.
	// The response to the final flight will not contain DTLS data but an ack.
	if packet == nil && acks != nil && a.piggyback.state == PiggybackingStatePending {
		a.log.Info("Done with the SPED handshake")
		a.piggyback.acks = nil
		a.piggyback.state = PiggybackingStateComplete

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agent.go Outdated
Comment on lines +801 to +803
for _, p := range packets {
_, _ = pair.Write(p.data)
}
Comment thread piggyback.go
Comment thread piggyback.go Outdated
Comment thread piggyback.go Outdated

@JoTurk JoTurk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sorry for all the back and forth :(

@fippo

fippo commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

still reasonable given the amount of cross-repo work required. Now it also seems we need to plumb ice-options if this is to be negotiated with ice-options ...

@JoTurk JoTurk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just two nits, looks great, thank you and sorry again

Comment thread piggyback.go Outdated
Comment thread piggyback.go Outdated
// flight has to keep it around until it gets acknowledged; that is the server
// in DTLS 1.2 and the client in DTLS 1.3. The other party has nothing more to
// send and drops its outgoing packets.
func (a *Agent) SetDtlsHandshakeComplete(isClient, isDtls13 bool) {

@JoTurk JoTurk Aug 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe instead of isdtls13 we should just have uint16 value of the version as a go "enum"

type dtlsVersion uint16

const (
	DTLS12 dtlsVersion = 0xfefd
	DTLS13 dtlsVersion = 0xfefc
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this becomes an export hassle and I do not think we will have DTLS 1.4 anytime soon (and nobody likes 1.0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree but isdtls13 just doesn't feel "right" for a public api, but I don't have a hard feeling about this :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah but ideally DTLS defines its version but ice can't import from DTLS and lots of conversion and ... :-|

@JoTurk JoTurk Aug 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We export the versions in dtls/pkg/protocol https://github.com/pion/dtls/blob/main/pkg/protocol/version.go and ice can import them and they are released with v3 so we don't have to wait for v4 :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes but that creates a rather awkward dep from ice on dtls...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ice already imports dtls directly xd https://github.com/pion/ice/blob/main/go.mod

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

whyyyyyyyyy...

(will update, awkwardness is not on me!)

@JoTurk

JoTurk commented Aug 16, 2026

Copy link
Copy Markdown
Member

Also I wonder if we should move the sped methods into a new optional object that the user can pass to options With sped(controller) so we don't have methods on agents but this isn't a big deal because ice is a due for an api rework and refactor and we can do all of this in the next major release. And release this for now.

@zshang-oai

Copy link
Copy Markdown
Contributor

From my Codex and internal security tool, just for your reference @fippo

Security concern: reportPiggybackingFromMessage extracts DTLS-IN-STUN and DTLS-IN-STUN-ACK without checking whether those attributes appear before MESSAGE-INTEGRITY.
Since STUN integrity only authenticates attributes preceding MESSAGE-INTEGRITY, an attacker could append SPED attributes after a valid integrity field without invalidating the existing HMAC. Those unauthenticated attributes would still reach ReportPiggybacking, potentially injecting DTLS data or modifying ACK state.
Could we either enable strict STUN decoding or explicitly reject SPED attributes appearing after MESSAGE-INTEGRITY? Regression tests should cover both appended DTLS and ACK attributes.

@fippo

fippo commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

pion/stun#273 should be on by default?

@JoTurk

JoTurk commented Aug 17, 2026

Copy link
Copy Markdown
Member

@fippo we're going to tag a new major release for STUN soon because of pion/stun#284 maybe we can switch strict mode to be on by default?

@fippo

fippo commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@JoTurk ah... we didn't ship that. Yes, lets do it as part of a breaking change.

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.

5 participants