mirror of
https://github.com/golang/net.git
synced 2026-03-31 10:27:08 +09:00
Now that the x/net module requires Go 1.25.0, the go1.25 build constraint is always satisfied. Simplify the code accordingly. Change-Id: I3d6fe4a132a26918455489b998730b494f5273c4 Reviewed-on: https://go-review.googlesource.com/c/net/+/744800 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Nicholas Husin <nsh@golang.org> Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
769 lines
24 KiB
Go
769 lines
24 KiB
Go
// 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 (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
"testing/synctest"
|
|
)
|
|
|
|
func TestConnIDClientHandshake(t *testing.T) {
|
|
synctest.Test(t, testConnIDClientHandshake)
|
|
}
|
|
func testConnIDClientHandshake(t *testing.T) {
|
|
tc := newTestConn(t, clientSide)
|
|
// On initialization, the client chooses local and remote IDs.
|
|
//
|
|
// The order in which we allocate the two isn't actually important,
|
|
// but test is a lot simpler if we assume.
|
|
if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) {
|
|
t.Errorf("after initialization: srcConnID = %x, want %x", got, want)
|
|
}
|
|
dstConnID, _ := tc.conn.connIDState.dstConnID()
|
|
if got, want := dstConnID, testLocalConnID(-1); !bytes.Equal(got, want) {
|
|
t.Errorf("after initialization: dstConnID = %x, want %x", got, want)
|
|
}
|
|
|
|
// The server's first Initial packet provides the client with a
|
|
// non-transient remote connection ID.
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
dstConnID, _ = tc.conn.connIDState.dstConnID()
|
|
if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) {
|
|
t.Errorf("after receiving Initial: dstConnID = %x, want %x", got, want)
|
|
}
|
|
|
|
wantLocal := []connID{{
|
|
cid: testLocalConnID(0),
|
|
seq: 0,
|
|
}}
|
|
if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) {
|
|
t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal))
|
|
}
|
|
wantRemote := []remoteConnID{{
|
|
connID: connID{
|
|
cid: testPeerConnID(0),
|
|
seq: 0,
|
|
},
|
|
}}
|
|
if got := tc.conn.connIDState.remote; !remoteConnIDListEqual(got, wantRemote) {
|
|
t.Errorf("remote ids: %v, want %v", fmtRemoteConnIDList(got), fmtRemoteConnIDList(wantRemote))
|
|
}
|
|
}
|
|
|
|
func TestConnIDServerHandshake(t *testing.T) {
|
|
synctest.Test(t, testConnIDServerHandshake)
|
|
}
|
|
func testConnIDServerHandshake(t *testing.T) {
|
|
tc := newTestConn(t, serverSide)
|
|
// On initialization, the server is provided with the client-chosen
|
|
// transient connection ID, and allocates an ID of its own.
|
|
// The Initial packet sets the remote connection ID.
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][:1],
|
|
})
|
|
if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) {
|
|
t.Errorf("after initClient: srcConnID = %q, want %q", got, want)
|
|
}
|
|
dstConnID, _ := tc.conn.connIDState.dstConnID()
|
|
if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) {
|
|
t.Errorf("after initClient: dstConnID = %q, want %q", got, want)
|
|
}
|
|
|
|
// The Initial flight of CRYPTO data includes transport parameters,
|
|
// which cause us to allocate another local connection ID.
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
off: 1,
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][1:],
|
|
})
|
|
wantLocal := []connID{{
|
|
cid: testPeerConnID(-1),
|
|
seq: -1,
|
|
}, {
|
|
cid: testLocalConnID(0),
|
|
seq: 0,
|
|
}, {
|
|
cid: testLocalConnID(1),
|
|
seq: 1,
|
|
}}
|
|
if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) {
|
|
t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal))
|
|
}
|
|
wantRemote := []remoteConnID{{
|
|
connID: connID{
|
|
cid: testPeerConnID(0),
|
|
seq: 0,
|
|
},
|
|
}}
|
|
if got := tc.conn.connIDState.remote; !remoteConnIDListEqual(got, wantRemote) {
|
|
t.Errorf("remote ids: %v, want %v", fmtRemoteConnIDList(got), fmtRemoteConnIDList(wantRemote))
|
|
}
|
|
|
|
// The client's first Handshake packet permits the server to discard the
|
|
// transient connection ID.
|
|
tc.writeFrames(packetTypeHandshake,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
|
|
})
|
|
wantLocal = []connID{{
|
|
cid: testLocalConnID(0),
|
|
seq: 0,
|
|
}, {
|
|
cid: testLocalConnID(1),
|
|
seq: 1,
|
|
}}
|
|
if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) {
|
|
t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal))
|
|
}
|
|
}
|
|
|
|
func connIDListEqual(a, b []connID) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].seq != b[i].seq {
|
|
return false
|
|
}
|
|
if !bytes.Equal(a[i].cid, b[i].cid) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func remoteConnIDListEqual(a, b []remoteConnID) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].seq != b[i].seq {
|
|
return false
|
|
}
|
|
if !bytes.Equal(a[i].cid, b[i].cid) {
|
|
return false
|
|
}
|
|
if a[i].resetToken != b[i].resetToken {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func fmtConnIDList(s []connID) string {
|
|
var strs []string
|
|
for _, cid := range s {
|
|
strs = append(strs, fmt.Sprintf("[seq:%v cid:{%x}]", cid.seq, cid.cid))
|
|
}
|
|
return "{" + strings.Join(strs, " ") + "}"
|
|
}
|
|
|
|
func fmtRemoteConnIDList(s []remoteConnID) string {
|
|
var strs []string
|
|
for _, cid := range s {
|
|
strs = append(strs, fmt.Sprintf("[seq:%v cid:{%x} token:{%x}]", cid.seq, cid.cid, cid.resetToken))
|
|
}
|
|
return "{" + strings.Join(strs, " ") + "}"
|
|
}
|
|
|
|
func TestNewRandomConnID(t *testing.T) {
|
|
cid, err := newRandomConnID(0)
|
|
if len(cid) != connIDLen || err != nil {
|
|
t.Fatalf("newConnID() = %x, %v; want %v bytes", cid, connIDLen, err)
|
|
}
|
|
}
|
|
|
|
func TestConnIDPeerRequestsManyIDs(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerRequestsManyIDs)
|
|
}
|
|
func testConnIDPeerRequestsManyIDs(t *testing.T) {
|
|
// "An endpoint SHOULD ensure that its peer has a sufficient number
|
|
// of available and unused connection IDs."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
|
//
|
|
// "An endpoint MAY limit the total number of connection IDs
|
|
// issued for each connection [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6
|
|
//
|
|
// Peer requests 100 connection IDs.
|
|
// We give them 4 in total.
|
|
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
|
p.activeConnIDLimit = 100
|
|
})
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.ignoreFrame(frameTypeCrypto)
|
|
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
tc.wantFrame("provide additional connection ID 1",
|
|
packetType1RTT, debugFrameNewConnectionID{
|
|
seq: 1,
|
|
connID: testLocalConnID(1),
|
|
token: testLocalStatelessResetToken(1),
|
|
})
|
|
tc.wantFrame("provide additional connection ID 2",
|
|
packetType1RTT, debugFrameNewConnectionID{
|
|
seq: 2,
|
|
connID: testLocalConnID(2),
|
|
token: testLocalStatelessResetToken(2),
|
|
})
|
|
tc.wantFrame("provide additional connection ID 3",
|
|
packetType1RTT, debugFrameNewConnectionID{
|
|
seq: 3,
|
|
connID: testLocalConnID(3),
|
|
token: testLocalStatelessResetToken(3),
|
|
})
|
|
tc.wantIdle("connection ID limit reached, no more to provide")
|
|
}
|
|
|
|
func TestConnIDPeerProvidesTooManyIDs(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerProvidesTooManyIDs)
|
|
}
|
|
func testConnIDPeerProvidesTooManyIDs(t *testing.T) {
|
|
// "An endpoint MUST NOT provide more connection IDs than the peer's limit."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
|
tc := newTestConn(t, serverSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
connID: testLocalConnID(2),
|
|
})
|
|
tc.wantFrame("peer provided 3 connection IDs, our limit is 2",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errConnectionIDLimit,
|
|
})
|
|
}
|
|
|
|
func TestConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerTemporarilyExceedsActiveConnIDLimit)
|
|
}
|
|
func testConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
|
|
// "An endpoint MAY send connection IDs that temporarily exceed a peer's limit
|
|
// if the NEW_CONNECTION_ID frame also requires the retirement of any excess [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
|
tc := newTestConn(t, serverSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
retirePriorTo: 2,
|
|
seq: 2,
|
|
connID: testPeerConnID(2),
|
|
}, debugFrameNewConnectionID{
|
|
retirePriorTo: 2,
|
|
seq: 3,
|
|
connID: testPeerConnID(3),
|
|
})
|
|
tc.wantFrame("peer requested we retire conn id 0",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
tc.wantFrame("peer requested we retire conn id 1",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 1,
|
|
})
|
|
}
|
|
|
|
func TestConnIDPeerRetiresConnID(t *testing.T) {
|
|
// "An endpoint SHOULD supply a new connection ID when the peer retires a connection ID."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6
|
|
for _, side := range []connSide{
|
|
clientSide,
|
|
serverSide,
|
|
} {
|
|
synctestSubtest(t, side.String(), func(t *testing.T) {
|
|
tc := newTestConn(t, side)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
tc.wantFrame("provide replacement connection ID",
|
|
packetType1RTT, debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testLocalConnID(2),
|
|
token: testLocalStatelessResetToken(2),
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID)
|
|
}
|
|
func testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
|
|
// "An endpoint that selects a zero-length connection ID during the handshake
|
|
// cannot issue a new connection ID."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-8
|
|
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
|
p.initialSrcConnID = []byte{}
|
|
})
|
|
tc.peerConnID = []byte{}
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.uncheckedHandshake()
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 1,
|
|
connID: testPeerConnID(1),
|
|
})
|
|
tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errProtocolViolation,
|
|
})
|
|
}
|
|
|
|
func TestConnIDPeerRequestsRetirement(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerRequestsRetirement)
|
|
}
|
|
func testConnIDPeerRequestsRetirement(t *testing.T) {
|
|
// "Upon receipt of an increased Retire Prior To field, the peer MUST
|
|
// stop using the corresponding connection IDs and retire them with
|
|
// RETIRE_CONNECTION_ID frames [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-5
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.wantFrame("peer asked for conn id 0 to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
if got, want := tc.lastPacket.dstConnID, testPeerConnID(1); !bytes.Equal(got, want) {
|
|
t.Fatalf("used destination conn id {%x}, want {%x}", got, want)
|
|
}
|
|
}
|
|
|
|
func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerDoesNotAcknowledgeRetirement)
|
|
}
|
|
func testConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
|
|
// "An endpoint SHOULD limit the number of connection IDs it has retired locally
|
|
// for which RETIRE_CONNECTION_ID frames have not yet been acknowledged."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.ignoreFrame(frameTypeRetireConnectionID)
|
|
|
|
// Send a number of NEW_CONNECTION_ID frames, each retiring an old one.
|
|
for seq := int64(0); seq < 7; seq++ {
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: seq + 2,
|
|
retirePriorTo: seq + 1,
|
|
connID: testPeerConnID(seq + 2),
|
|
})
|
|
// We're ignoring the RETIRE_CONNECTION_ID frames.
|
|
}
|
|
tc.wantFrame("number of retired, unacked conn ids is too large",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errConnectionIDLimit,
|
|
})
|
|
}
|
|
|
|
func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
|
|
synctest.Test(t, testConnIDRepeatedNewConnectionIDFrame)
|
|
}
|
|
func testConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
|
|
// "Receipt of the same [NEW_CONNECTION_ID] frame multiple times
|
|
// MUST NOT be treated as a connection error.
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-7
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
for i := 0; i < 4; i++ {
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
}
|
|
tc.wantFrame("peer asked for conn id to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
tc.wantIdle("repeated NEW_CONNECTION_ID frames are not an error")
|
|
}
|
|
|
|
func TestConnIDForSequenceNumberChanges(t *testing.T) {
|
|
synctest.Test(t, testConnIDForSequenceNumberChanges)
|
|
}
|
|
func testConnIDForSequenceNumberChanges(t *testing.T) {
|
|
// "[...] if a sequence number is used for different connection IDs,
|
|
// the endpoint MAY treat that receipt as a connection error
|
|
// of type PROTOCOL_VIOLATION."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-8
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.ignoreFrame(frameTypeRetireConnectionID)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testPeerConnID(3),
|
|
})
|
|
tc.wantFrame("connection ID for sequence 0 has changed",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errProtocolViolation,
|
|
})
|
|
}
|
|
|
|
func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) {
|
|
synctest.Test(t, testConnIDRetirePriorToAfterNewConnID)
|
|
}
|
|
func testConnIDRetirePriorToAfterNewConnID(t *testing.T) {
|
|
// "Receiving a value in the Retire Prior To field that is greater than
|
|
// that in the Sequence Number field MUST be treated as a connection error
|
|
// of type FRAME_ENCODING_ERROR.
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-9
|
|
tc := newTestConn(t, serverSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
retirePriorTo: 3,
|
|
seq: 2,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.wantFrame("invalid NEW_CONNECTION_ID: retired the new conn id",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errFrameEncoding,
|
|
})
|
|
}
|
|
|
|
func TestConnIDAlreadyRetired(t *testing.T) {
|
|
synctest.Test(t, testConnIDAlreadyRetired)
|
|
}
|
|
func testConnIDAlreadyRetired(t *testing.T) {
|
|
// "An endpoint that receives a NEW_CONNECTION_ID frame with a
|
|
// sequence number smaller than the Retire Prior To field of a
|
|
// previously received NEW_CONNECTION_ID frame MUST send a
|
|
// corresponding RETIRE_CONNECTION_ID frame [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-11
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 4,
|
|
retirePriorTo: 3,
|
|
connID: testPeerConnID(4),
|
|
})
|
|
tc.wantFrame("peer asked for conn id to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
tc.wantFrame("peer asked for conn id to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 1,
|
|
})
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 0,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.wantFrame("NEW_CONNECTION_ID was for an already-retired ID",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 2,
|
|
})
|
|
}
|
|
|
|
func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
|
|
synctest.Test(t, testConnIDRepeatedRetireConnectionIDFrame)
|
|
}
|
|
func testConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
for i := 0; i < 4; i++ {
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
}
|
|
tc.wantFrame("issue new conn id after peer retires one",
|
|
packetType1RTT, debugFrameNewConnectionID{
|
|
retirePriorTo: 1,
|
|
seq: 2,
|
|
connID: testLocalConnID(2),
|
|
token: testLocalStatelessResetToken(2),
|
|
})
|
|
tc.wantIdle("repeated RETIRE_CONNECTION_ID frames are not an error")
|
|
}
|
|
|
|
func TestConnIDRetiredUnsent(t *testing.T) {
|
|
synctest.Test(t, testConnIDRetiredUnsent)
|
|
}
|
|
func testConnIDRetiredUnsent(t *testing.T) {
|
|
// "Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number
|
|
// greater than any previously sent to the peer MUST be treated as a
|
|
// connection error of type PROTOCOL_VIOLATION."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-19.16-7
|
|
tc := newTestConn(t, clientSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameRetireConnectionID{
|
|
seq: 2,
|
|
})
|
|
tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errProtocolViolation,
|
|
})
|
|
}
|
|
|
|
func TestConnIDUsePreferredAddressConnID(t *testing.T) {
|
|
synctest.Test(t, testConnIDUsePreferredAddressConnID)
|
|
}
|
|
func testConnIDUsePreferredAddressConnID(t *testing.T) {
|
|
// Peer gives us a connection ID in the preferred address transport parameter.
|
|
// We don't use the preferred address at this time, but we should use the
|
|
// connection ID. (It isn't tied to any specific address.)
|
|
//
|
|
// This test will probably need updating if/when we start using the preferred address.
|
|
cid := testPeerConnID(10)
|
|
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
|
p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0")
|
|
p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0")
|
|
p.preferredAddrConnID = cid
|
|
p.preferredAddrResetToken = make([]byte, 16)
|
|
})
|
|
tc.uncheckedHandshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: []byte{0xff},
|
|
})
|
|
tc.wantFrame("peer asked for conn id 0 to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
if got, want := tc.lastPacket.dstConnID, cid; !bytes.Equal(got, want) {
|
|
t.Fatalf("used destination conn id {%x}, want {%x} from preferred address transport parameter", got, want)
|
|
}
|
|
}
|
|
|
|
func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs)
|
|
}
|
|
func testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
|
|
// Peer gives us more conn ids than our advertised limit,
|
|
// including a conn id in the preferred address transport parameter.
|
|
cid := testPeerConnID(10)
|
|
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
|
p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0")
|
|
p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0")
|
|
p.preferredAddrConnID = cid
|
|
p.preferredAddrResetToken = make([]byte, 16)
|
|
})
|
|
tc.uncheckedHandshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 0,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.wantFrame("peer provided 3 connection IDs, our limit is 2",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{
|
|
code: errConnectionIDLimit,
|
|
})
|
|
}
|
|
|
|
func TestConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
|
|
synctest.Test(t, testConnIDPeerWithZeroLengthIDProvidesPreferredAddr)
|
|
}
|
|
func testConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
|
|
// Peer gives us more conn ids than our advertised limit,
|
|
// including a conn id in the preferred address transport parameter.
|
|
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
|
p.initialSrcConnID = []byte{}
|
|
p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0")
|
|
p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0")
|
|
p.preferredAddrConnID = testPeerConnID(1)
|
|
p.preferredAddrResetToken = make([]byte, 16)
|
|
}, func(cids *newServerConnIDs) {
|
|
cids.srcConnID = []byte{}
|
|
}, func(tc *testConn) {
|
|
tc.peerConnID = []byte{}
|
|
})
|
|
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
tc.wantFrame("peer with zero-length connection ID tried to provide another in transport parameters",
|
|
packetTypeInitial, debugFrameConnectionCloseTransport{
|
|
code: errProtocolViolation,
|
|
})
|
|
}
|
|
|
|
func TestConnIDInitialSrcConnIDMismatch(t *testing.T) {
|
|
// "Endpoints MUST validate that received [initial_source_connection_id]
|
|
// parameters match received connection ID values."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-7.3-3
|
|
testSidesSynctest(t, "", func(t *testing.T, side connSide) {
|
|
tc := newTestConn(t, side, func(p *transportParameters) {
|
|
p.initialSrcConnID = []byte("invalid")
|
|
})
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.ignoreFrame(frameTypeCrypto)
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
if side == clientSide {
|
|
// Server transport parameters are carried in the Handshake packet.
|
|
tc.writeFrames(packetTypeHandshake,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
|
|
})
|
|
}
|
|
tc.wantFrame("initial_source_connection_id transport parameter mismatch",
|
|
packetTypeInitial, debugFrameConnectionCloseTransport{
|
|
code: errTransportParameter,
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestConnIDsCleanedUpAfterClose(t *testing.T) {
|
|
testSidesSynctest(t, "", func(t *testing.T, side connSide) {
|
|
tc := newTestConn(t, side, func(p *transportParameters) {
|
|
if side == clientSide {
|
|
token := testPeerStatelessResetToken(0)
|
|
p.statelessResetToken = token[:]
|
|
}
|
|
})
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 1,
|
|
connID: testPeerConnID(2),
|
|
token: testPeerStatelessResetToken(0),
|
|
})
|
|
tc.wantFrame("peer asked for conn id 0 to be retired",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
tc.writeFrames(packetType1RTT, debugFrameConnectionCloseTransport{})
|
|
tc.conn.Abort(nil)
|
|
tc.wantFrame("CONN_CLOSE sent after user closes connection",
|
|
packetType1RTT, debugFrameConnectionCloseTransport{})
|
|
|
|
// Wait for the conn to drain.
|
|
// Then wait for the conn loop to exit,
|
|
// and force an immediate sync of the connsMap updates
|
|
// (normally only done by the endpoint read loop).
|
|
tc.advanceToTimer()
|
|
<-tc.conn.donec
|
|
tc.endpoint.e.connsMap.applyUpdates()
|
|
|
|
if got := len(tc.endpoint.e.connsMap.byConnID); got != 0 {
|
|
t.Errorf("%v conn ids in endpoint map after closing, want 0", got)
|
|
}
|
|
if got := len(tc.endpoint.e.connsMap.byResetToken); got != 0 {
|
|
t.Errorf("%v reset tokens in endpoint map after closing, want 0", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestConnIDRetiredConnIDResent(t *testing.T) {
|
|
synctest.Test(t, testConnIDRetiredConnIDResent)
|
|
}
|
|
func testConnIDRetiredConnIDResent(t *testing.T) {
|
|
tc := newTestConn(t, serverSide)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
//tc.ignoreFrame(frameTypeRetireConnectionID)
|
|
|
|
// Send CID 2, retire 0-1 (negotiated during the handshake).
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
retirePriorTo: 2,
|
|
connID: testPeerConnID(2),
|
|
token: testPeerStatelessResetToken(2),
|
|
})
|
|
tc.wantFrame("retire CID 0", packetType1RTT, debugFrameRetireConnectionID{seq: 0})
|
|
tc.wantFrame("retire CID 1", packetType1RTT, debugFrameRetireConnectionID{seq: 1})
|
|
|
|
// Send CID 3, retire 2.
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 3,
|
|
retirePriorTo: 3,
|
|
connID: testPeerConnID(3),
|
|
token: testPeerStatelessResetToken(3),
|
|
})
|
|
tc.wantFrame("retire CID 2", packetType1RTT, debugFrameRetireConnectionID{seq: 2})
|
|
|
|
// Acknowledge retirement of CIDs 0-2.
|
|
// The server should have state for only one CID: 3.
|
|
tc.writeAckForAll()
|
|
if got, want := len(tc.conn.connIDState.remote), 1; got != want {
|
|
t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
|
|
}
|
|
|
|
// Send CID 2 again.
|
|
// The server should ignore this, since it's already retired the CID.
|
|
tc.ignoreFrames[frameTypeRetireConnectionID] = false
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
seq: 2,
|
|
connID: testPeerConnID(2),
|
|
token: testPeerStatelessResetToken(2),
|
|
})
|
|
if got, want := len(tc.conn.connIDState.remote), 1; got != want {
|
|
t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
|
|
}
|
|
tc.wantIdle("server does not re-retire already retired CID 2")
|
|
}
|