@@ -8,9 +8,11 @@ package ice
88import (
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+
15541757func (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