Skip to content

Commit ec416a1

Browse files
committed
Sync with libwebrtc M144
1 parent eed4ead commit ec416a1

3 files changed

Lines changed: 105 additions & 17 deletions

File tree

agent.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,9 @@ func (a *Agent) handleInboundRequest(
18661866
remoteTieBreaker := &AttrControl{}
18671867
if err := remoteTieBreaker.GetFrom(msg); err == nil && remoteTieBreaker.Role == a.role() {
18681868
a.handleRoleConflict(msg, local, remoteCandidate, remoteTieBreaker)
1869+
// The selector does not get to see this message, report any piggybacked
1870+
// DTLS to avoid the peer having to retransmit it.
1871+
a.reportPiggybackingFromMessage(msg, remoteCandidate)
18691872

18701873
return nil, false
18711874
}

piggyback.go

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package ice
77

88
import (
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+
2232
type piggybackingState int
2333

2434
const (
@@ -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

129175
func (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.
216284
func (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

piggyback_test.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import (
1414
"github.com/stretchr/testify/require"
1515
)
1616

17+
// fakeDtlsPacket prefixes the payload with a DTLS 1.2 handshake record header
18+
// so it is recognized as a DTLS packet.
19+
func fakeDtlsPacket(payload string) []byte {
20+
return append([]byte{22, 0xfe, 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte(payload)...)
21+
}
22+
1723
func TestSped(t *testing.T) {
1824
defer test.CheckRoutines(t)()
1925

@@ -25,12 +31,12 @@ func TestSped(t *testing.T) {
2531
require.NoError(t, err)
2632
require.NoError(t, aAgent.OnConnectionStateChange(aNotifier))
2733

28-
var toA string
29-
fromA := "Hello from A"
34+
var toA []byte
35+
fromA := fakeDtlsPacket("Hello from A")
3036
aAgent.SetDtlsCallback(func(packet []byte, rAddr net.Addr) {
31-
toA = string(packet)
37+
toA = packet
3238
})
33-
require.True(t, aAgent.Piggyback([]byte(fromA), true))
39+
require.True(t, aAgent.Piggyback(fromA, true))
3440

3541
bNotifier, bConnected := onConnected()
3642
bAgent, err := NewAgent(&AgentConfig{
@@ -39,12 +45,12 @@ func TestSped(t *testing.T) {
3945
require.NoError(t, err)
4046
require.NoError(t, bAgent.OnConnectionStateChange(bNotifier))
4147

42-
var toB string
43-
fromB := "Hello from B"
48+
var toB []byte
49+
fromB := fakeDtlsPacket("Hello from B")
4450
bAgent.SetDtlsCallback(func(packet []byte, rAddr net.Addr) {
45-
toB = string(packet)
51+
toB = packet
4652
})
47-
require.True(t, bAgent.Piggyback([]byte(fromB), true))
53+
require.True(t, bAgent.Piggyback(fromB, true))
4854

4955
gatherAndExchangeCandidates(t, aAgent, bAgent)
5056
go func() {

0 commit comments

Comments
 (0)