golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/stateless_reset_test.go (about)

     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 (
    10  	"bytes"
    11  	"context"
    12  	"crypto/rand"
    13  	"crypto/tls"
    14  	"errors"
    15  	"net/netip"
    16  	"testing"
    17  	"time"
    18  )
    19  
    20  func TestStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
    21  	// "[The stateless_reset_token] transport parameter MUST NOT be sent by a client [...]"
    22  	// https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.6.1
    23  	resetToken := testPeerStatelessResetToken(0)
    24  	tc := newTestConn(t, serverSide, func(p *transportParameters) {
    25  		p.statelessResetToken = resetToken[:]
    26  	})
    27  	tc.writeFrames(packetTypeInitial,
    28  		debugFrameCrypto{
    29  			data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
    30  		})
    31  	tc.wantFrame("client provided stateless_reset_token transport parameter",
    32  		packetTypeInitial, debugFrameConnectionCloseTransport{
    33  			code: errTransportParameter,
    34  		})
    35  }
    36  
    37  var testStatelessResetKey = func() (key [32]byte) {
    38  	if _, err := rand.Read(key[:]); err != nil {
    39  		panic(err)
    40  	}
    41  	return key
    42  }()
    43  
    44  func testStatelessResetToken(cid []byte) statelessResetToken {
    45  	var gen statelessResetTokenGenerator
    46  	gen.init(testStatelessResetKey)
    47  	return gen.tokenForConnID(cid)
    48  }
    49  
    50  func testLocalStatelessResetToken(seq int64) statelessResetToken {
    51  	return testStatelessResetToken(testLocalConnID(seq))
    52  }
    53  
    54  func newDatagramForReset(cid []byte, size int, addr netip.AddrPort) *datagram {
    55  	dgram := append([]byte{headerFormShort | fixedBit}, cid...)
    56  	for len(dgram) < size {
    57  		dgram = append(dgram, byte(len(dgram))) // semi-random junk
    58  	}
    59  	return &datagram{
    60  		b:        dgram,
    61  		peerAddr: addr,
    62  	}
    63  }
    64  
    65  func TestStatelessResetSentSizes(t *testing.T) {
    66  	config := &Config{
    67  		TLSConfig:         newTestTLSConfig(serverSide),
    68  		StatelessResetKey: testStatelessResetKey,
    69  	}
    70  	addr := netip.MustParseAddr("127.0.0.1")
    71  	te := newTestEndpoint(t, config)
    72  	for i, test := range []struct {
    73  		reqSize  int
    74  		wantSize int
    75  	}{{
    76  		// Datagrams larger than 42 bytes result in a 42-byte stateless reset.
    77  		// This isn't specifically mandated by RFC 9000, but is implied.
    78  		// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-11
    79  		reqSize:  1200,
    80  		wantSize: 42,
    81  	}, {
    82  		// "An endpoint that sends a Stateless Reset in response to a packet
    83  		// that is 43 bytes or shorter SHOULD send a Stateless Reset that is
    84  		// one byte shorter than the packet it responds to."
    85  		// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-11
    86  		reqSize:  43,
    87  		wantSize: 42,
    88  	}, {
    89  		reqSize:  42,
    90  		wantSize: 41,
    91  	}, {
    92  		// We should send a stateless reset in response to the smallest possible
    93  		// valid datagram the peer can send us.
    94  		// The smallest packet is 1-RTT:
    95  		// header byte, conn id, packet num, payload, AEAD.
    96  		reqSize:  1 + connIDLen + 1 + 1 + 16,
    97  		wantSize: 1 + connIDLen + 1 + 1 + 16 - 1,
    98  	}, {
    99  		// The smallest possible stateless reset datagram is 21 bytes.
   100  		// Since our response must be smaller than the incoming datagram,
   101  		// we must not respond to a 21 byte or smaller packet.
   102  		reqSize:  21,
   103  		wantSize: 0,
   104  	}} {
   105  		cid := testLocalConnID(int64(i))
   106  		token := testStatelessResetToken(cid)
   107  		addrport := netip.AddrPortFrom(addr, uint16(8000+i))
   108  		te.write(newDatagramForReset(cid, test.reqSize, addrport))
   109  
   110  		got := te.read()
   111  		if len(got) != test.wantSize {
   112  			t.Errorf("got %v-byte response to %v-byte req, want %v",
   113  				len(got), test.reqSize, test.wantSize)
   114  		}
   115  		if len(got) == 0 {
   116  			continue
   117  		}
   118  		// "Endpoints MUST send Stateless Resets formatted as
   119  		// a packet with a short header."
   120  		// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-15
   121  		if isLongHeader(got[0]) {
   122  			t.Errorf("response to %v-byte request is not a short-header packet\ngot: %x", test.reqSize, got)
   123  		}
   124  		if !bytes.HasSuffix(got, token[:]) {
   125  			t.Errorf("response to %v-byte request does not end in stateless reset token\ngot: %x\nwant suffix: %x", test.reqSize, got, token)
   126  		}
   127  	}
   128  }
   129  
   130  func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
   131  	// "[...] Stateless Reset Token field values from [...] NEW_CONNECTION_ID frames [...]"
   132  	// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
   133  	qr := &qlogRecord{}
   134  	tc := newTestConn(t, clientSide, qr.config)
   135  	tc.handshake()
   136  	tc.ignoreFrame(frameTypeAck)
   137  
   138  	// Retire connection ID 0.
   139  	tc.writeFrames(packetType1RTT,
   140  		debugFrameNewConnectionID{
   141  			retirePriorTo: 1,
   142  			seq:           2,
   143  			connID:        testPeerConnID(2),
   144  		})
   145  	tc.wantFrame("peer requested we retire conn id 0",
   146  		packetType1RTT, debugFrameRetireConnectionID{
   147  			seq: 0,
   148  		})
   149  
   150  	resetToken := testPeerStatelessResetToken(1) // provided during handshake
   151  	dgram := append(make([]byte, 100), resetToken[:]...)
   152  	tc.endpoint.write(&datagram{
   153  		b: dgram,
   154  	})
   155  
   156  	if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
   157  		t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
   158  	}
   159  	tc.wantIdle("closed connection is idle in draining")
   160  	tc.advance(1 * time.Second) // long enough to exit the draining state
   161  	tc.wantIdle("closed connection is idle after draining")
   162  
   163  	qr.wantEvents(t, jsonEvent{
   164  		"name": "connectivity:connection_closed",
   165  		"data": map[string]any{
   166  			"trigger": "stateless_reset",
   167  		},
   168  	})
   169  }
   170  
   171  func TestStatelessResetSuccessfulTransportParameter(t *testing.T) {
   172  	// "[...] Stateless Reset Token field values from [...]
   173  	// the server's transport parameters [...]"
   174  	// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
   175  	resetToken := testPeerStatelessResetToken(0)
   176  	tc := newTestConn(t, clientSide, func(p *transportParameters) {
   177  		p.statelessResetToken = resetToken[:]
   178  	})
   179  	tc.handshake()
   180  
   181  	dgram := append(make([]byte, 100), resetToken[:]...)
   182  	tc.endpoint.write(&datagram{
   183  		b: dgram,
   184  	})
   185  
   186  	if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
   187  		t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
   188  	}
   189  	tc.wantIdle("closed connection is idle")
   190  }
   191  
   192  func TestStatelessResetSuccessfulPrefix(t *testing.T) {
   193  	for _, test := range []struct {
   194  		name   string
   195  		prefix []byte
   196  		size   int
   197  	}{{
   198  		name: "short header and fixed bit",
   199  		prefix: []byte{
   200  			headerFormShort | fixedBit,
   201  		},
   202  		size: 100,
   203  	}, {
   204  		// "[...] endpoints MUST treat [long header packets] ending in a
   205  		// valid stateless reset token as a Stateless Reset [...]"
   206  		// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-15
   207  		name: "long header no fixed bit",
   208  		prefix: []byte{
   209  			headerFormLong,
   210  		},
   211  		size: 100,
   212  	}, {
   213  		// "[...] the comparison MUST be performed when the first packet
   214  		// in an incoming datagram [...] cannot be decrypted."
   215  		// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-2
   216  		name: "short header valid DCID",
   217  		prefix: append([]byte{
   218  			headerFormShort | fixedBit,
   219  		}, testLocalConnID(0)...),
   220  		size: 100,
   221  	}, {
   222  		name: "handshake valid DCID",
   223  		prefix: append([]byte{
   224  			headerFormLong | fixedBit | longPacketTypeHandshake,
   225  		}, testLocalConnID(0)...),
   226  		size: 100,
   227  	}, {
   228  		name: "no fixed bit valid DCID",
   229  		prefix: append([]byte{
   230  			0,
   231  		}, testLocalConnID(0)...),
   232  		size: 100,
   233  	}} {
   234  		t.Run(test.name, func(t *testing.T) {
   235  			resetToken := testPeerStatelessResetToken(0)
   236  			tc := newTestConn(t, clientSide, func(p *transportParameters) {
   237  				p.statelessResetToken = resetToken[:]
   238  			})
   239  			tc.handshake()
   240  
   241  			dgram := test.prefix
   242  			for len(dgram) < test.size-len(resetToken) {
   243  				dgram = append(dgram, byte(len(dgram))) // semi-random junk
   244  			}
   245  			dgram = append(dgram, resetToken[:]...)
   246  			tc.endpoint.write(&datagram{
   247  				b: dgram,
   248  			})
   249  			if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
   250  				t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
   251  			}
   252  		})
   253  	}
   254  }
   255  
   256  func TestStatelessResetRetiredConnID(t *testing.T) {
   257  	// "An endpoint MUST NOT check for any stateless reset tokens [...]
   258  	// for connection IDs that have been retired."
   259  	// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-3
   260  	resetToken := testPeerStatelessResetToken(0)
   261  	tc := newTestConn(t, clientSide, func(p *transportParameters) {
   262  		p.statelessResetToken = resetToken[:]
   263  	})
   264  	tc.handshake()
   265  	tc.ignoreFrame(frameTypeAck)
   266  
   267  	// We retire connection ID 0.
   268  	tc.writeFrames(packetType1RTT,
   269  		debugFrameNewConnectionID{
   270  			seq:           2,
   271  			retirePriorTo: 1,
   272  			connID:        testPeerConnID(2),
   273  		})
   274  	tc.wantFrame("peer asked for conn id 0 to be retired",
   275  		packetType1RTT, debugFrameRetireConnectionID{
   276  			seq: 0,
   277  		})
   278  
   279  	// Receive a stateless reset for connection ID 0.
   280  	dgram := append(make([]byte, 100), resetToken[:]...)
   281  	tc.endpoint.write(&datagram{
   282  		b: dgram,
   283  	})
   284  
   285  	if err := tc.conn.Wait(canceledContext()); !errors.Is(err, context.Canceled) {
   286  		t.Errorf("conn.Wait() = %v, want connection to be alive", err)
   287  	}
   288  }