-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathconn_send.go
407 lines (380 loc) · 14.2 KB
/
conn_send.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package quic
import (
"crypto/tls"
"errors"
"time"
)
// maybeSend sends datagrams, if possible.
//
// If sending is blocked by pacing, it returns the next time
// a datagram may be sent.
//
// If sending is blocked indefinitely, it returns the zero Time.
func (c *Conn) maybeSend(now time.Time) (next time.Time) {
// Assumption: The congestion window is not underutilized.
// If congestion control, pacing, and anti-amplification all permit sending,
// but we have no packet to send, then we will declare the window underutilized.
underutilized := false
defer func() {
c.loss.cc.setUnderutilized(c.log, underutilized)
}()
// Send one datagram on each iteration of this loop,
// until we hit a limit or run out of data to send.
//
// For each number space where we have write keys,
// attempt to construct a packet in that space.
// If the packet contains no frames (we have no data in need of sending),
// abandon the packet.
//
// Speculatively constructing packets means we don't need
// separate code paths for "do we have data to send?" and
// "send the data" that need to be kept in sync.
for {
limit, next := c.loss.sendLimit(now)
if limit == ccBlocked {
// If anti-amplification blocks sending, then no packet can be sent.
return next
}
if !c.sendOK(now) {
return time.Time{}
}
// We may still send ACKs, even if congestion control or pacing limit sending.
// Prepare to write a datagram of at most maxSendSize bytes.
c.w.reset(c.loss.maxSendSize())
dstConnID, ok := c.connIDState.dstConnID()
if !ok {
// It is currently not possible for us to end up without a connection ID,
// but handle the case anyway.
return time.Time{}
}
// Initial packet.
pad := false
var sentInitial *sentPacket
if c.keysInitial.canWrite() {
pnumMaxAcked := c.loss.spaces[initialSpace].maxAcked
pnum := c.loss.nextNumber(initialSpace)
p := longPacket{
ptype: packetTypeInitial,
version: quicVersion1,
num: pnum,
dstConnID: dstConnID,
srcConnID: c.connIDState.srcConnID(),
extra: c.retryToken,
}
c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p)
c.appendFrames(now, initialSpace, pnum, limit)
if logPackets {
logSentPacket(c, packetTypeInitial, pnum, p.srcConnID, p.dstConnID, c.w.payload())
}
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
c.logPacketSent(packetTypeInitial, pnum, p.srcConnID, p.dstConnID, c.w.packetLen(), c.w.payload())
}
sentInitial = c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysInitial.w, p)
if sentInitial != nil {
// Client initial packets and ack-eliciting server initial packaets
// need to be sent in a datagram padded to at least 1200 bytes.
// We can't add the padding yet, however, since we may want to
// coalesce additional packets with this one.
if c.side == clientSide || sentInitial.ackEliciting {
pad = true
}
}
}
// Handshake packet.
if c.keysHandshake.canWrite() {
pnumMaxAcked := c.loss.spaces[handshakeSpace].maxAcked
pnum := c.loss.nextNumber(handshakeSpace)
p := longPacket{
ptype: packetTypeHandshake,
version: quicVersion1,
num: pnum,
dstConnID: dstConnID,
srcConnID: c.connIDState.srcConnID(),
}
c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p)
c.appendFrames(now, handshakeSpace, pnum, limit)
if logPackets {
logSentPacket(c, packetTypeHandshake, pnum, p.srcConnID, p.dstConnID, c.w.payload())
}
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
c.logPacketSent(packetTypeHandshake, pnum, p.srcConnID, p.dstConnID, c.w.packetLen(), c.w.payload())
}
if sent := c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysHandshake.w, p); sent != nil {
c.packetSent(now, handshakeSpace, sent)
if c.side == clientSide {
// "[...] a client MUST discard Initial keys when it first
// sends a Handshake packet [...]"
// https://www.rfc-editor.org/rfc/rfc9001.html#section-4.9.1-2
c.discardKeys(now, initialSpace)
}
}
}
// 1-RTT packet.
if c.keysAppData.canWrite() {
pnumMaxAcked := c.loss.spaces[appDataSpace].maxAcked
pnum := c.loss.nextNumber(appDataSpace)
c.w.start1RTTPacket(pnum, pnumMaxAcked, dstConnID)
c.appendFrames(now, appDataSpace, pnum, limit)
if pad && len(c.w.payload()) > 0 {
// 1-RTT packets have no length field and extend to the end
// of the datagram, so if we're sending a datagram that needs
// padding we need to add it inside the 1-RTT packet.
c.w.appendPaddingTo(paddedInitialDatagramSize)
pad = false
}
if logPackets {
logSentPacket(c, packetType1RTT, pnum, nil, dstConnID, c.w.payload())
}
if c.logEnabled(QLogLevelPacket) && len(c.w.payload()) > 0 {
c.logPacketSent(packetType1RTT, pnum, nil, dstConnID, c.w.packetLen(), c.w.payload())
}
if sent := c.w.finish1RTTPacket(pnum, pnumMaxAcked, dstConnID, &c.keysAppData); sent != nil {
c.packetSent(now, appDataSpace, sent)
if c.skip.shouldSkip(pnum + 1) {
c.loss.skipNumber(now, appDataSpace)
c.skip.updateNumberSkip(c)
}
}
}
buf := c.w.datagram()
if len(buf) == 0 {
if limit == ccOK {
// We have nothing to send, and congestion control does not
// block sending. The congestion window is underutilized.
underutilized = true
}
return next
}
if sentInitial != nil {
if pad {
// Pad out the datagram with zeros, coalescing the Initial
// packet with invalid packets that will be ignored by the peer.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1-1
for len(buf) < paddedInitialDatagramSize {
buf = append(buf, 0)
// Technically this padding isn't in any packet, but
// account it to the Initial packet in this datagram
// for purposes of flow control and loss recovery.
sentInitial.size++
sentInitial.inFlight = true
}
}
// If we're a client and this Initial packet is coalesced
// with a Handshake packet, then we've discarded Initial keys
// since constructing the packet and shouldn't record it as in-flight.
if c.keysInitial.canWrite() {
c.packetSent(now, initialSpace, sentInitial)
}
}
c.endpoint.sendDatagram(datagram{
b: buf,
peerAddr: c.peerAddr,
})
}
}
func (c *Conn) packetSent(now time.Time, space numberSpace, sent *sentPacket) {
c.idleHandlePacketSent(now, sent)
c.loss.packetSent(now, c.log, space, sent)
}
func (c *Conn) appendFrames(now time.Time, space numberSpace, pnum packetNumber, limit ccLimit) {
if c.lifetime.localErr != nil {
c.appendConnectionCloseFrame(now, space, c.lifetime.localErr)
return
}
shouldSendAck := c.acks[space].shouldSendAck(now)
if limit != ccOK {
// ACKs are not limited by congestion control.
if shouldSendAck && c.appendAckFrame(now, space) {
c.acks[space].sentAck()
}
return
}
// We want to send an ACK frame if the ack controller wants to send a frame now,
// OR if we are sending a packet anyway and have ack-eliciting packets which we
// have not yet acked.
//
// We speculatively add ACK frames here, to put them at the front of the packet
// to avoid truncation.
//
// After adding all frames, if we don't need to send an ACK frame and have not
// added any other frames, we abandon the packet.
if c.appendAckFrame(now, space) {
defer func() {
// All frames other than ACK and PADDING are ack-eliciting,
// so if the packet is ack-eliciting we've added additional
// frames to it.
if !shouldSendAck && !c.w.sent.ackEliciting {
// There's nothing in this packet but ACK frames, and
// we don't want to send an ACK-only packet at this time.
// Abandoning the packet means we wrote an ACK frame for
// nothing, but constructing the frame is cheap.
c.w.abandonPacket()
return
}
// Either we are willing to send an ACK-only packet,
// or we've added additional frames.
c.acks[space].sentAck()
if !c.w.sent.ackEliciting && c.shouldMakePacketAckEliciting() {
c.w.appendPingFrame()
}
}()
}
if limit != ccOK {
return
}
pto := c.loss.ptoExpired
// TODO: Add all the other frames we can send.
// CRYPTO
c.crypto[space].dataToSend(pto, func(off, size int64) int64 {
b, _ := c.w.appendCryptoFrame(off, int(size))
c.crypto[space].sendData(off, b)
return int64(len(b))
})
// Test-only PING frames.
if space == c.testSendPingSpace && c.testSendPing.shouldSendPTO(pto) {
if !c.w.appendPingFrame() {
return
}
c.testSendPing.setSent(pnum)
}
if space == appDataSpace {
// HANDSHAKE_DONE
if c.handshakeConfirmed.shouldSendPTO(pto) {
if !c.w.appendHandshakeDoneFrame() {
return
}
c.handshakeConfirmed.setSent(pnum)
}
// NEW_CONNECTION_ID, RETIRE_CONNECTION_ID
if !c.connIDState.appendFrames(c, pnum, pto) {
return
}
// PATH_RESPONSE
if pad, ok := c.appendPathFrames(); !ok {
return
} else if pad {
defer c.w.appendPaddingTo(smallestMaxDatagramSize)
}
// All stream-related frames. This should come last in the packet,
// so large amounts of STREAM data don't crowd out other frames
// we may need to send.
if !c.appendStreamFrames(&c.w, pnum, pto) {
return
}
if !c.appendKeepAlive(now) {
return
}
}
// If this is a PTO probe and we haven't added an ack-eliciting frame yet,
// add a PING to make this an ack-eliciting probe.
//
// Technically, there are separate PTO timers for each number space.
// When a PTO timer expires, we MUST send an ack-eliciting packet in the
// timer's space. We SHOULD send ack-eliciting packets in every other space
// with in-flight data. (RFC 9002, section 6.2.4)
//
// What we actually do is send a single datagram containing an ack-eliciting packet
// for every space for which we have keys.
//
// We fill the PTO probe packets with new or unacknowledged data. For example,
// a PTO probe sent for the Initial space will generally retransmit previously
// sent but unacknowledged CRYPTO data.
//
// When sending a PTO probe datagram containing multiple packets, it is
// possible that an earlier packet will fill up the datagram, leaving no
// space for the remaining probe packet(s). This is not a problem in practice.
//
// A client discards Initial keys when it first sends a Handshake packet
// (RFC 9001 Section 4.9.1). Handshake keys are discarded when the handshake
// is confirmed (RFC 9001 Section 4.9.2). The PTO timer is not set for the
// Application Data packet number space until the handshake is confirmed
// (RFC 9002 Section 6.2.1). Therefore, the only times a PTO probe can fire
// while data for multiple spaces is in flight are:
//
// - a server's Initial or Handshake timers can fire while Initial and Handshake
// data is in flight; and
//
// - a client's Handshake timer can fire while Handshake and Application Data
// data is in flight.
//
// It is theoretically possible for a server's Initial CRYPTO data to overflow
// the maximum datagram size, but unlikely in practice; this space contains
// only the ServerHello TLS message, which is small. It's also unlikely that
// the Handshake PTO probe will fire while Initial data is in flight (this
// requires not just that the Initial CRYPTO data completely fill a datagram,
// but a quite specific arrangement of lost and retransmitted packets.)
// We don't bother worrying about this case here, since the worst case is
// that we send a PTO probe for the in-flight Initial data and drop the
// Handshake probe.
//
// If a client's Handshake PTO timer fires while Application Data data is in
// flight, it is possible that the resent Handshake CRYPTO data will crowd
// out the probe for the Application Data space. However, since this probe is
// optional (recall that the Application Data PTO timer is never set until
// after Handshake keys have been discarded), dropping it is acceptable.
if pto && !c.w.sent.ackEliciting {
c.w.appendPingFrame()
}
}
// shouldMakePacketAckEliciting is called when sending a packet containing nothing but an ACK frame.
// It reports whether we should add a PING frame to the packet to make it ack-eliciting.
func (c *Conn) shouldMakePacketAckEliciting() bool {
if c.keysAppData.needAckEliciting() {
// The peer has initiated a key update.
// We haven't sent them any packets yet in the new phase.
// Make this an ack-eliciting packet.
// Their ack of this packet will complete the key update.
return true
}
if c.loss.consecutiveNonAckElicitingPackets >= 19 {
// We've sent a run of non-ack-eliciting packets.
// Add in an ack-eliciting one every once in a while so the peer
// lets us know which ones have arrived.
//
// Google QUICHE injects a PING after sending 19 packets. We do the same.
//
// https://www.rfc-editor.org/rfc/rfc9000#section-13.2.4-2
return true
}
// TODO: Consider making every packet sent when in PTO ack-eliciting to speed up recovery.
return false
}
func (c *Conn) appendAckFrame(now time.Time, space numberSpace) bool {
seen, delay := c.acks[space].acksToSend(now)
if len(seen) == 0 {
return false
}
d := unscaledAckDelayFromDuration(delay, ackDelayExponent)
return c.w.appendAckFrame(seen, d)
}
func (c *Conn) appendConnectionCloseFrame(now time.Time, space numberSpace, err error) {
c.sentConnectionClose(now)
switch e := err.(type) {
case localTransportError:
c.w.appendConnectionCloseTransportFrame(e.code, 0, e.reason)
case *ApplicationError:
if space != appDataSpace {
// "CONNECTION_CLOSE frames signaling application errors (type 0x1d)
// MUST only appear in the application data packet number space."
// https://www.rfc-editor.org/rfc/rfc9000#section-12.5-2.2
c.w.appendConnectionCloseTransportFrame(errApplicationError, 0, "")
} else {
c.w.appendConnectionCloseApplicationFrame(e.Code, e.Reason)
}
default:
// TLS alerts are sent using error codes [0x0100,0x01ff).
// https://www.rfc-editor.org/rfc/rfc9000#section-20.1-2.36.1
var alert tls.AlertError
switch {
case errors.As(err, &alert):
// tls.AlertError is a uint8, so this can't exceed 0x01ff.
code := errTLSBase + transportError(alert)
c.w.appendConnectionCloseTransportFrame(code, 0, "")
default:
c.w.appendConnectionCloseTransportFrame(errInternal, 0, "")
}
}
}