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 }