Source file src/vendor/golang.org/x/net/internal/http3/transport.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  	"context"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"sync"
    13  
    14  	"golang.org/x/net/quic"
    15  )
    16  
    17  // A transport is an HTTP/3 transport.
    18  //
    19  // It does not manage a pool of connections,
    20  // and therefore does not implement net/http.RoundTripper.
    21  //
    22  // TODO: Provide a way to register an HTTP/3 transport with a net/http.transport's
    23  // connection pool.
    24  type transport struct {
    25  	// config is the QUIC configuration used for client connections.
    26  	config *quic.Config
    27  
    28  	listenQUIC func(addr string, config *quic.Config) (*quic.Endpoint, error)
    29  
    30  	mu sync.Mutex // Guards fields below.
    31  	// endpoint is the QUIC endpoint used by connections created by the
    32  	// transport. If CloseIdleConnections is called when activeConns is empty,
    33  	// endpoint will be unset. If unset, endpoint will be initialized by any
    34  	// call to dial.
    35  	endpoint      *quic.Endpoint
    36  	activeConns   map[*clientConn]struct{}
    37  	inFlightDials int
    38  }
    39  
    40  // netHTTPTransport implements the net/http.dialClientConner interface,
    41  // allowing our HTTP/3 transport to integrate with net/http.
    42  type netHTTPTransport struct {
    43  	*transport
    44  }
    45  
    46  // RoundTrip is defined since Transport.RegisterProtocol takes in a
    47  // RoundTripper. However, this method will never be used as net/http's
    48  // dialClientConner interface does not have a RoundTrip method and will only
    49  // use DialClientConn to create a new RoundTripper.
    50  func (t netHTTPTransport) RoundTrip(*http.Request) (*http.Response, error) {
    51  	panic("netHTTPTransport.RoundTrip should never be called")
    52  }
    53  
    54  func (t netHTTPTransport) DialClientConn(ctx context.Context, addr string, _ *url.URL, _ func()) (http.RoundTripper, error) {
    55  	return t.transport.dial(ctx, addr)
    56  }
    57  
    58  type TransportOpts struct {
    59  	// ListenQUIC determines how the transport will open a QUIC endpoint.
    60  	// By default, quic.Listen("udp", addr, config) is used.
    61  	// ListenQUIC might be called multiple times.
    62  	ListenQUIC func(addr string, config *quic.Config) (*quic.Endpoint, error)
    63  
    64  	// QUICConfig is the QUIC configuration used by the transport.
    65  	// QUICConfig may be nil and should not be modified after calling
    66  	// RegisterTransport.
    67  	// If QUICConfig.TLSConfig is nil, the TLSConfig of the net/http Transport
    68  	// given to RegisterTransport will be used.
    69  	QUICConfig *quic.Config
    70  }
    71  
    72  // RegisterTransport configures a net/http HTTP/1 Transport to use HTTP/3.
    73  func RegisterTransport(tr *http.Transport, opts TransportOpts) {
    74  	if opts.QUICConfig == nil {
    75  		opts.QUICConfig = &quic.Config{}
    76  	}
    77  	if opts.QUICConfig.TLSConfig == nil {
    78  		opts.QUICConfig.TLSConfig = tr.TLSClientConfig
    79  	}
    80  	if opts.ListenQUIC == nil {
    81  		opts.ListenQUIC = func(addr string, config *quic.Config) (*quic.Endpoint, error) {
    82  			return quic.Listen("udp", addr, config)
    83  		}
    84  	}
    85  	tr3 := &transport{
    86  		// initConfig will clone the tr.TLSClientConfig.
    87  		config:      initConfig(opts.QUICConfig),
    88  		listenQUIC:  opts.ListenQUIC,
    89  		activeConns: make(map[*clientConn]struct{}),
    90  	}
    91  	tr.RegisterProtocol("http/3", netHTTPTransport{tr3})
    92  }
    93  
    94  func (tr *transport) incInFlightDials() {
    95  	tr.mu.Lock()
    96  	defer tr.mu.Unlock()
    97  	tr.inFlightDials++
    98  }
    99  
   100  func (tr *transport) decInFlightDials() {
   101  	tr.mu.Lock()
   102  	defer tr.mu.Unlock()
   103  	tr.inFlightDials--
   104  }
   105  
   106  func (tr *transport) initEndpoint() (err error) {
   107  	tr.mu.Lock()
   108  	defer tr.mu.Unlock()
   109  	// This might cause rare issues on Darwin. Unlike Linux, Darwin kernel
   110  	// seems to have the following behaviors:
   111  	// - After closing a UDP socket, the port that was bound to the socket
   112  	//   might not be immediately usable again.
   113  	// - When doing IPv6 dual-stack binding (e.g., bind to ":0"), it will
   114  	//   happily bind the IPv6 port, even when the IPv4 port is unavailable.
   115  	//
   116  	// When both of these are combined, in practice, it is possible for the
   117  	// following to happen:
   118  	// 1. Transport binds ":0", creating a dual-stack IPv6 UDP socket.
   119  	//    Everything works as expected.
   120  	// 2. At some point, CloseIdleConnections is called and the socket is
   121  	//    closed.
   122  	// 3. Soon after, a new dial is started, and a new dual-stack IPv6 socket
   123  	//    is coincidentally assigned the same port as the previous socket.
   124  	// 4. If the IPv4 port is still unavailable, Darwin's permissive binding
   125  	//    behavior will cause us to have a socket that silently is unable to
   126  	//    receive packets on its IPv4 address.
   127  	// 5. If the dial target is an IPv4 address, transport will be able to send
   128  	//    packets to the target, but will be unable to receive its reply.
   129  	//
   130  	// TransportOpts.ListenQUIC can technically be configured to avoid
   131  	// dual-stack binding to avoid this issue, and high socket churn is
   132  	// probably uncommon for regular use cases. However, finding a workaround
   133  	// for this eventually would be ideal.
   134  	if tr.endpoint == nil {
   135  		tr.endpoint, err = tr.listenQUIC(":0", tr.config)
   136  	}
   137  	return err
   138  }
   139  
   140  // dial creates a new HTTP/3 client connection.
   141  func (tr *transport) dial(ctx context.Context, target string) (*clientConn, error) {
   142  	tr.incInFlightDials()
   143  	defer tr.decInFlightDials()
   144  
   145  	if err := tr.initEndpoint(); err != nil {
   146  		return nil, err
   147  	}
   148  	qconn, err := tr.endpoint.Dial(ctx, "udp", target, tr.config)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	return tr.newClientConn(ctx, qconn)
   153  }
   154  
   155  // CloseIdleConnections is called by net/http.Transport.CloseIdleConnections
   156  // after all existing idle connections are closed using http3.clientConn.Close.
   157  //
   158  // When the transport has no active connections anymore, calling this method
   159  // will make the transport clean up any shared resources that are no longer
   160  // required, such as its QUIC endpoint.
   161  func (tr *transport) CloseIdleConnections() {
   162  	tr.mu.Lock()
   163  	defer tr.mu.Unlock()
   164  	if tr.endpoint == nil || len(tr.activeConns) > 0 || tr.inFlightDials > 0 {
   165  		return
   166  	}
   167  	tr.endpoint.Close(canceledCtx)
   168  	tr.endpoint = nil
   169  }
   170  
   171  // A clientConn is a client HTTP/3 connection.
   172  //
   173  // Multiple goroutines may invoke methods on a clientConn simultaneously.
   174  type clientConn struct {
   175  	qconn *quic.Conn
   176  	genericConn
   177  
   178  	enc qpackEncoder
   179  	dec qpackDecoder
   180  }
   181  
   182  func (tr *transport) registerConn(cc *clientConn) {
   183  	tr.mu.Lock()
   184  	defer tr.mu.Unlock()
   185  	tr.activeConns[cc] = struct{}{}
   186  }
   187  
   188  func (tr *transport) unregisterConn(cc *clientConn) {
   189  	tr.mu.Lock()
   190  	defer tr.mu.Unlock()
   191  	delete(tr.activeConns, cc)
   192  }
   193  
   194  func (tr *transport) newClientConn(ctx context.Context, qconn *quic.Conn) (*clientConn, error) {
   195  	cc := &clientConn{
   196  		qconn: qconn,
   197  	}
   198  	tr.registerConn(cc)
   199  	cc.enc.init()
   200  
   201  	// Create control stream and send SETTINGS frame.
   202  	controlStream, err := newConnStream(ctx, cc.qconn, streamTypeControl)
   203  	if err != nil {
   204  		tr.unregisterConn(cc)
   205  		return nil, fmt.Errorf("http3: cannot create control stream: %v", err)
   206  	}
   207  	controlStream.writeSettings()
   208  	controlStream.Flush()
   209  
   210  	go func() {
   211  		cc.acceptStreams(qconn, cc)
   212  		tr.unregisterConn(cc)
   213  	}()
   214  	return cc, nil
   215  }
   216  
   217  // TODO: implement the rest of net/http.ClientConn methods beyond Close.
   218  func (cc *clientConn) Close() error {
   219  	// We need to use Close rather than Abort on the QUIC connection.
   220  	// Otherwise, when a net/http.Transport.CloseIdleConnections is called, it
   221  	// might call the http3.transport.CloseIdleConnections prior to all idle
   222  	// connections being fully closed; this would make it unable to close its
   223  	// QUIC endpoint, making http3.transport.CloseIdleConnections a no-op
   224  	// unintentionally.
   225  	return cc.qconn.Close()
   226  }
   227  
   228  func (cc *clientConn) Err() error {
   229  	return nil
   230  }
   231  
   232  func (cc *clientConn) Reserve() error {
   233  	return nil
   234  }
   235  
   236  func (cc *clientConn) Release() {
   237  }
   238  
   239  func (cc *clientConn) Available() int {
   240  	return 0
   241  }
   242  
   243  func (cc *clientConn) InFlight() int {
   244  	return 0
   245  }
   246  
   247  func (cc *clientConn) handleControlStream(st *stream) error {
   248  	// "A SETTINGS frame MUST be sent as the first frame of each control stream [...]"
   249  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2
   250  	if err := st.readSettings(func(settingsType, settingsValue int64) error {
   251  		switch settingsType {
   252  		case settingsMaxFieldSectionSize:
   253  			_ = settingsValue // TODO
   254  		case settingsQPACKMaxTableCapacity:
   255  			_ = settingsValue // TODO
   256  		case settingsQPACKBlockedStreams:
   257  			_ = settingsValue // TODO
   258  		default:
   259  			// Unknown settings types are ignored.
   260  		}
   261  		return nil
   262  	}); err != nil {
   263  		return err
   264  	}
   265  
   266  	for {
   267  		ftype, err := st.readFrameHeader()
   268  		if err != nil {
   269  			return err
   270  		}
   271  		switch ftype {
   272  		case frameTypeCancelPush:
   273  			// "If a CANCEL_PUSH frame is received that references a push ID
   274  			// greater than currently allowed on the connection,
   275  			// this MUST be treated as a connection error of type H3_ID_ERROR."
   276  			// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-7
   277  			return &connectionError{
   278  				code:    errH3IDError,
   279  				message: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent",
   280  			}
   281  		case frameTypeGoaway:
   282  			// TODO: Wait for requests to complete before closing connection.
   283  			return errH3NoError
   284  		default:
   285  			// Unknown frames are ignored.
   286  			if err := st.discardUnknownFrame(ftype); err != nil {
   287  				return err
   288  			}
   289  		}
   290  	}
   291  }
   292  
   293  func (cc *clientConn) handleEncoderStream(*stream) error {
   294  	// TODO
   295  	return nil
   296  }
   297  
   298  func (cc *clientConn) handleDecoderStream(*stream) error {
   299  	// TODO
   300  	return nil
   301  }
   302  
   303  func (cc *clientConn) handlePushStream(*stream) error {
   304  	// "A client MUST treat receipt of a push stream as a connection error
   305  	// of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent [...]"
   306  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.6-3
   307  	return &connectionError{
   308  		code:    errH3IDError,
   309  		message: "push stream created when no MAX_PUSH_ID has been sent",
   310  	}
   311  }
   312  
   313  func (cc *clientConn) handleRequestStream(st *stream) error {
   314  	// "Clients MUST treat receipt of a server-initiated bidirectional
   315  	// stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
   316  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
   317  	return &connectionError{
   318  		code:    errH3StreamCreationError,
   319  		message: "server created bidirectional stream",
   320  	}
   321  }
   322  
   323  // abort closes the connection with an error.
   324  func (cc *clientConn) abort(err error) {
   325  	if e, ok := err.(*connectionError); ok {
   326  		cc.qconn.Abort(&quic.ApplicationError{
   327  			Code:   uint64(e.code),
   328  			Reason: e.message,
   329  		})
   330  	} else {
   331  		cc.qconn.Abort(err)
   332  	}
   333  }
   334  

View as plain text