Skip to content

Commit 0e2f82c

Browse files
committed
Implement SPED (STUN Protocol for Embedding DTLS)
part of pion/webrtc#3335
1 parent 187b3ea commit 0e2f82c

2 files changed

Lines changed: 280 additions & 15 deletions

File tree

agent.go

Lines changed: 214 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ package ice
88
import (
99
"context"
1010
"fmt"
11+
"hash/crc32"
1112
"math"
1213
"net"
1314
"net/netip"
15+
"slices"
1416
"strings"
1517
"sync"
1618
"sync/atomic"
@@ -35,6 +37,33 @@ type bindingRequest struct {
3537
destination net.Addr
3638
isUseCandidate bool
3739
nominationValue *uint32 // Tracks nomination value for renomination requests
40+
// TODO: having a callback or the original request would be useful for SPED
41+
// so that the response can do the "implicit" ack of the packet in the request
42+
}
43+
44+
type packetWithCrc struct {
45+
data []byte
46+
crc uint32
47+
}
48+
49+
type piggybackingState int
50+
51+
const (
52+
PiggybackingStateTentative = iota
53+
PiggybackingStateConfirmed
54+
PiggybackingStatePending
55+
PiggybackingStateComplete
56+
PiggybackingStateOff
57+
)
58+
59+
// DTLS-in-STUN controller.
60+
type piggybackingController struct {
61+
mu sync.Mutex
62+
state piggybackingState
63+
packets []packetWithCrc
64+
packetsIndex int
65+
acks []uint32
66+
dtlsCallback func(packet []byte, rAddr net.Addr)
3867
}
3968

4069
// Agent represents the ICE agent.
@@ -171,6 +200,8 @@ type Agent struct {
171200
lastRenominationTime time.Time
172201

173202
turnClientFactory func(*turn.ClientConfig) (turnClient, error)
203+
204+
piggyback piggybackingController
174205
}
175206

176207
// NewAgent creates a new Agent.
@@ -213,6 +244,12 @@ func newAgentFromConfig(config *AgentConfig, opts ...AgentOption) (*Agent, error
213244
}
214245
agent.addressRewriteRules = rules
215246
}
247+
// TODO: wire up SPED to a config and and initialize here.
248+
// Setting the state to off here disableѕ SPED
249+
agent.piggyback.mu.Lock()
250+
agent.piggyback.acks = []uint32{}
251+
agent.piggyback.state = PiggybackingStateTentative
252+
agent.piggyback.mu.Unlock()
216253

217254
return newAgentWithConfig(agent, opts...)
218255
}
@@ -670,6 +707,22 @@ func (a *Agent) updateConnectionState(newState ConnectionState) {
670707
a.deleteAllCandidates()
671708
}
672709

710+
var packetsToFlush []packetWithCrc
711+
a.piggyback.mu.Lock()
712+
if newState == ConnectionStateConnected && a.piggyback.state == PiggybackingStateOff {
713+
// Piggybacking was discovered as not supported.
714+
// Flush any pending DTLS packets.
715+
packetsToFlush = a.piggyback.packets
716+
a.piggyback.packets = []packetWithCrc{}
717+
}
718+
a.piggyback.mu.Unlock()
719+
720+
if pair := a.getSelectedPair(); pair != nil && len(packetsToFlush) > 0 {
721+
for _, p := range packetsToFlush {
722+
pair.Write(p.data)
723+
}
724+
}
725+
673726
a.log.Infof("Setting new connection state: %s", newState)
674727
a.connectionState = newState
675728
a.connectionStateNotifier.EnqueueConnectionState(newState)
@@ -1298,14 +1351,27 @@ func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) {
12981351
return
12991352
}
13001353

