Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 1 | // Copyright 2023 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | //go:build go1.21 |
| 6 | |
| 7 | package quic |
| 8 | |
| 9 | import ( |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 10 | "bytes" |
| 11 | "crypto/tls" |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 12 | "fmt" |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 13 | "net/netip" |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 14 | "strings" |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 15 | "testing" |
| 16 | ) |
| 17 | |
| 18 | func TestConnIDClientHandshake(t *testing.T) { |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 19 | tc := newTestConn(t, clientSide) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 20 | // On initialization, the client chooses local and remote IDs. |
| 21 | // |
| 22 | // The order in which we allocate the two isn't actually important, |
| 23 | // but test is a lot simpler if we assume. |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 24 | if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) { |
| 25 | t.Errorf("after initialization: srcConnID = %x, want %x", got, want) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 26 | } |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 27 | dstConnID, _ := tc.conn.connIDState.dstConnID() |
| 28 | if got, want := dstConnID, testLocalConnID(-1); !bytes.Equal(got, want) { |
| 29 | t.Errorf("after initialization: dstConnID = %x, want %x", got, want) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 30 | } |
| 31 | |
| 32 | // The server's first Initial packet provides the client with a |
| 33 | // non-transient remote connection ID. |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 34 | tc.writeFrames(packetTypeInitial, |
| 35 | debugFrameCrypto{ |
| 36 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], |
| 37 | }) |
| 38 | dstConnID, _ = tc.conn.connIDState.dstConnID() |
| 39 | if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) { |
| 40 | t.Errorf("after receiving Initial: dstConnID = %x, want %x", got, want) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 41 | } |
| 42 | |
| 43 | wantLocal := []connID{{ |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 44 | cid: testLocalConnID(0), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 45 | seq: 0, |
| 46 | }} |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 47 | if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { |
| 48 | t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 49 | } |
| 50 | wantRemote := []connID{{ |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 51 | cid: testPeerConnID(0), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 52 | seq: 0, |
| 53 | }} |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 54 | if got := tc.conn.connIDState.remote; !connIDListEqual(got, wantRemote) { |
| 55 | t.Errorf("remote ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantRemote)) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 56 | } |
| 57 | } |
| 58 | |
| 59 | func TestConnIDServerHandshake(t *testing.T) { |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 60 | tc := newTestConn(t, serverSide) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 61 | // On initialization, the server is provided with the client-chosen |
| 62 | // transient connection ID, and allocates an ID of its own. |
| 63 | // The Initial packet sets the remote connection ID. |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 64 | tc.writeFrames(packetTypeInitial, |
| 65 | debugFrameCrypto{ |
| 66 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][:1], |
| 67 | }) |
| 68 | if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) { |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 69 | t.Errorf("after initClient: srcConnID = %q, want %q", got, want) |
| 70 | } |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 71 | dstConnID, _ := tc.conn.connIDState.dstConnID() |
| 72 | if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) { |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 73 | t.Errorf("after initClient: dstConnID = %q, want %q", got, want) |
| 74 | } |
| 75 | |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 76 | // The Initial flight of CRYPTO data includes transport parameters, |
| 77 | // which cause us to allocate another local connection ID. |
| 78 | tc.writeFrames(packetTypeInitial, |
| 79 | debugFrameCrypto{ |
| 80 | off: 1, |
| 81 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][1:], |
| 82 | }) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 83 | wantLocal := []connID{{ |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 84 | cid: testPeerConnID(-1), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 85 | seq: -1, |
| 86 | }, { |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 87 | cid: testLocalConnID(0), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 88 | seq: 0, |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 89 | }, { |
| 90 | cid: testLocalConnID(1), |
| 91 | seq: 1, |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 92 | }} |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 93 | if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { |
| 94 | t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 95 | } |
| 96 | wantRemote := []connID{{ |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 97 | cid: testPeerConnID(0), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 98 | seq: 0, |
| 99 | }} |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 100 | if got := tc.conn.connIDState.remote; !connIDListEqual(got, wantRemote) { |
| 101 | t.Errorf("remote ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantRemote)) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | // The client's first Handshake packet permits the server to discard the |
| 105 | // transient connection ID. |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 106 | tc.writeFrames(packetTypeHandshake, |
| 107 | debugFrameCrypto{ |
| 108 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], |
| 109 | }) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 110 | wantLocal = []connID{{ |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 111 | cid: testLocalConnID(0), |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 112 | seq: 0, |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 113 | }, { |
| 114 | cid: testLocalConnID(1), |
| 115 | seq: 1, |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 116 | }} |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 117 | if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { |
| 118 | t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 119 | } |
| 120 | } |
| 121 | |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 122 | func connIDListEqual(a, b []connID) bool { |
| 123 | if len(a) != len(b) { |
| 124 | return false |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 125 | } |
Damien Neil | 47caaff | 2023-09-12 15:19:59 -0700 | [diff] [blame] | 126 | for i := range a { |
| 127 | if a[i].seq != b[i].seq { |
| 128 | return false |
| 129 | } |
| 130 | if !bytes.Equal(a[i].cid, b[i].cid) { |
| 131 | return false |
| 132 | } |
| 133 | } |
| 134 | return true |
| 135 | } |
| 136 | |
| 137 | func fmtConnIDList(s []connID) string { |
| 138 | var strs []string |
| 139 | for _, cid := range s { |
| 140 | strs = append(strs, fmt.Sprintf("[seq:%v cid:{%x}]", cid.seq, cid.cid)) |
| 141 | } |
| 142 | return "{" + strings.Join(strs, " ") + "}" |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | func TestNewRandomConnID(t *testing.T) { |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 146 | cid, err := newRandomConnID(0) |
Damien Neil | 57553cb | 2022-10-13 12:09:20 -0700 | [diff] [blame] | 147 | if len(cid) != connIDLen || err != nil { |
| 148 | t.Fatalf("newConnID() = %x, %v; want %v bytes", cid, connIDLen, err) |
| 149 | } |
| 150 | } |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 151 | |
| 152 | func TestConnIDPeerRequestsManyIDs(t *testing.T) { |
| 153 | // "An endpoint SHOULD ensure that its peer has a sufficient number |
| 154 | // of available and unused connection IDs." |
| 155 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 |
| 156 | // |
| 157 | // "An endpoint MAY limit the total number of connection IDs |
| 158 | // issued for each connection [...]" |
| 159 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6 |
| 160 | // |
| 161 | // Peer requests 100 connection IDs. |
| 162 | // We give them 4 in total. |
| 163 | tc := newTestConn(t, serverSide, func(p *transportParameters) { |
| 164 | p.activeConnIDLimit = 100 |
| 165 | }) |
| 166 | tc.ignoreFrame(frameTypeAck) |
| 167 | tc.ignoreFrame(frameTypeCrypto) |
| 168 | |
| 169 | tc.writeFrames(packetTypeInitial, |
| 170 | debugFrameCrypto{ |
| 171 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], |
| 172 | }) |
| 173 | tc.wantFrame("provide additional connection ID 1", |
| 174 | packetType1RTT, debugFrameNewConnectionID{ |
| 175 | seq: 1, |
| 176 | connID: testLocalConnID(1), |
| 177 | }) |
| 178 | tc.wantFrame("provide additional connection ID 2", |
| 179 | packetType1RTT, debugFrameNewConnectionID{ |
| 180 | seq: 2, |
| 181 | connID: testLocalConnID(2), |
| 182 | }) |
| 183 | tc.wantFrame("provide additional connection ID 3", |
| 184 | packetType1RTT, debugFrameNewConnectionID{ |
| 185 | seq: 3, |
| 186 | connID: testLocalConnID(3), |
| 187 | }) |
| 188 | tc.wantIdle("connection ID limit reached, no more to provide") |
| 189 | } |
| 190 | |
| 191 | func TestConnIDPeerProvidesTooManyIDs(t *testing.T) { |
| 192 | // "An endpoint MUST NOT provide more connection IDs than the peer's limit." |
| 193 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 |
| 194 | tc := newTestConn(t, serverSide) |
| 195 | tc.handshake() |
| 196 | tc.ignoreFrame(frameTypeAck) |
| 197 | |
| 198 | tc.writeFrames(packetType1RTT, |
| 199 | debugFrameNewConnectionID{ |
| 200 | seq: 2, |
| 201 | connID: testLocalConnID(2), |
| 202 | }) |
| 203 | tc.wantFrame("peer provided 3 connection IDs, our limit is 2", |
| 204 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 205 | code: errConnectionIDLimit, |
| 206 | }) |
| 207 | } |
| 208 | |
| 209 | func TestConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) { |
| 210 | // "An endpoint MAY send connection IDs that temporarily exceed a peer's limit |
| 211 | // if the NEW_CONNECTION_ID frame also requires the retirement of any excess [...]" |
| 212 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 |
| 213 | tc := newTestConn(t, serverSide) |
| 214 | tc.handshake() |
| 215 | tc.ignoreFrame(frameTypeAck) |
| 216 | |
| 217 | tc.writeFrames(packetType1RTT, |
| 218 | debugFrameNewConnectionID{ |
| 219 | retirePriorTo: 2, |
| 220 | seq: 2, |
| 221 | connID: testPeerConnID(2), |
| 222 | }, debugFrameNewConnectionID{ |
| 223 | retirePriorTo: 2, |
| 224 | seq: 3, |
| 225 | connID: testPeerConnID(3), |
| 226 | }) |
| 227 | tc.wantFrame("peer requested we retire conn id 0", |
| 228 | packetType1RTT, debugFrameRetireConnectionID{ |
| 229 | seq: 0, |
| 230 | }) |
| 231 | tc.wantFrame("peer requested we retire conn id 1", |
| 232 | packetType1RTT, debugFrameRetireConnectionID{ |
| 233 | seq: 1, |
| 234 | }) |
| 235 | } |
| 236 | |
| 237 | func TestConnIDPeerRetiresConnID(t *testing.T) { |
| 238 | // "An endpoint SHOULD supply a new connection ID when the peer retires a connection ID." |
| 239 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6 |
| 240 | for _, side := range []connSide{ |
| 241 | clientSide, |
| 242 | serverSide, |
| 243 | } { |
| 244 | t.Run(side.String(), func(t *testing.T) { |
| 245 | tc := newTestConn(t, side) |
| 246 | tc.handshake() |
| 247 | tc.ignoreFrame(frameTypeAck) |
| 248 | |
| 249 | tc.writeFrames(packetType1RTT, |
| 250 | debugFrameRetireConnectionID{ |
| 251 | seq: 0, |
| 252 | }) |
| 253 | tc.wantFrame("provide replacement connection ID", |
| 254 | packetType1RTT, debugFrameNewConnectionID{ |
| 255 | seq: 2, |
| 256 | retirePriorTo: 1, |
| 257 | connID: testLocalConnID(2), |
| 258 | }) |
| 259 | }) |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) { |
Damien Neil | 21814e7 | 2023-09-20 18:29:51 -0700 | [diff] [blame] | 264 | // "An endpoint that selects a zero-length connection ID during the handshake |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 265 | // cannot issue a new connection ID." |
| 266 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-8 |
Damien Neil | 21814e7 | 2023-09-20 18:29:51 -0700 | [diff] [blame] | 267 | tc := newTestConn(t, clientSide, func(p *transportParameters) { |
| 268 | p.initialSrcConnID = []byte{} |
| 269 | }) |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 270 | tc.peerConnID = []byte{} |
| 271 | tc.ignoreFrame(frameTypeAck) |
| 272 | tc.uncheckedHandshake() |
| 273 | |
| 274 | tc.writeFrames(packetType1RTT, |
| 275 | debugFrameNewConnectionID{ |
| 276 | seq: 1, |
| 277 | connID: testPeerConnID(1), |
| 278 | }) |
| 279 | tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length", |
| 280 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 281 | code: errProtocolViolation, |
| 282 | }) |
| 283 | } |
| 284 | |
| 285 | func TestConnIDPeerRequestsRetirement(t *testing.T) { |
| 286 | // "Upon receipt of an increased Retire Prior To field, the peer MUST |
| 287 | // stop using the corresponding connection IDs and retire them with |
| 288 | // RETIRE_CONNECTION_ID frames [...]" |
| 289 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-5 |
| 290 | tc := newTestConn(t, clientSide) |
| 291 | tc.handshake() |
| 292 | tc.ignoreFrame(frameTypeAck) |
| 293 | |
| 294 | tc.writeFrames(packetType1RTT, |
| 295 | debugFrameNewConnectionID{ |
| 296 | seq: 2, |
| 297 | retirePriorTo: 1, |
| 298 | connID: testPeerConnID(2), |
| 299 | }) |
| 300 | tc.wantFrame("peer asked for conn id 0 to be retired", |
| 301 | packetType1RTT, debugFrameRetireConnectionID{ |
| 302 | seq: 0, |
| 303 | }) |
Damien Neil | 52fbe37 | 2023-08-13 10:33:31 -0400 | [diff] [blame] | 304 | if got, want := tc.lastPacket.dstConnID, testPeerConnID(1); !bytes.Equal(got, want) { |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 305 | t.Fatalf("used destination conn id {%x}, want {%x}", got, want) |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) { |
| 310 | // "An endpoint SHOULD limit the number of connection IDs it has retired locally |
| 311 | // for which RETIRE_CONNECTION_ID frames have not yet been acknowledged." |
| 312 | // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6 |
| 313 | tc := newTestConn(t, clientSide) |
| 314 | tc.handshake() |
| 315 | tc.ignoreFrame(frameTypeAck) |
| 316 | tc.ignoreFrame(frameTypeRetireConnectionID) |
| 317 | |
| 318 | // Send a number of NEW_CONNECTION_ID frames, each retiring an old one. |
| 319 | for seq := int64(0); seq < 7; seq++ { |
| 320 | tc.writeFrames(packetType1RTT, |
| 321 | debugFrameNewConnectionID{ |
| 322 | seq: seq + 2, |
| 323 | retirePriorTo: seq + 1, |
| 324 | connID: testPeerConnID(seq + 2), |
| 325 | }) |
| 326 | // We're ignoring the RETIRE_CONNECTION_ID frames. |
| 327 | } |
| 328 | tc.wantFrame("number of retired, unacked conn ids is too large", |
| 329 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 330 | code: errConnectionIDLimit, |
| 331 | }) |
| 332 | } |
| 333 | |
| 334 | func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) { |
| 335 | // "Receipt of the same [NEW_CONNECTION_ID] frame multiple times |
| 336 | // MUST NOT be treated as a connection error. |
| 337 | // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-7 |
| 338 | tc := newTestConn(t, clientSide) |
| 339 | tc.handshake() |
| 340 | tc.ignoreFrame(frameTypeAck) |
| 341 | |
| 342 | for i := 0; i < 4; i++ { |
| 343 | tc.writeFrames(packetType1RTT, |
| 344 | debugFrameNewConnectionID{ |
| 345 | seq: 2, |
| 346 | retirePriorTo: 1, |
| 347 | connID: testPeerConnID(2), |
| 348 | }) |
| 349 | } |
| 350 | tc.wantFrame("peer asked for conn id to be retired", |
| 351 | packetType1RTT, debugFrameRetireConnectionID{ |
| 352 | seq: 0, |
| 353 | }) |
| 354 | tc.wantIdle("repeated NEW_CONNECTION_ID frames are not an error") |
| 355 | } |
| 356 | |
| 357 | func TestConnIDForSequenceNumberChanges(t *testing.T) { |
| 358 | // "[...] if a sequence number is used for different connection IDs, |
| 359 | // the endpoint MAY treat that receipt as a connection error |
| 360 | // of type PROTOCOL_VIOLATION." |
| 361 | // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-8 |
| 362 | tc := newTestConn(t, clientSide) |
| 363 | tc.handshake() |
| 364 | tc.ignoreFrame(frameTypeAck) |
| 365 | tc.ignoreFrame(frameTypeRetireConnectionID) |
| 366 | |
| 367 | tc.writeFrames(packetType1RTT, |
| 368 | debugFrameNewConnectionID{ |
| 369 | seq: 2, |
| 370 | retirePriorTo: 1, |
| 371 | connID: testPeerConnID(2), |
| 372 | }) |
| 373 | tc.writeFrames(packetType1RTT, |
| 374 | debugFrameNewConnectionID{ |
| 375 | seq: 2, |
| 376 | retirePriorTo: 1, |
| 377 | connID: testPeerConnID(3), |
| 378 | }) |
| 379 | tc.wantFrame("connection ID for sequence 0 has changed", |
| 380 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 381 | code: errProtocolViolation, |
| 382 | }) |
| 383 | } |
| 384 | |
| 385 | func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) { |
| 386 | // "Receiving a value in the Retire Prior To field that is greater than |
| 387 | // that in the Sequence Number field MUST be treated as a connection error |
| 388 | // of type FRAME_ENCODING_ERROR. |
| 389 | // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-9 |
| 390 | tc := newTestConn(t, serverSide) |
| 391 | tc.handshake() |
| 392 | tc.ignoreFrame(frameTypeAck) |
| 393 | |
| 394 | tc.writeFrames(packetType1RTT, |
| 395 | debugFrameNewConnectionID{ |
| 396 | retirePriorTo: 3, |
| 397 | seq: 2, |
| 398 | connID: testPeerConnID(2), |
| 399 | }) |
| 400 | tc.wantFrame("invalid NEW_CONNECTION_ID: retired the new conn id", |
| 401 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 402 | code: errFrameEncoding, |
| 403 | }) |
| 404 | } |
| 405 | |
| 406 | func TestConnIDAlreadyRetired(t *testing.T) { |
| 407 | // "An endpoint that receives a NEW_CONNECTION_ID frame with a |
| 408 | // sequence number smaller than the Retire Prior To field of a |
| 409 | // previously received NEW_CONNECTION_ID frame MUST send a |
| 410 | // corresponding RETIRE_CONNECTION_ID frame [...]" |
| 411 | // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-11 |
| 412 | tc := newTestConn(t, clientSide) |
| 413 | tc.handshake() |
| 414 | tc.ignoreFrame(frameTypeAck) |
| 415 | |
| 416 | tc.writeFrames(packetType1RTT, |
| 417 | debugFrameNewConnectionID{ |
| 418 | seq: 4, |
| 419 | retirePriorTo: 3, |
| 420 | connID: testPeerConnID(4), |
| 421 | }) |
| 422 | tc.wantFrame("peer asked for conn id to be retired", |
| 423 | packetType1RTT, debugFrameRetireConnectionID{ |
| 424 | seq: 0, |
| 425 | }) |
| 426 | tc.wantFrame("peer asked for conn id to be retired", |
| 427 | packetType1RTT, debugFrameRetireConnectionID{ |
| 428 | seq: 1, |
| 429 | }) |
| 430 | tc.writeFrames(packetType1RTT, |
| 431 | debugFrameNewConnectionID{ |
| 432 | seq: 2, |
| 433 | retirePriorTo: 0, |
| 434 | connID: testPeerConnID(2), |
| 435 | }) |
| 436 | tc.wantFrame("NEW_CONNECTION_ID was for an already-retired ID", |
| 437 | packetType1RTT, debugFrameRetireConnectionID{ |
| 438 | seq: 2, |
| 439 | }) |
| 440 | } |
| 441 | |
| 442 | func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) { |
| 443 | tc := newTestConn(t, clientSide) |
| 444 | tc.handshake() |
| 445 | tc.ignoreFrame(frameTypeAck) |
| 446 | |
| 447 | for i := 0; i < 4; i++ { |
| 448 | tc.writeFrames(packetType1RTT, |
| 449 | debugFrameRetireConnectionID{ |
| 450 | seq: 0, |
| 451 | }) |
| 452 | } |
| 453 | tc.wantFrame("issue new conn id after peer retires one", |
| 454 | packetType1RTT, debugFrameNewConnectionID{ |
| 455 | retirePriorTo: 1, |
| 456 | seq: 2, |
| 457 | connID: testLocalConnID(2), |
| 458 | }) |
| 459 | tc.wantIdle("repeated RETIRE_CONNECTION_ID frames are not an error") |
| 460 | } |
| 461 | |
| 462 | func TestConnIDRetiredUnsent(t *testing.T) { |
| 463 | // "Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number |
| 464 | // greater than any previously sent to the peer MUST be treated as a |
| 465 | // connection error of type PROTOCOL_VIOLATION." |
| 466 | // https://www.rfc-editor.org/rfc/rfc9000#section-19.16-7 |
| 467 | tc := newTestConn(t, clientSide) |
| 468 | tc.handshake() |
| 469 | tc.ignoreFrame(frameTypeAck) |
| 470 | |
| 471 | tc.writeFrames(packetType1RTT, |
| 472 | debugFrameRetireConnectionID{ |
| 473 | seq: 2, |
| 474 | }) |
| 475 | tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length", |
| 476 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 477 | code: errProtocolViolation, |
| 478 | }) |
| 479 | } |
| 480 | |
| 481 | func TestConnIDUsePreferredAddressConnID(t *testing.T) { |
| 482 | // Peer gives us a connection ID in the preferred address transport parameter. |
| 483 | // We don't use the preferred address at this time, but we should use the |
| 484 | // connection ID. (It isn't tied to any specific address.) |
| 485 | // |
| 486 | // This test will probably need updating if/when we start using the preferred address. |
| 487 | cid := testPeerConnID(10) |
| 488 | tc := newTestConn(t, serverSide, func(p *transportParameters) { |
| 489 | p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") |
| 490 | p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") |
| 491 | p.preferredAddrConnID = cid |
| 492 | p.preferredAddrResetToken = make([]byte, 16) |
| 493 | }) |
| 494 | tc.uncheckedHandshake() |
| 495 | tc.ignoreFrame(frameTypeAck) |
| 496 | |
| 497 | tc.writeFrames(packetType1RTT, |
| 498 | debugFrameNewConnectionID{ |
| 499 | seq: 2, |
| 500 | retirePriorTo: 1, |
| 501 | connID: []byte{0xff}, |
| 502 | }) |
| 503 | tc.wantFrame("peer asked for conn id 0 to be retired", |
| 504 | packetType1RTT, debugFrameRetireConnectionID{ |
| 505 | seq: 0, |
| 506 | }) |
Damien Neil | 52fbe37 | 2023-08-13 10:33:31 -0400 | [diff] [blame] | 507 | if got, want := tc.lastPacket.dstConnID, cid; !bytes.Equal(got, want) { |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 508 | t.Fatalf("used destination conn id {%x}, want {%x} from preferred address transport parameter", got, want) |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) { |
| 513 | // Peer gives us more conn ids than our advertised limit, |
| 514 | // including a conn id in the preferred address transport parameter. |
| 515 | cid := testPeerConnID(10) |
| 516 | tc := newTestConn(t, serverSide, func(p *transportParameters) { |
| 517 | p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") |
| 518 | p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") |
| 519 | p.preferredAddrConnID = cid |
| 520 | p.preferredAddrResetToken = make([]byte, 16) |
| 521 | }) |
| 522 | tc.uncheckedHandshake() |
| 523 | tc.ignoreFrame(frameTypeAck) |
| 524 | |
| 525 | tc.writeFrames(packetType1RTT, |
| 526 | debugFrameNewConnectionID{ |
| 527 | seq: 2, |
| 528 | retirePriorTo: 0, |
| 529 | connID: testPeerConnID(2), |
| 530 | }) |
| 531 | tc.wantFrame("peer provided 3 connection IDs, our limit is 2", |
| 532 | packetType1RTT, debugFrameConnectionCloseTransport{ |
| 533 | code: errConnectionIDLimit, |
| 534 | }) |
| 535 | } |
| 536 | |
| 537 | func TestConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) { |
| 538 | // Peer gives us more conn ids than our advertised limit, |
| 539 | // including a conn id in the preferred address transport parameter. |
| 540 | tc := newTestConn(t, serverSide, func(p *transportParameters) { |
Damien Neil | 21814e7 | 2023-09-20 18:29:51 -0700 | [diff] [blame] | 541 | p.initialSrcConnID = []byte{} |
Damien Neil | bd8ac9e | 2023-07-20 16:45:15 -0700 | [diff] [blame] | 542 | p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") |
| 543 | p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") |
| 544 | p.preferredAddrConnID = testPeerConnID(1) |
| 545 | p.preferredAddrResetToken = make([]byte, 16) |
| 546 | }) |
| 547 | tc.peerConnID = []byte{} |
| 548 | |
| 549 | tc.writeFrames(packetTypeInitial, |
| 550 | debugFrameCrypto{ |
| 551 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], |
| 552 | }) |
| 553 | tc.wantFrame("peer with zero-length connection ID tried to provide another in transport parameters", |
| 554 | packetTypeInitial, debugFrameConnectionCloseTransport{ |
| 555 | code: errProtocolViolation, |
| 556 | }) |
| 557 | } |
Damien Neil | 21814e7 | 2023-09-20 18:29:51 -0700 | [diff] [blame] | 558 | |
| 559 | func TestConnIDInitialSrcConnIDMismatch(t *testing.T) { |
| 560 | // "Endpoints MUST validate that received [initial_source_connection_id] |
| 561 | // parameters match received connection ID values." |
| 562 | // https://www.rfc-editor.org/rfc/rfc9000#section-7.3-3 |
| 563 | testSides(t, "", func(t *testing.T, side connSide) { |
| 564 | tc := newTestConn(t, side, func(p *transportParameters) { |
| 565 | p.initialSrcConnID = []byte("invalid") |
| 566 | }) |
| 567 | tc.ignoreFrame(frameTypeAck) |
| 568 | tc.ignoreFrame(frameTypeCrypto) |
| 569 | tc.writeFrames(packetTypeInitial, |
| 570 | debugFrameCrypto{ |
| 571 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], |
| 572 | }) |
| 573 | if side == clientSide { |
| 574 | // Server transport parameters are carried in the Handshake packet. |
| 575 | tc.writeFrames(packetTypeHandshake, |
| 576 | debugFrameCrypto{ |
| 577 | data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], |
| 578 | }) |
| 579 | } |
| 580 | tc.wantFrame("initial_source_connection_id transport parameter mismatch", |
| 581 | packetTypeInitial, debugFrameConnectionCloseTransport{ |
| 582 | code: errTransportParameter, |
| 583 | }) |
| 584 | }) |
| 585 | } |