66package ice
77
88import (
9+ "errors"
910 "hash/crc32"
1011 "net"
1112 "slices"
@@ -19,6 +20,15 @@ type packetWithCrc struct {
1920 crc uint32
2021}
2122
23+ // dtlsRecordHeaderLen is the length of the DTLS record header.
24+ const dtlsRecordHeaderLen = 13
25+
26+ // isDtlsPacket determines whether the payload is a DTLS record. Only those are
27+ // embedded in or extracted from STUN messages.
28+ func isDtlsPacket (payload []byte ) bool {
29+ return len (payload ) >= dtlsRecordHeaderLen && payload [0 ] > 19 && payload [0 ] < 64
30+ }
31+
2232type piggybackingState int
2333
2434const (
@@ -39,6 +49,8 @@ type piggybackingController struct {
3949 dtlsCallback func (packet []byte , rAddr net.Addr )
4050 newFlight bool
4151 connected bool
52+ // DTLS 1.2 only. For DTLS 1.3 the role this matters for is reversed.
53+ isDtlsClient bool
4254}
4355
4456// init sets the controller to its initial off state. SetDtlsCallback flips it
@@ -77,6 +89,25 @@ func (a *Agent) SetDtlsCallback(cb func(packet []byte, rAddr net.Addr)) {
7789 }
7890}
7991
92+ // SetDtlsRole informs the controller whether we are the DTLS client. It needs
93+ // to be called before the local DTLS handshake completes since it determines
94+ // whether the last flight has to be kept around for retransmission.
95+ func (a * Agent ) SetDtlsRole (isClient bool ) {
96+ a .piggyback .mu .Lock ()
97+ defer a .piggyback .mu .Unlock ()
98+ a .piggyback .isDtlsClient = isClient
99+ }
100+
101+ // SetDtlsFailed disables piggybacking after the DTLS handshake failed.
102+ func (a * Agent ) SetDtlsFailed () {
103+ a .piggyback .mu .Lock ()
104+ defer a .piggyback .mu .Unlock ()
105+ if a .piggyback .state != PiggybackingStateComplete && a .piggyback .state != PiggybackingStateOff {
106+ a .log .Info ("DTLS failed during negotiation, disabling piggybacking" )
107+ }
108+ a .piggyback .state = PiggybackingStateOff
109+ }
110+
80111// Piggyback stores a packet to be picked in a round-robin fashion.
81112// Returns `true` if packet is to be consumed.
82113// A nil packet signals that the local DTLS handshake completed.
@@ -88,6 +119,10 @@ func (a *Agent) Piggyback(packet []byte, end bool) bool {
88119 }
89120
90121 if packet != nil {
122+ // Anything that is not DTLS gets sent as-is.
123+ if ! isDtlsPacket (packet ) {
124+ return false
125+ }
91126 // If we receive a packet after the end of a flight we need
92127 // to clear the outgoing list.
93128 if a .piggyback .newFlight {
@@ -97,7 +132,17 @@ func (a *Agent) Piggyback(packet []byte, end bool) bool {
97132 a .piggyback .newFlight = end
98133 crc := crc32 .ChecksumIEEE (packet )
99134 a .piggyback .packets = append (a .piggyback .packets , packetWithCrc {packet , crc })
100- } else if a .piggyback .state != PiggybackingStateOff {
135+ } else if a .piggyback .state != PiggybackingStateOff && a .piggyback .state != PiggybackingStateComplete {
136+ // As DTLS 1.2 client we have nothing more to send at this point but
137+ // will continue to send ACK attributes until receiving the last flight
138+ // from the server. As DTLS 1.2 server we need to keep the last flight
139+ // around until we receive the post-handshake acknowledgment.
140+ // For DTLS 1.3 this is reversed since the handshake is one round trip
141+ // shorter.
142+ if a .piggyback .isDtlsClient {
143+ a .piggyback .packets = []packetWithCrc {}
144+ a .piggyback .packetsIndex = 0
145+ }
101146 a .piggyback .state = PiggybackingStatePending
102147 }
103148 // If we are connected we could send DTLS plain.
@@ -113,17 +158,18 @@ func (a *Agent) GetPiggybackDataAndAcks() ([]byte, []uint32) {
113158 return nil , nil
114159 }
115160 if len (a .piggyback .packets ) == 0 {
116- return nil , a .piggyback .acks
161+ return nil , slices . Clone ( a .piggyback .acks )
117162 }
118163
119164 packet := a .piggyback .packets [a .piggyback .packetsIndex ]
120165 a .piggyback .packetsIndex = (a .piggyback .packetsIndex + 1 ) % len (a .piggyback .packets )
121166
122- // Return a copy to prevent external modification of the internal buffer
167+ // Return copies to prevent external modification of the internal buffers
168+ // which are written to while the message is built.
123169 result := make ([]byte , len (packet .data ))
124170 copy (result , packet .data )
125171
126- return result , a .piggyback .acks
172+ return result , slices . Clone ( a .piggyback .acks )
127173}
128174
129175func (a * Agent ) ReportPiggybacking (packet []byte , acks []uint32 , rAddr net.Addr ) { //nolint:cyclop
@@ -143,8 +189,10 @@ func (a *Agent) ReportPiggybacking(packet []byte, acks []uint32, rAddr net.Addr)
143189
144190 return
145191 }
146- if packet == nil && acks == nil && a .piggyback .acks != nil {
147- a .log .Infof ("Done with the SPED handshake" , a .piggyback .state )
192+ // In the pending state the peer may have stopped sending acks when it
193+ // moved to the complete state. Move to the same state.
194+ if packet == nil && acks == nil && a .piggyback .state == PiggybackingStatePending {
195+ a .log .Info ("Done with the SPED handshake" )
148196 a .piggyback .acks = nil
149197 a .piggyback .state = PiggybackingStateComplete
150198 a .piggyback .mu .Unlock ()
@@ -170,9 +218,29 @@ func (a *Agent) ReportPiggybacking(packet []byte, acks []uint32, rAddr net.Addr)
170218 a .piggyback .packetsIndex = 0
171219 }
172220 }
173- if len (packet ) == 0 {
221+ // The response to the final flight of the handshake will not contain the
222+ // DTLS data but will contain an ack. Must not happen on the initial server
223+ // to client packet which has no DTLS data yet.
224+ if packet == nil && acks != nil && a .piggyback .state == PiggybackingStatePending {
225+ a .log .Info ("Done with the SPED handshake" )
226+ a .piggyback .acks = nil
227+ a .piggyback .state = PiggybackingStateComplete
228+ a .piggyback .mu .Unlock ()
229+
230+ return
231+ }
232+ // Keep sending the (possibly empty) ack attribute as long as we are active,
233+ // its absence signals that the handshake is done.
234+ if a .piggyback .acks == nil {
174235 a .piggyback .acks = []uint32 {}
175236 }
237+ // Drop non-DTLS data.
238+ if len (packet ) > 0 && ! isDtlsPacket (packet ) {
239+ a .log .Warn ("Dropping non-DTLS data" )
240+ a .piggyback .mu .Unlock ()
241+
242+ return
243+ }
176244
177245 var dtlsCallback func (packet []byte , rAddr net.Addr )
178246 // Handle the incoming packet. Calculate and store the crc32 of the packet
@@ -214,10 +282,21 @@ func (a *Agent) appendPiggybackAttributes(attrs []stun.Setter) []stun.Setter {
214282// reportPiggybackingFromMessage extracts the DTLS-in-STUN payload and ACK list
215283// from a STUN message and forwards them to the controller.
216284func (a * Agent ) reportPiggybackingFromMessage (message * stun.Message , remote Candidate ) {
285+ // A malformed attribute must not be treated like an absent one which
286+ // signals that the peer does not support piggybacking or is done with the
287+ // handshake. Drop the message instead.
217288 var dtls DtlsInStunAttribute
218- _ = dtls .GetFrom (message )
289+ if err := dtls .GetFrom (message ); err != nil && ! errors .Is (err , stun .ErrAttributeNotFound ) {
290+ a .log .Warnf ("Discarding malformed DTLS-in-STUN attribute: %v" , err )
291+
292+ return
293+ }
219294 var ack DtlsInStunAckAttribute
220- _ = ack .GetFrom (message )
295+ if err := ack .GetFrom (message ); err != nil && ! errors .Is (err , stun .ErrAttributeNotFound ) {
296+ a .log .Warnf ("Discarding malformed DTLS-in-STUN ack attribute: %v" , err )
297+
298+ return
299+ }
221300 a .ReportPiggybacking (dtls , ack , remote .addr ())
222301}
223302
0 commit comments