mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37: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>
303 lines
9.1 KiB
Go
303 lines
9.1 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"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"errors"
|
|
"net/netip"
|
|
"testing"
|
|
"testing/synctest"
|
|
"time"
|
|
)
|
|
|
|
func TestStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
|
|
synctest.Test(t, testStatelessResetClientSendsStatelessResetTokenTransportParameter)
|
|
}
|
|
func testStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
|
|
// "[The stateless_reset_token] transport parameter MUST NOT be sent by a client [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.6.1
|
|
resetToken := testPeerStatelessResetToken(0)
|
|
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
|
p.statelessResetToken = resetToken[:]
|
|
})
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
tc.wantFrame("client provided stateless_reset_token transport parameter",
|
|
packetTypeInitial, debugFrameConnectionCloseTransport{
|
|
code: errTransportParameter,
|
|
})
|
|
}
|
|
|
|
var testStatelessResetKey = func() (key [32]byte) {
|
|
if _, err := rand.Read(key[:]); err != nil {
|
|
panic(err)
|
|
}
|
|
return key
|
|
}()
|
|
|
|
func testStatelessResetToken(cid []byte) statelessResetToken {
|
|
var gen statelessResetTokenGenerator
|
|
gen.init(testStatelessResetKey)
|
|
return gen.tokenForConnID(cid)
|
|
}
|
|
|
|
func testLocalStatelessResetToken(seq int64) statelessResetToken {
|
|
return testStatelessResetToken(testLocalConnID(seq))
|
|
}
|
|
|
|
func newDatagramForReset(cid []byte, size int, addr netip.AddrPort) *datagram {
|
|
dgram := append([]byte{headerFormShort | fixedBit}, cid...)
|
|
for len(dgram) < size {
|
|
dgram = append(dgram, byte(len(dgram))) // semi-random junk
|
|
}
|
|
return &datagram{
|
|
b: dgram,
|
|
peerAddr: addr,
|
|
}
|
|
}
|
|
|
|
func TestStatelessResetSentSizes(t *testing.T) {
|
|
synctest.Test(t, testStatelessResetSentSizes)
|
|
}
|
|
func testStatelessResetSentSizes(t *testing.T) {
|
|
config := &Config{
|
|
TLSConfig: newTestTLSConfig(serverSide),
|
|
StatelessResetKey: testStatelessResetKey,
|
|
}
|
|
addr := netip.MustParseAddr("127.0.0.1")
|
|
te := newTestEndpoint(t, config)
|
|
for i, test := range []struct {
|
|
reqSize int
|
|
wantSize int
|
|
}{{
|
|
// Datagrams larger than 42 bytes result in a 42-byte stateless reset.
|
|
// This isn't specifically mandated by RFC 9000, but is implied.
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-11
|
|
reqSize: 1200,
|
|
wantSize: 42,
|
|
}, {
|
|
// "An endpoint that sends a Stateless Reset in response to a packet
|
|
// that is 43 bytes or shorter SHOULD send a Stateless Reset that is
|
|
// one byte shorter than the packet it responds to."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-11
|
|
reqSize: 43,
|
|
wantSize: 42,
|
|
}, {
|
|
reqSize: 42,
|
|
wantSize: 41,
|
|
}, {
|
|
// We should send a stateless reset in response to the smallest possible
|
|
// valid datagram the peer can send us.
|
|
// The smallest packet is 1-RTT:
|
|
// header byte, conn id, packet num, payload, AEAD.
|
|
reqSize: 1 + connIDLen + 1 + 1 + 16,
|
|
wantSize: 1 + connIDLen + 1 + 1 + 16 - 1,
|
|
}, {
|
|
// The smallest possible stateless reset datagram is 21 bytes.
|
|
// Since our response must be smaller than the incoming datagram,
|
|
// we must not respond to a 21 byte or smaller packet.
|
|
reqSize: 21,
|
|
wantSize: 0,
|
|
}} {
|
|
cid := testLocalConnID(int64(i))
|
|
token := testStatelessResetToken(cid)
|
|
addrport := netip.AddrPortFrom(addr, uint16(8000+i))
|
|
te.write(newDatagramForReset(cid, test.reqSize, addrport))
|
|
|
|
got := te.read()
|
|
if len(got) != test.wantSize {
|
|
t.Errorf("got %v-byte response to %v-byte req, want %v",
|
|
len(got), test.reqSize, test.wantSize)
|
|
}
|
|
if len(got) == 0 {
|
|
continue
|
|
}
|
|
// "Endpoints MUST send Stateless Resets formatted as
|
|
// a packet with a short header."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-15
|
|
if isLongHeader(got[0]) {
|
|
t.Errorf("response to %v-byte request is not a short-header packet\ngot: %x", test.reqSize, got)
|
|
}
|
|
if !bytes.HasSuffix(got, token[:]) {
|
|
t.Errorf("response to %v-byte request does not end in stateless reset token\ngot: %x\nwant suffix: %x", test.reqSize, got, token)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
|
synctest.Test(t, testStatelessResetSuccessfulNewConnectionID)
|
|
}
|
|
func testStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
|
// "[...] Stateless Reset Token field values from [...] NEW_CONNECTION_ID frames [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
|
|
qr := &qlogRecord{}
|
|
tc := newTestConn(t, clientSide, qr.config)
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
// Retire connection ID 0.
|
|
tc.writeFrames(packetType1RTT,
|
|
debugFrameNewConnectionID{
|
|
retirePriorTo: 1,
|
|
seq: 2,
|
|
connID: testPeerConnID(2),
|
|
})
|
|
tc.wantFrame("peer requested we retire conn id 0",
|
|
packetType1RTT, debugFrameRetireConnectionID{
|
|
seq: 0,
|
|
})
|
|
|
|
resetToken := testPeerStatelessResetToken(1) // provided during handshake
|
|
dgram := append(make([]byte, 100), resetToken[:]...)
|
|
tc.endpoint.write(&datagram{
|
|
b: dgram,
|
|
})
|
|
|
|
if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
|
|
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
|
|
}
|
|
tc.wantIdle("closed connection is idle in draining")
|
|
time.Sleep(1 * time.Second) // long enough to exit the draining state
|
|
tc.wantIdle("closed connection is idle after draining")
|
|
|
|
qr.wantEvents(t, jsonEvent{
|
|
"name": "connectivity:connection_closed",
|
|
"data": map[string]any{
|
|
"trigger": "stateless_reset",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestStatelessResetSuccessfulTransportParameter(t *testing.T) {
|
|
synctest.Test(t, testStatelessResetSuccessfulTransportParameter)
|
|
}
|
|
func testStatelessResetSuccessfulTransportParameter(t *testing.T) {
|
|
// "[...] Stateless Reset Token field values from [...]
|
|
// the server's transport parameters [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
|
|
resetToken := testPeerStatelessResetToken(0)
|
|
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
|
p.statelessResetToken = resetToken[:]
|
|
})
|
|
tc.handshake()
|
|
|
|
dgram := append(make([]byte, 100), resetToken[:]...)
|
|
tc.endpoint.write(&datagram{
|
|
b: dgram,
|
|
})
|
|
|
|
if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
|
|
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
|
|
}
|
|
tc.wantIdle("closed connection is idle")
|
|
}
|
|
|
|
func TestStatelessResetSuccessfulPrefix(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix []byte
|
|
size int
|
|
}{{
|
|
name: "short header and fixed bit",
|
|
prefix: []byte{
|
|
headerFormShort | fixedBit,
|
|
},
|
|
size: 100,
|
|
}, {
|
|
// "[...] endpoints MUST treat [long header packets] ending in a
|
|
// valid stateless reset token as a Stateless Reset [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-15
|
|
name: "long header no fixed bit",
|
|
prefix: []byte{
|
|
headerFormLong,
|
|
},
|
|
size: 100,
|
|
}, {
|
|
// "[...] the comparison MUST be performed when the first packet
|
|
// in an incoming datagram [...] cannot be decrypted."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-2
|
|
name: "short header valid DCID",
|
|
prefix: append([]byte{
|
|
headerFormShort | fixedBit,
|
|
}, testLocalConnID(0)...),
|
|
size: 100,
|
|
}, {
|
|
name: "handshake valid DCID",
|
|
prefix: append([]byte{
|
|
headerFormLong | fixedBit | longPacketTypeHandshake,
|
|
}, testLocalConnID(0)...),
|
|
size: 100,
|
|
}, {
|
|
name: "no fixed bit valid DCID",
|
|
prefix: append([]byte{
|
|
0,
|
|
}, testLocalConnID(0)...),
|
|
size: 100,
|
|
}} {
|
|
synctestSubtest(t, test.name, func(t *testing.T) {
|
|
resetToken := testPeerStatelessResetToken(0)
|
|
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
|
p.statelessResetToken = resetToken[:]
|
|
})
|
|
tc.handshake()
|
|
|
|
dgram := test.prefix
|
|
for len(dgram) < test.size-len(resetToken) {
|
|
dgram = append(dgram, byte(len(dgram))) // semi-random junk
|
|
}
|
|
dgram = append(dgram, resetToken[:]...)
|
|
tc.endpoint.write(&datagram{
|
|
b: dgram,
|
|
})
|
|
if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
|
|
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStatelessResetRetiredConnID(t *testing.T) {
|
|
synctest.Test(t, testStatelessResetRetiredConnID)
|
|
}
|
|
func testStatelessResetRetiredConnID(t *testing.T) {
|
|
// "An endpoint MUST NOT check for any stateless reset tokens [...]
|
|
// for connection IDs that have been retired."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-3
|
|
resetToken := testPeerStatelessResetToken(0)
|
|
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
|
p.statelessResetToken = resetToken[:]
|
|
})
|
|
tc.handshake()
|
|
tc.ignoreFrame(frameTypeAck)
|
|
|
|
// We retire connection ID 0.
|
|
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,
|
|
})
|
|
|
|
// Receive a stateless reset for connection ID 0.
|
|
dgram := append(make([]byte, 100), resetToken[:]...)
|
|
tc.endpoint.write(&datagram{
|
|
b: dgram,
|
|
})
|
|
|
|
if err := tc.conn.Wait(canceledContext()); !errors.Is(err, context.Canceled) {
|
|
t.Errorf("conn.Wait() = %v, want connection to be alive", err)
|
|
}
|
|
}
|