Skip to content

Commit c2be992

Browse files
committed
quic: remember which remote connection IDs have been retired
Keep track of which peer-provided connection ID sequence numbers we have sent a RETIRE_CONNECTION_ID for, received an ack, and removed from our set of tracked IDs. Rework how we track retired connection IDs in general: Rather than keeping all state for retired IDs, just keep a set of the sequence numbers of IDs which we're in the process of retiring. Change-Id: I14da8b5295d5fbe8318c8afe556cbd2c8a56d856 Reviewed-on: https://go-review.googlesource.com/c/net/+/635717 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent dfc720d commit c2be992

File tree

3 files changed

+119
-67
lines changed

3 files changed

+119
-67
lines changed

quic/conn_id.go

+62-67
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package quic
99
import (
1010
"bytes"
1111
"crypto/rand"
12+
"slices"
1213
)
1314

1415
// connIDState is a conn's connection IDs.
@@ -25,8 +26,16 @@ type connIDState struct {
2526
remote []remoteConnID
2627

2728
nextLocalSeq int64
28-
retireRemotePriorTo int64 // largest Retire Prior To value sent by the peer
29-
peerActiveConnIDLimit int64 // peer's active_connection_id_limit transport parameter
29+
peerActiveConnIDLimit int64 // peer's active_connection_id_limit
30+
31+
// Handling of retirement of remote connection IDs.
32+
// The rangesets track ID sequence numbers.
33+
// IDs in need of retirement are added to remoteRetiring,
34+
// moved to remoteRetiringSent once we send a RETIRE_CONECTION_ID frame,
35+
// and removed from the set once retirement completes.
36+
retireRemotePriorTo int64 // largest Retire Prior To value sent by the peer
37+
remoteRetiring rangeset[int64] // remote IDs in need of retirement
38+
remoteRetiringSent rangeset[int64] // remote IDs waiting for ack of retirement
3039

3140
originalDstConnID []byte // expected original_destination_connection_id param
3241
retrySrcConnID []byte // expected retry_source_connection_id param
@@ -45,9 +54,6 @@ type connID struct {
4554
// For the transient destination ID in a client's Initial packet, this is -1.
4655
seq int64
4756

48-
// retired is set when the connection ID is retired.
49-
retired bool
50-
5157
// send is set when the connection ID's state needs to be sent to the peer.
5258
//
5359
// For local IDs, this indicates a new ID that should be sent
@@ -144,24 +150,20 @@ func (s *connIDState) srcConnID() []byte {
144150
// dstConnID is the Destination Connection ID to use in a sent packet.
145151
func (s *connIDState) dstConnID() (cid []byte, ok bool) {
146152
for i := range s.remote {
147-
if !s.remote[i].retired {
148-
return s.remote[i].cid, true
149-
}
153+
return s.remote[i].cid, true
150154
}
151155
return nil, false
152156
}
153157

154158
// isValidStatelessResetToken reports whether the given reset token is
155159
// associated with a non-retired connection ID which we have used.
156160
func (s *connIDState) isValidStatelessResetToken(resetToken statelessResetToken) bool {
157-
for i := range s.remote {
158-
// We currently only use the first available remote connection ID,
159-
// so any other reset token is not valid.
160-
if !s.remote[i].retired {
161-
return s.remote[i].resetToken == resetToken
162-
}
161+
if len(s.remote) == 0 {
162+
return false
163163
}
164-
return false
164+
// We currently only use the first available remote connection ID,
165+
// so any other reset token is not valid.
166+
return s.remote[0].resetToken == resetToken
165167
}
166168

167169
// setPeerActiveConnIDLimit sets the active_connection_id_limit
@@ -174,7 +176,7 @@ func (s *connIDState) setPeerActiveConnIDLimit(c *Conn, lim int64) error {
174176
func (s *connIDState) issueLocalIDs(c *Conn) error {
175177
toIssue := min(int(s.peerActiveConnIDLimit), maxPeerActiveConnIDLimit)
176178
for i := range s.local {
177-
if s.local[i].seq != -1 && !s.local[i].retired {
179+
if s.local[i].seq != -1 {
178180
toIssue--
179181
}
180182
}
@@ -271,7 +273,7 @@ func (s *connIDState) handlePacket(c *Conn, ptype packetType, srcConnID []byte)
271273
}
272274
}
273275
case ptype == packetTypeHandshake && c.side == serverSide:
274-
if len(s.local) > 0 && s.local[0].seq == -1 && !s.local[0].retired {
276+
if len(s.local) > 0 && s.local[0].seq == -1 {
275277
// We're a server connection processing the first Handshake packet from
276278
// the client. Discard the transient, client-chosen connection ID used
277279
// for Initial packets; the client will never send it again.
@@ -304,23 +306,29 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
304306
}
305307
}
306308

309+
if seq < s.retireRemotePriorTo {
310+
// This ID was already retired by a previous NEW_CONNECTION_ID frame.
311+
// Nothing to do.
312+
return nil
313+
}
314+
307315
if retire > s.retireRemotePriorTo {
316+
// Add newly-retired connection IDs to the set we need to send
317+
// RETIRE_CONNECTION_ID frames for, and remove them from s.remote.
318+
//
319+
// (This might cause us to send a RETIRE_CONNECTION_ID for an ID we've
320+
// never seen. That's fine.)
321+
s.remoteRetiring.add(s.retireRemotePriorTo, retire)
308322
s.retireRemotePriorTo = retire
323+
s.needSend = true
324+
s.remote = slices.DeleteFunc(s.remote, func(rcid remoteConnID) bool {
325+
return rcid.seq < s.retireRemotePriorTo
326+
})
309327
}
310328

311329
have := false // do we already have this connection ID?
312-
active := 0
313330
for i := range s.remote {
314331
rcid := &s.remote[i]
315-
if !rcid.retired && rcid.seq >= 0 && rcid.seq < s.retireRemotePriorTo {
316-
s.retireRemote(rcid)
317-
c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
318-
conns.retireResetToken(c, rcid.resetToken)
319-
})
320-
}
321-
if !rcid.retired {
322-
active++
323-
}
324332
if rcid.seq == seq {
325333
if !bytes.Equal(rcid.cid, cid) {
326334
return localTransportError{
@@ -329,6 +337,7 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
329337
}
330338
}
331339
have = true // yes, we've seen this sequence number
340+
break
332341
}
333342
}
334343

@@ -345,18 +354,12 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
345354
},
346355
resetToken: resetToken,
347356
})
348-
if seq < s.retireRemotePriorTo {
349-
// This ID was already retired by a previous NEW_CONNECTION_ID frame.
350-
s.retireRemote(&s.remote[len(s.remote)-1])
351-
} else {
352-
active++
353-
c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
354-
conns.addResetToken(c, resetToken)
355-
})
356-
}
357+
c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
358+
conns.addResetToken(c, resetToken)
359+
})
357360
}
358361

