Source file src/net/http/internal/http3/quic_test.go

     1  // Copyright 2025 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  package http3
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/tls"
    11  	"net"
    12  	"net/netip"
    13  	"slices"
    14  	"sync"
    15  	"testing"
    16  	"time"
    17  
    18  	"internal/gate"
    19  	"net/http/internal/testcert"
    20  	"golang.org/x/net/quic"
    21  )
    22  
    23  // TestNewQUICConfig verifies that newQUICConfig always produces a config with
    24  // an "h3" ALPN, without modifying the caller's tls.Config.
    25  func TestNewQUICConfig(t *testing.T) {
    26  	for _, test := range []struct {
    27  		name      string
    28  		tlsConfig *tls.Config
    29  	}{{
    30  		// net/http.Transport.TLSClientConfig is nil by default.
    31  		name:      "nil",
    32  		tlsConfig: nil,
    33  	}, {
    34  		name:      "no NextProtos",
    35  		tlsConfig: &tls.Config{ServerName: "example.tld"},
    36  	}, {
    37  		name:      "other NextProtos",
    38  		tlsConfig: &tls.Config{NextProtos: []string{"http/1.1"}},
    39  	}, {
    40  		name:      "h3 already set",
    41  		tlsConfig: &tls.Config{NextProtos: []string{"h3"}},
    42  	}} {
    43  		t.Run(test.name, func(t *testing.T) {
    44  			var origNextProtos []string
    45  			if test.tlsConfig != nil {
    46  				origNextProtos = slices.Clone(test.tlsConfig.NextProtos)
    47  			}
    48  
    49  			config := newQUICConfig(nil, test.tlsConfig)
    50  			if config == nil {
    51  				t.Fatal("newQUICConfig returned nil config")
    52  			}
    53  			if config.TLSConfig == nil {
    54  				t.Fatal("newQUICConfig returned config with nil TLSConfig")
    55  			}
    56  			if got := config.TLSConfig.NextProtos; !slices.Equal(got, []string{"h3"}) {
    57  				t.Errorf("TLSConfig.NextProtos = %q, want [h3]", got)
    58  			}
    59  			if test.tlsConfig != nil {
    60  				if got := test.tlsConfig.NextProtos; !slices.Equal(got, origNextProtos) {
    61  					t.Errorf("newQUICConfig modified the caller's TLSConfig.NextProtos: got %q, want %q", got, origNextProtos)
    62  				}
    63  			}
    64  		})
    65  	}
    66  }
    67  
    68  // newLocalQUICEndpoint returns a QUIC Endpoint listening on localhost.
    69  func newLocalQUICEndpoint(t *testing.T) *quic.Endpoint {
    70  	t.Helper()
    71  	conf := &quic.Config{
    72  		TLSConfig: testTLSConfig,
    73  	}
    74  	e, err := quic.Listen("udp", "127.0.0.1:0", conf)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	t.Cleanup(func() {
    79  		e.Close(context.Background())
    80  	})
    81  	return e
    82  }
    83  
    84  // newQUICEndpointPair returns two QUIC endpoints on the same test network.
    85  func newQUICEndpointPair(t testing.TB) (e1, e2 *quic.Endpoint) {
    86  	config := &quic.Config{
    87  		TLSConfig: testTLSConfig,
    88  	}
    89  	tn := &testNet{}
    90  	e1 = tn.newQUICEndpoint(t, config)
    91  	e2 = tn.newQUICEndpoint(t, config)
    92  	return e1, e2
    93  }
    94  
    95  // newQUICStreamPair returns the two sides of a bidirectional QUIC stream.
    96  func newQUICStreamPair(t testing.TB) (s1, s2 *quic.Stream) {
    97  	t.Helper()
    98  	config := &quic.Config{
    99  		TLSConfig: testTLSConfig,
   100  	}
   101  	e1, e2 := newQUICEndpointPair(t)
   102  	c1, err := e1.Dial(context.Background(), "udp", e2.LocalAddr().String(), config)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	c2, err := e2.Accept(context.Background())
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	s1, err = c1.NewStream(context.Background())
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	s1.Flush()
   115  	s2, err = c2.AcceptStream(context.Background())
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  	return s1, s2
   120  }
   121  
   122  // A testNet is a fake network of net.PacketConns.
   123  type testNet struct {
   124  	mu    sync.Mutex
   125  	conns map[netip.AddrPort]*testPacketConn
   126  }
   127  
   128  // newPacketConn returns a new PacketConn with a unique source address.
   129  func (tn *testNet) newPacketConn() *testPacketConn {
   130  	tn.mu.Lock()
   131  	defer tn.mu.Unlock()
   132  	if tn.conns == nil {
   133  		tn.conns = make(map[netip.AddrPort]*testPacketConn)
   134  	}
   135  	localAddr := netip.AddrPortFrom(
   136  		netip.AddrFrom4([4]byte{
   137  			127, 0, 0, byte(len(tn.conns)),
   138  		}),
   139  		443)
   140  	tc := &testPacketConn{
   141  		tn:        tn,
   142  		localAddr: localAddr,
   143  		gate:      gate.New(false),
   144  	}
   145  	tn.conns[localAddr] = tc
   146  	return tc
   147  }
   148  
   149  func (tn *testNet) newQUICEndpoint(t testing.TB, config *quic.Config) *quic.Endpoint {
   150  	t.Helper()
   151  	pc := tn.newPacketConn()
   152  	e, err := quic.NewEndpoint(pc, config)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	t.Cleanup(func() {
   157  		e.Close(t.Context())
   158  	})
   159  	return e
   160  }
   161  
   162  // connForAddr returns the conn with the given source address.
   163  func (tn *testNet) connForAddr(srcAddr netip.AddrPort) *testPacketConn {
   164  	tn.mu.Lock()
   165  	defer tn.mu.Unlock()
   166  	return tn.conns[srcAddr]
   167  }
   168  
   169  // A testPacketConn is a net.PacketConn on a testNet fake network.
   170  type testPacketConn struct {
   171  	tn        *testNet
   172  	localAddr netip.AddrPort
   173  
   174  	gate   gate.Gate
   175  	queue  []testPacket
   176  	closed bool
   177  }
   178  
   179  type testPacket struct {
   180  	b   []byte
   181  	src netip.AddrPort
   182  }
   183  
   184  func (tc *testPacketConn) unlock() {
   185  	tc.gate.Unlock(tc.closed || len(tc.queue) > 0)
   186  }
   187  
   188  func (tc *testPacketConn) ReadFrom(p []byte) (n int, srcAddr net.Addr, err error) {
   189  	if err := tc.gate.WaitAndLock(context.Background()); err != nil {
   190  		return 0, nil, err
   191  	}
   192  	defer tc.unlock()
   193  	if tc.closed {
   194  		return 0, nil, net.ErrClosed
   195  	}
   196  	n = copy(p, tc.queue[0].b)
   197  	srcAddr = net.UDPAddrFromAddrPort(tc.queue[0].src)
   198  	tc.queue = tc.queue[1:]
   199  	return n, srcAddr, nil
   200  }
   201  
   202  func (tc *testPacketConn) WriteTo(p []byte, dstAddr net.Addr) (n int, err error) {
   203  	tc.gate.Lock()
   204  	closed := tc.closed
   205  	tc.unlock()
   206  	if closed {
   207  		return 0, net.ErrClosed
   208  	}
   209  
   210  	ap, err := addrPortFromAddr(dstAddr)
   211  	if err != nil {
   212  		return 0, err
   213  	}
   214  	dst := tc.tn.connForAddr(ap)
   215  	if dst == nil {
   216  		return len(p), nil // sent into the void
   217  	}
   218  	dst.gate.Lock()
   219  	defer dst.unlock()
   220  	dst.queue = append(dst.queue, testPacket{
   221  		b:   bytes.Clone(p),
   222  		src: tc.localAddr,
   223  	})
   224  	return len(p), nil
   225  }
   226  
   227  func (tc *testPacketConn) Close() error {
   228  	tc.tn.mu.Lock()
   229  	tc.tn.conns[tc.localAddr] = nil
   230  	tc.tn.mu.Unlock()
   231  
   232  	tc.gate.Lock()
   233  	defer tc.unlock()
   234  	tc.closed = true
   235  	tc.queue = nil
   236  	return nil
   237  }
   238  
   239  func (tc *testPacketConn) LocalAddr() net.Addr {
   240  	return net.UDPAddrFromAddrPort(tc.localAddr)
   241  }
   242  
   243  func (tc *testPacketConn) SetDeadline(time.Time) error      { panic("unimplemented") }
   244  func (tc *testPacketConn) SetReadDeadline(time.Time) error  { panic("unimplemented") }
   245  func (tc *testPacketConn) SetWriteDeadline(time.Time) error { panic("unimplemented") }
   246  
   247  func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
   248  	switch a := addr.(type) {
   249  	case *net.UDPAddr:
   250  		return a.AddrPort(), nil
   251  	}
   252  	return netip.ParseAddrPort(addr.String())
   253  }
   254  
   255  var testTLSConfig = &tls.Config{
   256  	InsecureSkipVerify: true,
   257  	CipherSuites: []uint16{
   258  		tls.TLS_AES_128_GCM_SHA256,
   259  		tls.TLS_AES_256_GCM_SHA384,
   260  		tls.TLS_CHACHA20_POLY1305_SHA256,
   261  	},
   262  	MinVersion:   tls.VersionTLS13,
   263  	Certificates: []tls.Certificate{testCert},
   264  	NextProtos:   []string{"h3"},
   265  }
   266  
   267  var testCert = func() tls.Certificate {
   268  	cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
   269  	if err != nil {
   270  		panic(err)
   271  	}
   272  	return cert
   273  }()
   274  

View as plain text