github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/http3/conn.go (about)

     1  package http3
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"golang.org/x/exp/slog"
     7  	"net"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/metacubex/quic-go"
    12  	"github.com/metacubex/quic-go/internal/protocol"
    13  	"github.com/metacubex/quic-go/quicvarint"
    14  
    15  	"github.com/quic-go/qpack"
    16  )
    17  
    18  // Connection is an HTTP/3 connection.
    19  // It has all methods from the quic.Connection expect for AcceptStream, AcceptUniStream,
    20  // SendDatagram and ReceiveDatagram.
    21  type Connection interface {
    22  	OpenStream() (quic.Stream, error)
    23  	OpenStreamSync(context.Context) (quic.Stream, error)
    24  	OpenUniStream() (quic.SendStream, error)
    25  	OpenUniStreamSync(context.Context) (quic.SendStream, error)
    26  	LocalAddr() net.Addr
    27  	RemoteAddr() net.Addr
    28  	CloseWithError(quic.ApplicationErrorCode, string) error
    29  	Context() context.Context
    30  	ConnectionState() quic.ConnectionState
    31  
    32  	// ReceivedSettings returns a channel that is closed once the client's SETTINGS frame was received.
    33  	ReceivedSettings() <-chan struct{}
    34  	// Settings returns the settings received on this connection.
    35  	Settings() *Settings
    36  }
    37  
    38  type connection struct {
    39  	quic.Connection
    40  
    41  	perspective protocol.Perspective
    42  	logger      *slog.Logger
    43  
    44  	enableDatagrams bool
    45  
    46  	decoder *qpack.Decoder
    47  
    48  	streamMx sync.Mutex
    49  	streams  map[protocol.StreamID]*datagrammer
    50  
    51  	settings         *Settings
    52  	receivedSettings chan struct{}
    53  }
    54  
    55  func newConnection(
    56  	quicConn quic.Connection,
    57  	enableDatagrams bool,
    58  	perspective protocol.Perspective,
    59  	logger *slog.Logger,
    60  ) *connection {
    61  	c := &connection{
    62  		Connection:       quicConn,
    63  		perspective:      perspective,
    64  		logger:           logger,
    65  		enableDatagrams:  enableDatagrams,
    66  		decoder:          qpack.NewDecoder(func(hf qpack.HeaderField) {}),
    67  		receivedSettings: make(chan struct{}),
    68  		streams:          make(map[protocol.StreamID]*datagrammer),
    69  	}
    70  	return c
    71  }
    72  
    73  func (c *connection) clearStream(id quic.StreamID) {
    74  	c.streamMx.Lock()
    75  	defer c.streamMx.Unlock()
    76  
    77  	delete(c.streams, id)
    78  }
    79  
    80  func (c *connection) openRequestStream(
    81  	ctx context.Context,
    82  	requestWriter *requestWriter,
    83  	reqDone chan<- struct{},
    84  	disableCompression bool,
    85  	maxHeaderBytes uint64,
    86  ) (*requestStream, error) {
    87  	str, err := c.Connection.OpenStreamSync(ctx)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	datagrams := newDatagrammer(func(b []byte) error { return c.sendDatagram(str.StreamID(), b) })
    92  	c.streamMx.Lock()
    93  	c.streams[str.StreamID()] = datagrams
    94  	c.streamMx.Unlock()
    95  	qstr := newStateTrackingStream(str, c, datagrams)
    96  	hstr := newStream(qstr, c, datagrams)
    97  	return newRequestStream(hstr, requestWriter, reqDone, c.decoder, disableCompression, maxHeaderBytes), nil
    98  }
    99  
   100  func (c *connection) acceptStream(ctx context.Context) (quic.Stream, *datagrammer, error) {
   101  	str, err := c.AcceptStream(ctx)
   102  	if err != nil {
   103  		return nil, nil, err
   104  	}
   105  	datagrams := newDatagrammer(func(b []byte) error { return c.sendDatagram(str.StreamID(), b) })
   106  	if c.perspective == protocol.PerspectiveServer {
   107  		strID := str.StreamID()
   108  		c.streamMx.Lock()
   109  		c.streams[strID] = datagrams
   110  		c.streamMx.Unlock()
   111  		str = newStateTrackingStream(str, c, datagrams)
   112  	}
   113  	return str, datagrams, nil
   114  }
   115  
   116  func (c *connection) HandleUnidirectionalStreams(hijack func(StreamType, quic.ConnectionTracingID, quic.ReceiveStream, error) (hijacked bool)) {
   117  	var (
   118  		rcvdControlStr      atomic.Bool
   119  		rcvdQPACKEncoderStr atomic.Bool
   120  		rcvdQPACKDecoderStr atomic.Bool
   121  	)
   122  
   123  	for {
   124  		str, err := c.Connection.AcceptUniStream(context.Background())
   125  		if err != nil {
   126  			if c.logger != nil {
   127  				c.logger.Debug("accepting unidirectional stream failed", "error", err)
   128  			}
   129  			return
   130  		}
   131  
   132  		go func(str quic.ReceiveStream) {
   133  			streamType, err := quicvarint.Read(quicvarint.NewReader(str))
   134  			if err != nil {
   135  				id := c.Connection.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID)
   136  				if hijack != nil && hijack(StreamType(streamType), id, str, err) {
   137  					return
   138  				}
   139  				if c.logger != nil {
   140  					c.logger.Debug("reading stream type on stream failed", "stream ID", str.StreamID(), "error", err)
   141  				}
   142  				return
   143  			}
   144  			// We're only interested in the control stream here.
   145  			switch streamType {
   146  			case streamTypeControlStream:
   147  			case streamTypeQPACKEncoderStream:
   148  				if isFirst := rcvdQPACKEncoderStr.CompareAndSwap(false, true); !isFirst {
   149  					c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK encoder stream")
   150  				}
   151  				// Our QPACK implementation doesn't use the dynamic table yet.
   152  				return
   153  			case streamTypeQPACKDecoderStream:
   154  				if isFirst := rcvdQPACKDecoderStr.CompareAndSwap(false, true); !isFirst {
   155  					c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK decoder stream")
   156  				}
   157  				// Our QPACK implementation doesn't use the dynamic table yet.
   158  				return
   159  			case streamTypePushStream:
   160  				switch c.perspective {
   161  				case protocol.PerspectiveClient:
   162  					// we never increased the Push ID, so we don't expect any push streams
   163  					c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
   164  				case protocol.PerspectiveServer:
   165  					// only the server can push
   166  					c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
   167  				}
   168  				return
   169  			default:
   170  				if hijack != nil {
   171  					if hijack(
   172  						StreamType(streamType),
   173  						c.Connection.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID),
   174  						str,
   175  						nil,
   176  					) {
   177  						return
   178  					}
   179  				}
   180  				str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
   181  				return
   182  			}
   183  			// Only a single control stream is allowed.
   184  			if isFirstControlStr := rcvdControlStr.CompareAndSwap(false, true); !isFirstControlStr {
   185  				c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
   186  				return
   187  			}
   188  			fp := &frameParser{conn: c.Connection, r: str}
   189  			f, err := fp.ParseNext()
   190  			if err != nil {
   191  				c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
   192  				return
   193  			}
   194  			sf, ok := f.(*settingsFrame)
   195  			if !ok {
   196  				c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
   197  				return
   198  			}
   199  			c.settings = &Settings{
   200  				EnableDatagrams:       sf.Datagram,
   201  				EnableExtendedConnect: sf.ExtendedConnect,
   202  				Other:                 sf.Other,
   203  			}
   204  			close(c.receivedSettings)
   205  			if !sf.Datagram {
   206  				return
   207  			}
   208  			// If datagram support was enabled on our side as well as on the server side,
   209  			// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
   210  			// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
   211  			if c.enableDatagrams && !c.Connection.ConnectionState().SupportsDatagrams {
   212  				c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
   213  				return
   214  			}
   215  			go func() {
   216  				if err := c.receiveDatagrams(); err != nil {
   217  					if c.logger != nil {
   218  						c.logger.Debug("receiving datagrams failed", "error", err)
   219  					}
   220  				}
   221  			}()
   222  		}(str)
   223  	}
   224  }
   225  
   226  func (c *connection) sendDatagram(streamID protocol.StreamID, b []byte) error {
   227  	// TODO: this creates a lot of garbage and an additional copy
   228  	data := make([]byte, 0, len(b)+8)
   229  	data = quicvarint.Append(data, uint64(streamID/4))
   230  	data = append(data, b...)
   231  	return c.Connection.SendDatagram(data)
   232  }
   233  
   234  func (c *connection) receiveDatagrams() error {
   235  	for {
   236  		b, err := c.Connection.ReceiveDatagram(context.Background())
   237  		if err != nil {
   238  			return err
   239  		}
   240  		quarterStreamID, n, err := quicvarint.Parse(b)
   241  		if err != nil {
   242  			c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
   243  			return fmt.Errorf("could not read quarter stream id: %w", err)
   244  		}
   245  		if quarterStreamID > maxQuarterStreamID {
   246  			c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
   247  			return fmt.Errorf("invalid quarter stream id: %w", err)
   248  		}
   249  		streamID := protocol.StreamID(4 * quarterStreamID)
   250  		c.streamMx.Lock()
   251  		dg, ok := c.streams[streamID]
   252  		if !ok {
   253  			c.streamMx.Unlock()
   254  			return nil
   255  		}
   256  		c.streamMx.Unlock()
   257  		dg.enqueue(b[n:])
   258  	}
   259  }
   260  
   261  // ReceivedSettings returns a channel that is closed once the peer's SETTINGS frame was received.
   262  func (c *connection) ReceivedSettings() <-chan struct{} { return c.receivedSettings }
   263  
   264  // Settings returns the settings received on this connection.
   265  // It is only valid to call this function after the channel returned by ReceivedSettings was closed.
   266  func (c *connection) Settings() *Settings { return c.settings }