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