1301-
if out, err := stun.Build(m, stun.BindingSuccess,
1354+
attributes := []stun.Setter{
1355+
m,
1356+
stun.BindingSuccess,
13021357
&stun.XORMappedAddress{
13031358
IP: ip.AsSlice(),
13041359
Port: port,
13051360
},
1361+
}
1362+
if packet, acks := a.GetPiggybackDataAndAcks(); acks != nil {
1363+
if acks != nil {
1364+
attributes = append(attributes, DtlsInStunAckAttribute(acks))
1365+
}
1366+
if packet != nil {
1367+
attributes = append(attributes, DtlsInStunAttribute(packet))
1368+
}
1369+
}
1370+
attributes = append(attributes,
13061371
stun.NewShortTermIntegrity(a.localPwd),
1307-
stun.Fingerprint,
1308-
); err != nil {
1372+
stun.Fingerprint)
1373+
1374+
if out, err := stun.Build(attributes...); err != nil {
13091375
a.log.Warnf("Failed to handle inbound ICE from: %s to: %s error: %s", local, remote, err)
13101376
} else {
13111377
if pair := a.findPair(local, remote); pair != nil {
@@ -1551,6 +1617,143 @@ func (a *Agent) getSelectedPair() *CandidatePair {
15511617
return nil
15521618
}
15531619

1620+
func (a *Agent) SetDtlsCallback(cb func(packet []byte, rAddr net.Addr)) {
1621+
a.piggyback.mu.Lock()
1622+
defer a.piggyback.mu.Unlock()
1623+
a.piggyback.dtlsCallback = cb
1624+
}
1625+
1626+
// Piggyback stores a packet to be picked in a round-robin fashion.
1627+
// Returns `true` if packet is to be consumed.
1628+
func (a *Agent) Piggyback(packet []byte) bool {
1629+
a.piggyback.mu.Lock()
1630+
defer a.piggyback.mu.Unlock()
1631+
if a.piggyback.state == PiggybackingStateOff {
1632+
// TODO: ѕhould we store the packet for later so we
1633+
// can send it when the connection gets established?
1634+
return a.connectionState != ConnectionStateConnected
1635+
}
1636+
1637+
if packet != nil {
1638+
crc := crc32.ChecksumIEEE(packet)
1639+
a.piggyback.packets = append(a.piggyback.packets, packetWithCrc{packet, crc})
1640+
} else {
1641+
a.piggyback.state = PiggybackingStatePending
1642+
}
1643+
// If we are connected we could send DTLS plain.
1644+
return true // a.connectionState == ConnectionStateConnected
1645+
}
1646+
1647+
// GetPiggybackData returns a packet from the stored list in a round-robin fashion and a list of acks.
1648+
func (a *Agent) GetPiggybackDataAndAcks() ([]byte, []uint32) {
1649+
a.piggyback.mu.Lock()
1650+
defer a.piggyback.mu.Unlock()
1651+
1652+
if a.piggyback.state == PiggybackingStateOff || a.piggyback.state == PiggybackingStateComplete {
1653+
return nil, nil
1654+
}
1655+
if len(a.piggyback.packets) == 0 {
1656+
return nil, a.piggyback.acks
1657+
}
1658+
1659+
packet := a.piggyback.packets[a.piggyback.packetsIndex]
1660+
a.piggyback.packetsIndex = (a.piggyback.packetsIndex + 1) % len(a.piggyback.packets)
1661+
1662+
// Return a copy to prevent external modification of the internal buffer
1663+
result := make([]byte, len(packet.data))
1664+
copy(result, packet.data)
1665+
return result, a.piggyback.acks
1666+
}
1667+
1668+
func (a *Agent) ReportPiggybacking(packet []byte, acks []uint32, rAddr net.Addr) {
1669+
a.piggyback.mu.Lock()
1670+
1671+
if a.piggyback.state == PiggybackingStateComplete || a.piggyback.state == PiggybackingStateOff {
1672+
a.piggyback.mu.Unlock()
1673+
return
1674+
}
1675+
if packet == nil && acks == nil && a.piggyback.state == PiggybackingStateTentative {
1676+
// Any pending packets will be flushed later when the ICE connection gets established.
1677+
a.log.Infof("Piggybacking discovered as not supported, falling back to normal state")
1678+
a.piggyback.dtlsCallback = nil
1679+
a.piggyback.state = PiggybackingStateOff
1680+
a.piggyback.mu.Unlock()
1681+
return
1682+
}
1683+
if packet == nil && acks == nil && a.piggyback.acks != nil {
1684+
a.log.Infof("Done with the SPED handshake", a.piggyback.state)
1685+
// TODO: check that we are in pending state?
1686+
a.piggyback.acks = nil
1687+
a.piggyback.state = PiggybackingStateComplete
1688+
a.piggyback.mu.Unlock()
1689+
return
1690+
}
1691+
if a.piggyback.state == PiggybackingStateTentative {
1692+
a.piggyback.state = PiggybackingStateConfirmed
1693+
}
1694+
// Handle incoming acks.
1695+
if size := len(acks); size > 0 {
1696+
beforeLen := len(a.piggyback.packets)
1697+
a.piggyback.packets = slices.DeleteFunc(a.piggyback.packets, func(p packetWithCrc) bool {
1698+
for _, ackCrc := range acks {
1699+
if p.crc == ackCrc {
1700+
return true // This packet is acknowledged, so remove it.
1701+
}
1702+
}
1703+
return false // This packet is not acknowledged, so keep it.
1704+
})
1705+
removed := beforeLen - len(a.piggyback.packets)
1706+
1707+
// Adjust the index if it's out of bounds after deletion
1708+
// TODO: for fairness one should only adjust if the index was affected?
1709+
if a.piggyback.packetsIndex >= removed {
1710+
a.piggyback.packetsIndex -= removed
1711+
} else {
1712+
a.piggyback.packetsIndex = 0
1713+
}
1714+
}
1715+
if len(packet) == 0 {
1716+
a.piggyback.acks = []uint32{}
1717+
}
1718+
1719+
var dtlsCallback func(packet []byte, rAddr net.Addr)
1720+
// Handle the incoming packet. Calculate and store the crc32 of the packet
1721+
// for acks, then notify the DTLS packet.
1722+
if a.piggyback.dtlsCallback != nil && len(packet) > 0 {
1723+
crc := crc32.ChecksumIEEE(packet)
1724+
if !slices.Contains(a.piggyback.acks, crc) {
1725+
a.piggyback.acks = append(a.piggyback.acks, crc)
1726+
if len(a.piggyback.acks) > 4 {
1727+
a.piggyback.acks = a.piggyback.acks[1:]
1728+
}
1729+
}
1730+
dtlsCallback = a.piggyback.dtlsCallback
1731+
}
1732+
1733+
a.piggyback.mu.Unlock()
1734+
1735+
if dtlsCallback != nil {
1736+
dtlsCallback(packet, rAddr)
1737+
}
1738+
}
1739+
1740+
func (a *Agent) ReportDtlsPacket(packet []byte) {
1741+
a.piggyback.mu.Lock()
1742+
1743+
if a.piggyback.state == PiggybackingStateComplete || a.piggyback.state == PiggybackingStateOff {
1744+
a.piggyback.mu.Unlock()
1745+
return
1746+
}
1747+
crc := crc32.ChecksumIEEE(packet)
1748+
if !slices.Contains(a.piggyback.acks, crc) {
1749+
a.piggyback.acks = append(a.piggyback.acks, crc)
1750+
if len(a.piggyback.acks) > 4 {
1751+
a.piggyback.acks = a.piggyback.acks[1:]
1752+
}
1753+
}
1754+
a.piggyback.mu.Unlock()
1755+
}
1756+
15541757
func (a *Agent) closeMulticastConn() {
15551758
if a.mDNSConn != nil {
15561759
if err := a.mDNSConn.Close(); err != nil {
@@ -1742,6 +1945,14 @@ func (a *Agent) sendNominationRequest(pair *CandidatePair, nominationValue uint3
17421945
a.log.Tracef("Sending renomination request from %s to %s with nomination value %d",
17431946
pair.Local, pair.Remote, nominationValue)
17441947
}
1948+
if packet, acks := a.GetPiggybackDataAndAcks(); acks != nil {
1949+
if acks != nil {
1950+
attributes = append(attributes, DtlsInStunAckAttribute(acks))
1951+
}
1952+
if packet != nil {
1953+
attributes = append(attributes, DtlsInStunAttribute(packet))
1954+
}
1955+
}
17451956

17461957
msg, err := stun.Build(append([]stun.Setter{stun.BindingRequest}, attributes...)...)
17471958
if err != nil {

0 commit comments

Comments
 (0)