359-
if active > activeConnIDLimit {
362+
if len(s.remote) > activeConnIDLimit {
360363
// Retired connection IDs (including newly-retired ones) do not count
361364
// against the limit.
362365
// https://www.rfc-editor.org/rfc/rfc9000.html#section-5.1.1-5
@@ -370,25 +373,18 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
370373
// for which RETIRE_CONNECTION_ID frames have not yet been acknowledged."
371374
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6
372375
//
373-
// Set a limit of four times the active_connection_id_limit for
374-
// the total number of remote connection IDs we keep state for locally.
375-
if len(s.remote) > 4*activeConnIDLimit {
376+
// Set a limit of three times the active_connection_id_limit for
377+
// the total number of remote connection IDs we keep retirement state for.
378+
if s.remoteRetiring.size()+s.remoteRetiringSent.size() > 3*activeConnIDLimit {
376379
return localTransportError{
377380
code: errConnectionIDLimit,
378-
reason: "too many unacknowledged RETIRE_CONNECTION_ID frames",
381+
reason: "too many unacknowledged retired connection ids",
379382
}
380383
}
381384

382385
return nil
383386
}
384387

385-
// retireRemote marks a remote connection ID as retired.
386-
func (s *connIDState) retireRemote(rcid *remoteConnID) {
387-
rcid.retired = true
388-
rcid.send.setUnsent()
389-
s.needSend = true
390-
}
391-
392388
func (s *connIDState) handleRetireConnID(c *Conn, seq int64) error {
393389
if seq >= s.nextLocalSeq {
394390
return localTransportError{
@@ -424,20 +420,11 @@ func (s *connIDState) ackOrLossNewConnectionID(pnum packetNumber, seq int64, fat
424420
}
425421

426422
func (s *connIDState) ackOrLossRetireConnectionID(pnum packetNumber, seq int64, fate packetFate) {
427-
for i := 0; i < len(s.remote); i++ {
428-
if s.remote[i].seq != seq {
429-
continue
430-
}
431-
if fate == packetAcked {
432-
// We have retired this connection ID, and the peer has acked.
433-
// Discard its state completely.
434-
s.remote = append(s.remote[:i], s.remote[i+1:]...)
435-
} else {
436-
// RETIRE_CONNECTION_ID frame was lost, mark for retransmission.
437-
s.needSend = true
438-
s.remote[i].send.ackOrLoss(pnum, fate)
439-
}
440-
return
423+
s.remoteRetiringSent.sub(seq, seq+1)
424+
if fate == packetLost {
425+
// RETIRE_CONNECTION_ID frame was lost, mark for retransmission.
426+
s.remoteRetiring.add(seq, seq+1)
427+
s.needSend = true
441428
}
442429
}
443430

@@ -469,14 +456,22 @@ func (s *connIDState) appendFrames(c *Conn, pnum packetNumber, pto bool) bool {
469456
}
470457
s.local[i].send.setSent(pnum)
471458
}
472-
for i := range s.remote {
473-
if !s.remote[i].send.shouldSendPTO(pto) {
474-
continue
459+
if pto {
460+
for _, r := range s.remoteRetiringSent {
461+
for cid := r.start; cid < r.end; cid++ {
462+
if !c.w.appendRetireConnectionIDFrame(cid) {
463+
return false
464+
}
465+
}
475466
}
476-
if !c.w.appendRetireConnectionIDFrame(s.remote[i].seq) {
467+
}
468+
for s.remoteRetiring.numRanges() > 0 {
469+
cid := s.remoteRetiring.min()
470+
if !c.w.appendRetireConnectionIDFrame(cid) {
477471
return false
478472
}
479-
s.remote[i].send.setSent(pnum)
473+
s.remoteRetiring.sub(cid, cid+1)
474+
s.remoteRetiringSent.add(cid, cid+1)
480475
}
481476
s.needSend = false
482477
return true

quic/conn_id_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,52 @@ func TestConnIDsCleanedUpAfterClose(t *testing.T) {
664664
}
665665
})
666666
}
667+
668+
func TestConnIDRetiredConnIDResent(t *testing.T) {
669+
tc := newTestConn(t, serverSide)
670+
tc.handshake()
671+
tc.ignoreFrame(frameTypeAck)
672+
//tc.ignoreFrame(frameTypeRetireConnectionID)
673+
674+
// Send CID 2, retire 0-1 (negotiated during the handshake).
675+
tc.writeFrames(packetType1RTT,
676+
debugFrameNewConnectionID{
677+
seq: 2,
678+
retirePriorTo: 2,
679+
connID: testPeerConnID(2),
680+
token: testPeerStatelessResetToken(2),
681+
})
682+
tc.wantFrame("retire CID 0", packetType1RTT, debugFrameRetireConnectionID{seq: 0})
683+
tc.wantFrame("retire CID 1", packetType1RTT, debugFrameRetireConnectionID{seq: 1})
684+
685+
// Send CID 3, retire 2.
686+
tc.writeFrames(packetType1RTT,
687+
debugFrameNewConnectionID{
688+
seq: 3,
689+
retirePriorTo: 3,
690+
connID: testPeerConnID(3),
691+
token: testPeerStatelessResetToken(3),
692+
})
693+
tc.wantFrame("retire CID 2", packetType1RTT, debugFrameRetireConnectionID{seq: 2})
694+
695+
// Acknowledge retirement of CIDs 0-2.
696+
// The server should have state for only one CID: 3.
697+
tc.writeAckForAll()
698+
if got, want := len(tc.conn.connIDState.remote), 1; got != want {
699+
t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
700+
}
701+
702+
// Send CID 2 again.
703+
// The server should ignore this, since it's already retired the CID.
704+
tc.ignoreFrames[frameTypeRetireConnectionID] = false
705+
tc.writeFrames(packetType1RTT,
706+
debugFrameNewConnectionID{
707+
seq: 2,
708+
connID: testPeerConnID(2),
709+
token: testPeerStatelessResetToken(2),
710+
})
711+
if got, want := len(tc.conn.connIDState.remote), 1; got != want {
712+
t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
713+
}
714+
tc.wantIdle("server does not re-retire already retired CID 2")
715+
}

quic/rangeset.go

+8
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ func (s rangeset[T]) numRanges() int {
159159
return len(s)
160160
}
161161

162+
// size returns the size of all ranges in the rangeset.
163+
func (s rangeset[T]) size() (total T) {
164+
for _, r := range s {
165+
total += r.size()
166+
}
167+
return total
168+
}
169+
162170
// isrange reports if the rangeset covers exactly the range [start, end).
163171
func (s rangeset[T]) isrange(start, end T) bool {
164172
switch len(s) {

0 commit comments

Comments
 (0)