mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2024-09-11 21:17:40 +03:00
Update quic-go
This commit is contained in:
parent
f8ce22d9b9
commit
34a1f2ebf5
2
go.mod
2
go.mod
@ -20,7 +20,7 @@ require (
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/opencoff/go-sieve v0.2.1
|
||||
github.com/powerman/check v1.7.0
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/quic-go/quic-go v0.43.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
|
4
go.sum
4
go.sum
@ -73,8 +73,8 @@ github.com/powerman/deepequal v0.1.0 h1:sVwtyTsBuYIvdbLR1O2wzRY63YgPqdGZmk/o80l+
|
||||
github.com/powerman/deepequal v0.1.0/go.mod h1:3k7aG/slufBhUANdN67o/UPg8i5YaiJ6FmibWX0cn04=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/quic-go/quic-go v0.43.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g=
|
||||
github.com/quic-go/quic-go v0.43.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
|
10
vendor/github.com/quic-go/quic-go/.golangci.yml
generated
vendored
10
vendor/github.com/quic-go/quic-go/.golangci.yml
generated
vendored
@ -5,11 +5,21 @@ linters-settings:
|
||||
misspell:
|
||||
ignore-words:
|
||||
- ect
|
||||
depguard:
|
||||
rules:
|
||||
quicvarint:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/github.com/quic-go/quic-go/quicvarint/*"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- depguard
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- goimports
|
||||
|
205
vendor/github.com/quic-go/quic-go/README.md
generated
vendored
205
vendor/github.com/quic-go/quic-go/README.md
generated
vendored
@ -2,11 +2,12 @@
|
||||
|
||||
<img src="docs/quic.png" width=303 height=124>
|
||||
|
||||
[![Documentation](https://img.shields.io/badge/docs-quic--go.net-red?style=flat)](https://quic-go.net/docs/)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go)](https://pkg.go.dev/github.com/quic-go/quic-go)
|
||||
[![Code Coverage](https://img.shields.io/codecov/c/github/quic-go/quic-go/master.svg?style=flat-square)](https://codecov.io/gh/quic-go/quic-go/)
|
||||
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go)
|
||||
|
||||
quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go. It has support for HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
|
||||
quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go. It has support for HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) and HTTP Datagrams ([RFC 9297](https://datatracker.ietf.org/doc/html/rfc9297)).
|
||||
|
||||
In addition to these base RFCs, it also implements the following RFCs:
|
||||
* Unreliable Datagram Extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221))
|
||||
@ -16,207 +17,7 @@ In addition to these base RFCs, it also implements the following RFCs:
|
||||
|
||||
Support for WebTransport over HTTP/3 ([draft-ietf-webtrans-http3](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/)) is implemented in [webtransport-go](https://github.com/quic-go/webtransport-go).
|
||||
|
||||
## Using QUIC
|
||||
|
||||
### Running a Server
|
||||
|
||||
The central entry point is the `quic.Transport`. A transport manages QUIC connections running on a single UDP socket. Since QUIC uses Connection IDs, it can demultiplex a listener (accepting incoming connections) and an arbitrary number of outgoing QUIC connections on the same UDP socket.
|
||||
|
||||
```go
|
||||
udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 1234})
|
||||
// ... error handling
|
||||
tr := quic.Transport{
|
||||
Conn: udpConn,
|
||||
}
|
||||
ln, err := tr.Listen(tlsConf, quicConf)
|
||||
// ... error handling
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
// ... error handling
|
||||
// handle the connection, usually in a new Go routine
|
||||
}
|
||||
}()
|
||||
```
|
||||
|
||||
The listener `ln` can now be used to accept incoming QUIC connections by (repeatedly) calling the `Accept` method (see below for more information on the `quic.Connection`).
|
||||
|
||||
As a shortcut, `quic.Listen` and `quic.ListenAddr` can be used without explicitly initializing a `quic.Transport`:
|
||||
|
||||
```
|
||||
ln, err := quic.Listen(udpConn, tlsConf, quicConf)
|
||||
```
|
||||
|
||||
When using the shortcut, it's not possible to reuse the same UDP socket for outgoing connections.
|
||||
|
||||
### Running a Client
|
||||
|
||||
As mentioned above, multiple outgoing connections can share a single UDP socket, since QUIC uses Connection IDs to demultiplex connections.
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
|
||||
defer cancel()
|
||||
conn, err := tr.Dial(ctx, <server address>, <tls.Config>, <quic.Config>)
|
||||
// ... error handling
|
||||
```
|
||||
|
||||
As a shortcut, `quic.Dial` and `quic.DialAddr` can be used without explictly initializing a `quic.Transport`:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
|
||||
defer cancel()
|
||||
conn, err := quic.Dial(ctx, conn, <server address>, <tls.Config>, <quic.Config>)
|
||||
```
|
||||
|
||||
Just as we saw before when used a similar shortcut to run a server, it's also not possible to reuse the same UDP socket for other outgoing connections, or to listen for incoming connections.
|
||||
|
||||
### Using a QUIC Connection
|
||||
|
||||
#### Accepting Streams
|
||||
|
||||
QUIC is a stream-multiplexed transport. A `quic.Connection` fundamentally differs from the `net.Conn` and the `net.PacketConn` interface defined in the standard library. Data is sent and received on (unidirectional and bidirectional) streams (and, if supported, in [datagrams](#quic-datagrams)), not on the connection itself. The stream state machine is described in detail in [Section 3 of RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000#section-3).
|
||||
|
||||
Note: A unidirectional stream is a stream that the initiator can only write to (`quic.SendStream`), and the receiver can only read from (`quic.ReceiveStream`). A bidirectional stream (`quic.Stream`) allows reading from and writing to for both sides.
|
||||
|
||||
On the receiver side, streams are accepted using the `AcceptStream` (for bidirectional) and `AcceptUniStream` functions. For most user cases, it makes sense to call these functions in a loop:
|
||||
|
||||
```go
|
||||
for {
|
||||
str, err := conn.AcceptStream(context.Background()) // for bidirectional streams
|
||||
// ... error handling
|
||||
// handle the stream, usually in a new Go routine
|
||||
}
|
||||
```
|
||||
|
||||
These functions return an error when the underlying QUIC connection is closed.
|
||||
|
||||
#### Opening Streams
|
||||
|
||||
There are two slightly different ways to open streams, one synchronous and one (potentially) asynchronous. This API is necessary since the receiver grants us a certain number of streams that we're allowed to open. It may grant us additional streams later on (typically when existing streams are closed), but it means that at the time we want to open a new stream, we might not be able to do so.
|
||||
|
||||
Using the synchronous method `OpenStreamSync` for bidirectional streams, and `OpenUniStreamSync` for unidirectional streams, an application can block until the peer allows opening additional streams. In case that we're allowed to open a new stream, these methods return right away:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
str, err := conn.OpenStreamSync(ctx) // wait up to 5s to open a new bidirectional stream
|
||||
```
|
||||
|
||||
The asynchronous version never blocks. If it's currently not possible to open a new stream, it returns a `net.Error` timeout error:
|
||||
|
||||
```go
|
||||
str, err := conn.OpenStream()
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
// It's currently not possible to open another stream,
|
||||
// but it might be possible later, once the peer allowed us to do so.
|
||||
}
|
||||
```
|
||||
|
||||
These functions return an error when the underlying QUIC connection is closed.
|
||||
|
||||
#### Using Streams
|
||||
|
||||
Using QUIC streams is pretty straightforward. The `quic.ReceiveStream` implements the `io.Reader` interface, and the `quic.SendStream` implements the `io.Writer` interface. A bidirectional stream (`quic.Stream`) implements both these interfaces. Conceptually, a bidirectional stream can be thought of as the composition of two unidirectional streams in opposite directions.
|
||||
|
||||
Calling `Close` on a `quic.SendStream` or a `quic.Stream` closes the send side of the stream. On the receiver side, this will be surfaced as an `io.EOF` returned from the `io.Reader` once all data has been consumed. Note that for bidirectional streams, `Close` _only_ closes the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.
|
||||
|
||||
In case the application wishes to abort sending on a `quic.SendStream` or a `quic.Stream` , it can reset the send side by calling `CancelWrite` with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a `quic.StreamError` containing that error code on the `io.Reader`. Note that for bidirectional streams, `CancelWrite` _only_ resets the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.
|
||||
|
||||
Conversely, in case the application wishes to abort receiving from a `quic.ReceiveStream` or a `quic.Stream`, it can ask the sender to abort data transmission by calling `CancelRead` with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a `quic.StreamError` containing that error code on the `io.Writer`. Note that for bidirectional streams, `CancelWrite` _only_ resets the receive side of the stream. It is still possible to write to the stream.
|
||||
|
||||
A bidirectional stream is only closed once both the read and the write side of the stream have been either closed or reset. Only then the peer is granted a new stream according to the maximum number of concurrent streams configured via `quic.Config.MaxIncomingStreams`.
|
||||
|
||||
### Configuring QUIC
|
||||
|
||||
The `quic.Config` struct passed to both the listen and dial calls (see above) contains a wide range of configuration options for QUIC connections, incl. the ability to fine-tune flow control limits, the number of streams that the peer is allowed to open concurrently, keep-alives, idle timeouts, and many more. Please refer to the documentation for the `quic.Config` for details.
|
||||
|
||||
The `quic.Transport` contains a few configuration options that don't apply to any single QUIC connection, but to all connections handled by that transport. It is highly recommend to set the `StatelessResetToken`, which allows endpoints to quickly recover from crashes / reboots of our node (see [Section 10.3 of RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000#section-10.3)).
|
||||
|
||||
### Closing a Connection
|
||||
|
||||
#### When the remote Peer closes the Connection
|
||||
|
||||
In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Additionally, it is set as cancellation cause of the connection context. Users can use errors assertions to find out what exactly went wrong:
|
||||
|
||||
* `quic.VersionNegotiationError`: Happens during the handshake, if there is no overlap between our and the remote's supported QUIC versions.
|
||||
* `quic.HandshakeTimeoutError`: Happens if the QUIC handshake doesn't complete within the time specified in `quic.Config.HandshakeTimeout`.
|
||||
* `quic.IdleTimeoutError`: Happens after completion of the handshake if the connection is idle for longer than the minimum of both peers idle timeouts (as configured by `quic.Config.IdleTimeout`). The connection is considered idle when no stream data (and datagrams, if applicable) are exchanged for that period. The QUIC connection can be instructed to regularly send a packet to prevent a connection from going idle by setting `quic.Config.KeepAlive`. However, this is no guarantee that the peer doesn't suddenly go away (e.g. by abruptly shutting down the node or by crashing), or by a NAT binding expiring, in which case this error might still occur.
|
||||
* `quic.StatelessResetError`: Happens when the remote peer lost the state required to decrypt the packet. This requires the `quic.Transport.StatelessResetToken` to be configured by the peer.
|
||||
* `quic.TransportError`: Happens if when the QUIC protocol is violated. Unless the error code is `APPLICATION_ERROR`, this will not happen unless one of the QUIC stacks involved is misbehaving. Please open an issue if you encounter this error.
|
||||
* `quic.ApplicationError`: Happens when the remote decides to close the connection, see below.
|
||||
|
||||
#### Initiated by the Application
|
||||
|
||||
A `quic.Connection` can be closed using `CloseWithError`:
|
||||
|
||||
```go
|
||||
conn.CloseWithError(0x42, "error 0x42 occurred")
|
||||
```
|
||||
|
||||
Applications can transmit both an error code (an unsigned 62-bit number) as well as a UTF-8 encoded human-readable reason. The error code allows the receiver to learn why the connection was closed, and the reason can be useful for debugging purposes.
|
||||
|
||||
On the receiver side, this is surfaced as a `quic.ApplicationError`.
|
||||
|
||||
### QUIC Datagrams
|
||||
|
||||
Unreliable datagrams are a QUIC extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221)) that is negotiated during the handshake. Support can be enabled by setting the `quic.Config.EnableDatagram` flag. Note that this doesn't guarantee that the peer also supports datagrams. Whether or not the feature negotiation succeeded can be learned from the `quic.ConnectionState.SupportsDatagrams` obtained from `quic.Connection.ConnectionState()`.
|
||||
|
||||
QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not retransmitted.
|
||||
|
||||
Datagrams are sent using the `SendDatagram` method on the `quic.Connection`:
|
||||
|
||||
```go
|
||||
conn.SendDatagram([]byte("foobar"))
|
||||
```
|
||||
|
||||
And received using `ReceiveDatagram`:
|
||||
|
||||
```go
|
||||
msg, err := conn.ReceiveDatagram()
|
||||
```
|
||||
|
||||
Note that this code path is currently not optimized. It works for datagrams that are sent occasionally, but it doesn't achieve the same throughput as writing data on a stream. Please get in touch on issue #3766 if your use case relies on high datagram throughput, or if you'd like to help fix this issue. There are also some restrictions regarding the maximum message size (see #3599).
|
||||
|
||||
### QUIC Event Logging using qlog
|
||||
|
||||
quic-go logs a wide range of events defined in [draft-ietf-quic-qlog-quic-events](https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-quic-events/), providing comprehensive insights in the internals of a QUIC connection.
|
||||
|
||||
qlog files can be processed by a number of 3rd-party tools. [qviz](https://qvis.quictools.info/) has proven very useful for debugging all kinds of QUIC connection failures.
|
||||
|
||||
qlog can be activated by setting the `Tracer` callback on the `Config`. It is called as soon as quic-go decides to start the QUIC handshake on a new connection.
|
||||
`qlog.DefaultTracer` provides a tracer implementation which writes qlog files to a directory specified by the `QLOGDIR` environment variable, if set.
|
||||
The default qlog tracer can be used like this:
|
||||
```go
|
||||
quic.Config{
|
||||
Tracer: qlog.DefaultTracer,
|
||||
}
|
||||
```
|
||||
|
||||
This example creates a new qlog file under `<QLOGDIR>/<Original Destination Connection ID>_<Vantage Point>.qlog`, e.g. `qlogs/2e0407da_client.qlog`.
|
||||
|
||||
|
||||
For custom qlog behavior, `qlog.NewConnectionTracer` can be used.
|
||||
|
||||
## Using HTTP/3
|
||||
|
||||
### As a server
|
||||
|
||||
See the [example server](example/main.go). Starting a QUIC server is very similar to the standard library http package in Go:
|
||||
|
||||
```go
|
||||
http.Handle("/", http.FileServer(http.Dir(wwwDir)))
|
||||
http3.ListenAndServeQUIC("localhost:4242", "/path/to/cert/chain.pem", "/path/to/privkey.pem", nil)
|
||||
```
|
||||
|
||||
### As a client
|
||||
|
||||
See the [example client](example/client/main.go). Use a `http3.RoundTripper` as a `Transport` in a `http.Client`.
|
||||
|
||||
```go
|
||||
http.Client{
|
||||
Transport: &http3.RoundTripper{},
|
||||
}
|
||||
```
|
||||
Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/).
|
||||
|
||||
## Projects using quic-go
|
||||
|
||||
|
2
vendor/github.com/quic-go/quic-go/client.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/client.go
generated
vendored
@ -35,7 +35,7 @@ type client struct {
|
||||
conn quicConn
|
||||
|
||||
tracer *logging.ConnectionTracer
|
||||
tracingID uint64
|
||||
tracingID ConnectionTracingID
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
|
20
vendor/github.com/quic-go/quic-go/connection.go
generated
vendored
20
vendor/github.com/quic-go/quic-go/connection.go
generated
vendored
@ -113,8 +113,8 @@ func (e *errCloseForRecreating) Error() string {
|
||||
return "closing connection in order to recreate it"
|
||||
}
|
||||
|
||||
var connTracingID uint64 // to be accessed atomically
|
||||
func nextConnTracingID() uint64 { return atomic.AddUint64(&connTracingID, 1) }
|
||||
var connTracingID atomic.Uint64 // to be accessed atomically
|
||||
func nextConnTracingID() ConnectionTracingID { return ConnectionTracingID(connTracingID.Add(1)) }
|
||||
|
||||
// A Connection is a QUIC connection
|
||||
type connection struct {
|
||||
@ -234,7 +234,7 @@ type connection struct {
|
||||
tokenGenerator *handshake.TokenGenerator,
|
||||
clientAddressValidated bool,
|
||||
tracer *logging.ConnectionTracer,
|
||||
tracingID uint64,
|
||||
tracingID ConnectionTracingID,
|
||||
logger utils.Logger,
|
||||
v protocol.Version,
|
||||
) quicConn {
|
||||
@ -272,8 +272,8 @@ func(connID protocol.ConnectionID) { runner.Add(connID, s) },
|
||||
s.queueControlFrame,
|
||||
connIDGenerator,
|
||||
)
|
||||
s.preSetup()
|
||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
||||
s.preSetup()
|
||||
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
||||
0,
|
||||
getMaxPacketSize(s.conn.RemoteAddr()),
|
||||
@ -347,7 +347,7 @@ func(connID protocol.ConnectionID) { runner.Add(connID, s) },
|
||||
enable0RTT bool,
|
||||
hasNegotiatedVersion bool,
|
||||
tracer *logging.ConnectionTracer,
|
||||
tracingID uint64,
|
||||
tracingID ConnectionTracingID,
|
||||
logger utils.Logger,
|
||||
v protocol.Version,
|
||||
) quicConn {
|
||||
@ -381,8 +381,8 @@ func(connID protocol.ConnectionID) { runner.Add(connID, s) },
|
||||
s.queueControlFrame,
|
||||
connIDGenerator,
|
||||
)
|
||||
s.preSetup()
|
||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
||||
s.preSetup()
|
||||
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
||||
initialPacketNumber,
|
||||
getMaxPacketSize(s.conn.RemoteAddr()),
|
||||
@ -471,6 +471,7 @@ func(size protocol.ByteCount) bool {
|
||||
)
|
||||
s.earlyConnReadyChan = make(chan struct{})
|
||||
s.streamsMap = newStreamsMap(
|
||||
s.ctx,
|
||||
s,
|
||||
s.newFlowController,
|
||||
uint64(s.config.MaxIncomingStreams),
|
||||
@ -2357,10 +2358,9 @@ func (s *connection) SendDatagram(p []byte) error {
|
||||
}
|
||||
|
||||
f := &wire.DatagramFrame{DataLenPresent: true}
|
||||
if protocol.ByteCount(len(p)) > f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version) {
|
||||
return &DatagramTooLargeError{
|
||||
PeerMaxDatagramFrameSize: int64(s.peerParams.MaxDatagramFrameSize),
|
||||
}
|
||||
maxDataLen := f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version)
|
||||
if protocol.ByteCount(len(p)) > maxDataLen {
|
||||
return &DatagramTooLargeError{MaxDatagramPayloadSize: int64(maxDataLen)}
|
||||
}
|
||||
f.Data = make([]byte, len(p))
|
||||
copy(f.Data, p)
|
||||
|
2
vendor/github.com/quic-go/quic-go/errors.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/errors.go
generated
vendored
@ -64,7 +64,7 @@ func (e *StreamError) Error() string {
|
||||
|
||||
// DatagramTooLargeError is returned from Connection.SendDatagram if the payload is too large to be sent.
|
||||
type DatagramTooLargeError struct {
|
||||
PeerMaxDatagramFrameSize int64
|
||||
MaxDatagramPayloadSize int64
|
||||
}
|
||||
|
||||
func (e *DatagramTooLargeError) Is(target error) bool {
|
||||
|
2
vendor/github.com/quic-go/quic-go/framer.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/framer.go
generated
vendored
@ -157,7 +157,7 @@ func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen pro
|
||||
// For the last STREAM frame, we'll remove the DataLen field later.
|
||||
// Therefore, we can pretend to have more bytes available when popping
|
||||
// the STREAM frame (which will always have the DataLen set).
|
||||
remainingLen += quicvarint.Len(uint64(remainingLen))
|
||||
remainingLen += protocol.ByteCount(quicvarint.Len(uint64(remainingLen)))
|
||||
frame, ok, hasMoreData := str.popStreamFrame(remainingLen, v)
|
||||
if hasMoreData { // put the stream back in the queue (at the end)
|
||||
f.streamQueue.PushBack(id)
|
||||
|
101
vendor/github.com/quic-go/quic-go/http3/README.md
generated
vendored
101
vendor/github.com/quic-go/quic-go/http3/README.md
generated
vendored
@ -1,104 +1,9 @@
|
||||
# HTTP/3
|
||||
|
||||
[![Documentation](https://img.shields.io/badge/docs-quic--go.net-red?style=flat)](https://quic-go.net/docs/)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go/http3)](https://pkg.go.dev/github.com/quic-go/quic-go/http3)
|
||||
|
||||
This package implements HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
|
||||
This package implements HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) and HTTP Datagrams ([RFC 9297](https://datatracker.ietf.org/doc/html/rfc9297)).
|
||||
It aims to provide feature parity with the standard library's HTTP/1.1 and HTTP/2 implementation.
|
||||
|
||||
## Serving HTTP/3
|
||||
|
||||
The easiest way to start an HTTP/3 server is using
|
||||
```go
|
||||
mux := http.NewServeMux()
|
||||
// ... add HTTP handlers to mux ...
|
||||
// If mux is nil, the http.DefaultServeMux is used.
|
||||
http3.ListenAndServeQUIC("0.0.0.0:443", "/path/to/cert", "/path/to/key", mux)
|
||||
```
|
||||
|
||||
`ListenAndServeQUIC` is a convenience function. For more configurability, set up an `http3.Server` explicitly:
|
||||
```go
|
||||
server := http3.Server{
|
||||
Handler: mux,
|
||||
Addr: "0.0.0.0:443",
|
||||
TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}), // use your tls.Config here
|
||||
QuicConfig: &quic.Config{},
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
```
|
||||
|
||||
The `http3.Server` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#Server) for a complete list. The `QuicConfig` is used to configure the underlying QUIC connection. More details can be found in the documentation of the QUIC package.
|
||||
|
||||
It is also possible to manually set up a `quic.Transport`, and then pass the listener to the server. This is useful when you want to set configuration options on the `quic.Transport`.
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
ln, _ := tr.ListenEarly(tlsConf, quicConf)
|
||||
server.ServeListener(ln)
|
||||
```
|
||||
|
||||
Alternatively, it is also possible to pass fully established QUIC connections to the HTTP/3 server. This is useful if the QUIC server offers multiple ALPNs (via `NextProtos` in the `tls.Config`).
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
// alternatively, use tr.ListenEarly to accept 0-RTT connections
|
||||
ln, _ := tr.Listen(tlsConf, quicConf)
|
||||
for {
|
||||
c, _ := ln.Accept()
|
||||
switch c.ConnectionState().TLS.NegotiatedProtocol {
|
||||
case http3.NextProtoH3:
|
||||
go server.ServeQUICConn(c)
|
||||
// ... handle other protocols ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dialing HTTP/3
|
||||
|
||||
This package provides a `http.RoundTripper` implementation that can be used on the `http.Client`:
|
||||
|
||||
```go
|
||||
&http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
}
|
||||
defer roundTripper.Close()
|
||||
client := &http.Client{
|
||||
Transport: roundTripper,
|
||||
}
|
||||
```
|
||||
|
||||
The `http3.RoundTripper` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#RoundTripper) for a complete list.
|
||||
|
||||
To use a custom `quic.Transport`, the function used to dial new QUIC connections can be configured:
|
||||
```go
|
||||
tr := quic.Transport{}
|
||||
roundTripper := &http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
a, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr.DialEarly(ctx, a, tlsConf, quicConf)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Using the same UDP Socket for Server and Roundtripper
|
||||
|
||||
Since QUIC demultiplexes packets based on their connection IDs, it is possible allows running a QUIC server and client on the same UDP socket. This also works when using HTTP/3: HTTP requests can be sent from the same socket that a server is listening on.
|
||||
|
||||
To achieve this using this package, first initialize a single `quic.Transport`, and pass a `quic.EarlyListner` obtained from that transport to `http3.Server.ServeListener`, and use the `DialEarly` function of the transport as the `Dial` function for the `http3.RoundTripper`.
|
||||
|
||||
## QPACK
|
||||
|
||||
HTTP/3 utilizes QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) for efficient HTTP header field compression. Our implementation, available at[quic-go/qpack](https://github.com/quic-go/qpack), provides a minimal implementation of the protocol.
|
||||
|
||||
While the current implementation is a fully interoperable implementation of the QPACK protocol, it only uses the static compression table. The dynamic table would allow for more effective compression of frequently transmitted header fields. This can be particularly beneficial in scenarios where headers have considerable redundancy or in high-throughput environments.
|
||||
|
||||
If you think that your application would benefit from higher compression efficiency, or if you're interested in contributing improvements here, please let us know in [#2424](https://github.com/quic-go/quic-go/issues/2424).
|
||||
Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/).
|
||||
|
130
vendor/github.com/quic-go/quic-go/http3/body.go
generated
vendored
130
vendor/github.com/quic-go/quic-go/http3/body.go
generated
vendored
@ -2,68 +2,67 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// The HTTPStreamer allows taking over a HTTP/3 stream. The interface is implemented by:
|
||||
// * for the server: the http.Request.Body
|
||||
// * for the client: the http.Response.Body
|
||||
// On the client side, the stream will be closed for writing, unless the DontCloseRequestStream RoundTripOpt was set.
|
||||
// When a stream is taken over, it's the caller's responsibility to close the stream.
|
||||
type HTTPStreamer interface {
|
||||
HTTPStream() Stream
|
||||
}
|
||||
|
||||
type StreamCreator interface {
|
||||
// Context returns a context that is cancelled when the underlying connection is closed.
|
||||
Context() context.Context
|
||||
OpenStream() (quic.Stream, error)
|
||||
OpenStreamSync(context.Context) (quic.Stream, error)
|
||||
OpenUniStream() (quic.SendStream, error)
|
||||
OpenUniStreamSync(context.Context) (quic.SendStream, error)
|
||||
LocalAddr() net.Addr
|
||||
RemoteAddr() net.Addr
|
||||
ConnectionState() quic.ConnectionState
|
||||
}
|
||||
|
||||
var _ StreamCreator = quic.Connection(nil)
|
||||
|
||||
// A Hijacker allows hijacking of the stream creating part of a quic.Session from a http.Response.Body.
|
||||
// It is used by WebTransport to create WebTransport streams after a session has been established.
|
||||
type Hijacker interface {
|
||||
StreamCreator() StreamCreator
|
||||
Connection() Connection
|
||||
}
|
||||
|
||||
// The body of a http.Request or http.Response.
|
||||
var errTooMuchData = errors.New("peer sent too much data")
|
||||
|
||||
// The body is used in the requestBody (for a http.Request) and the responseBody (for a http.Response).
|
||||
type body struct {
|
||||
str quic.Stream
|
||||
str *stream
|
||||
|
||||
wasHijacked bool // set when HTTPStream is called
|
||||
remainingContentLength int64
|
||||
violatedContentLength bool
|
||||
hasContentLength bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ io.ReadCloser = &body{}
|
||||
_ HTTPStreamer = &body{}
|
||||
)
|
||||
|
||||
func newRequestBody(str Stream) *body {
|
||||
return &body{str: str}
|
||||
func newBody(str *stream, contentLength int64) *body {
|
||||
b := &body{str: str}
|
||||
if contentLength >= 0 {
|
||||
b.hasContentLength = true
|
||||
b.remainingContentLength = contentLength
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (r *body) HTTPStream() Stream {
|
||||
r.wasHijacked = true
|
||||
return r.str
|
||||
}
|
||||
func (r *body) StreamID() quic.StreamID { return r.str.StreamID() }
|
||||
|
||||
func (r *body) wasStreamHijacked() bool {
|
||||
return r.wasHijacked
|
||||
func (r *body) checkContentLengthViolation() error {
|
||||
if !r.hasContentLength {
|
||||
return nil
|
||||
}
|
||||
if r.remainingContentLength < 0 || r.remainingContentLength == 0 && r.str.hasMoreData() {
|
||||
if !r.violatedContentLength {
|
||||
r.str.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
r.str.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
r.violatedContentLength = true
|
||||
}
|
||||
return errTooMuchData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *body) Read(b []byte) (int, error) {
|
||||
if err := r.checkContentLengthViolation(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.hasContentLength {
|
||||
b = b[:min(int64(len(b)), r.remainingContentLength)]
|
||||
}
|
||||
n, err := r.str.Read(b)
|
||||
r.remainingContentLength -= int64(n)
|
||||
if err := r.checkContentLengthViolation(); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, maybeReplaceError(err)
|
||||
}
|
||||
|
||||
@ -72,9 +71,26 @@ func (r *body) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type hijackableBody struct {
|
||||
type requestBody struct {
|
||||
body
|
||||
conn quic.Connection // only needed to implement Hijacker
|
||||
connCtx context.Context
|
||||
rcvdSettings <-chan struct{}
|
||||
getSettings func() *Settings
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &requestBody{}
|
||||
|
||||
func newRequestBody(str *stream, contentLength int64, connCtx context.Context, rcvdSettings <-chan struct{}, getSettings func() *Settings) *requestBody {
|
||||
return &requestBody{
|
||||
body: *newBody(str, contentLength),
|
||||
connCtx: connCtx,
|
||||
rcvdSettings: rcvdSettings,
|
||||
getSettings: getSettings,
|
||||
}
|
||||
}
|
||||
|
||||
type hijackableBody struct {
|
||||
body body
|
||||
|
||||
// only set for the http.Response
|
||||
// The channel is closed when the user is done with this response:
|
||||
@ -83,27 +99,17 @@ type hijackableBody struct {
|
||||
reqDoneClosed bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ Hijacker = &hijackableBody{}
|
||||
_ HTTPStreamer = &hijackableBody{}
|
||||
)
|
||||
var _ io.ReadCloser = &hijackableBody{}
|
||||
|
||||
func newResponseBody(str Stream, conn quic.Connection, done chan<- struct{}) *hijackableBody {
|
||||
func newResponseBody(str *stream, contentLength int64, done chan<- struct{}) *hijackableBody {
|
||||
return &hijackableBody{
|
||||
body: body{
|
||||
str: str,
|
||||
},
|
||||
body: *newBody(str, contentLength),
|
||||
reqDone: done,
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *hijackableBody) StreamCreator() StreamCreator {
|
||||
return r.conn
|
||||
}
|
||||
|
||||
func (r *hijackableBody) Read(b []byte) (int, error) {
|
||||
n, err := r.str.Read(b)
|
||||
n, err := r.body.Read(b)
|
||||
if err != nil {
|
||||
r.requestDone()
|
||||
}
|
||||
@ -120,17 +126,9 @@ func (r *hijackableBody) requestDone() {
|
||||
r.reqDoneClosed = true
|
||||
}
|
||||
|
||||
func (r *body) StreamID() quic.StreamID {
|
||||
return r.str.StreamID()
|
||||
}
|
||||
|
||||
func (r *hijackableBody) Close() error {
|
||||
r.requestDone()
|
||||
// If the EOF was read, CancelRead() is a no-op.
|
||||
r.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
|
||||
r.body.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *hijackableBody) HTTPStream() Stream {
|
||||
return r.str
|
||||
}
|
||||
|
491
vendor/github.com/quic-go/quic-go/http3/client.go
generated
vendored
491
vendor/github.com/quic-go/quic-go/http3/client.go
generated
vendored
@ -2,28 +2,31 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
)
|
||||
|
||||
// MethodGet0RTT allows a GET request to be sent using 0-RTT.
|
||||
// Note that 0-RTT data doesn't provide replay protection.
|
||||
const MethodGet0RTT = "GET_0RTT"
|
||||
const (
|
||||
// MethodGet0RTT allows a GET request to be sent using 0-RTT.
|
||||
// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
|
||||
MethodGet0RTT = "GET_0RTT"
|
||||
// MethodHead0RTT allows a HEAD request to be sent using 0-RTT.
|
||||
// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
|
||||
MethodHead0RTT = "HEAD_0RTT"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "quic-go HTTP/3"
|
||||
@ -35,122 +38,67 @@
|
||||
KeepAlivePeriod: 10 * time.Second,
|
||||
}
|
||||
|
||||
type dialFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error)
|
||||
// SingleDestinationRoundTripper is an HTTP/3 client doing requests to a single remote server.
|
||||
type SingleDestinationRoundTripper struct {
|
||||
Connection quic.Connection
|
||||
|
||||
var dialAddr dialFunc = quic.DialAddrEarly
|
||||
// Enable support for HTTP/3 datagrams (RFC 9297).
|
||||
// If a QUICConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
|
||||
EnableDatagrams bool
|
||||
|
||||
type roundTripperOpts struct {
|
||||
DisableCompression bool
|
||||
EnableDatagram bool
|
||||
MaxHeaderBytes int64
|
||||
// Additional HTTP/3 settings.
|
||||
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
|
||||
AdditionalSettings map[uint64]uint64
|
||||
StreamHijacker func(FrameType, quic.Connection, quic.Stream, error) (hijacked bool, err error)
|
||||
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
||||
}
|
||||
StreamHijacker func(FrameType, quic.ConnectionTracingID, quic.Stream, error) (hijacked bool, err error)
|
||||
UniStreamHijacker func(StreamType, quic.ConnectionTracingID, quic.ReceiveStream, error) (hijacked bool)
|
||||
|
||||
// client is a HTTP3 client doing requests
|
||||
type client struct {
|
||||
tlsConf *tls.Config
|
||||
config *quic.Config
|
||||
opts *roundTripperOpts
|
||||
// MaxResponseHeaderBytes specifies a limit on how many response bytes are
|
||||
// allowed in the server's response header.
|
||||
// Zero means to use a default limit.
|
||||
MaxResponseHeaderBytes int64
|
||||
|
||||
dialOnce sync.Once
|
||||
dialer dialFunc
|
||||
handshakeErr error
|
||||
// DisableCompression, if true, prevents the Transport from requesting compression with an
|
||||
// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
|
||||
// If the Transport requests gzip on its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body.
|
||||
// However, if the user explicitly requested gzip it is not automatically uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
receivedSettings chan struct{} // closed once the server's SETTINGS frame was processed
|
||||
settings *Settings // set once receivedSettings is closed
|
||||
Logger *slog.Logger
|
||||
|
||||
initOnce sync.Once
|
||||
hconn *connection
|
||||
requestWriter *requestWriter
|
||||
|
||||
decoder *qpack.Decoder
|
||||
|
||||
hostname string
|
||||
conn atomic.Pointer[quic.EarlyConnection]
|
||||
|
||||
logger utils.Logger
|
||||
decoder *qpack.Decoder
|
||||
}
|
||||
|
||||
var _ roundTripCloser = &client{}
|
||||
var _ http.RoundTripper = &SingleDestinationRoundTripper{}
|
||||
|
||||
func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) {
|
||||
if conf == nil {
|
||||
conf = defaultQuicConfig.Clone()
|
||||
conf.EnableDatagrams = opts.EnableDatagram
|
||||
}
|
||||
if opts.EnableDatagram && !conf.EnableDatagrams {
|
||||
return nil, errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
|
||||
}
|
||||
if len(conf.Versions) == 0 {
|
||||
conf = conf.Clone()
|
||||
conf.Versions = []quic.Version{protocol.SupportedVersions[0]}
|
||||
}
|
||||
if len(conf.Versions) != 1 {
|
||||
return nil, errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
|
||||
}
|
||||
if conf.MaxIncomingStreams == 0 {
|
||||
conf.MaxIncomingStreams = -1 // don't allow any bidirectional streams
|
||||
}
|
||||
logger := utils.DefaultLogger.WithPrefix("h3 client")
|
||||
|
||||
if tlsConf == nil {
|
||||
tlsConf = &tls.Config{}
|
||||
} else {
|
||||
tlsConf = tlsConf.Clone()
|
||||
}
|
||||
if tlsConf.ServerName == "" {
|
||||
sni, _, err := net.SplitHostPort(hostname)
|
||||
if err != nil {
|
||||
// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
|
||||
sni = hostname
|
||||
}
|
||||
tlsConf.ServerName = sni
|
||||
}
|
||||
// Replace existing ALPNs by H3
|
||||
tlsConf.NextProtos = []string{versionToALPN(conf.Versions[0])}
|
||||
|
||||
return &client{
|
||||
hostname: authorityAddr("https", hostname),
|
||||
tlsConf: tlsConf,
|
||||
requestWriter: newRequestWriter(logger),
|
||||
receivedSettings: make(chan struct{}),
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
config: conf,
|
||||
opts: opts,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
}, nil
|
||||
func (c *SingleDestinationRoundTripper) Start() Connection {
|
||||
c.initOnce.Do(func() { c.init() })
|
||||
return c.hconn
|
||||
}
|
||||
|
||||
func (c *client) dial(ctx context.Context) error {
|
||||
var err error
|
||||
var conn quic.EarlyConnection
|
||||
if c.dialer != nil {
|
||||
conn, err = c.dialer(ctx, c.hostname, c.tlsConf, c.config)
|
||||
} else {
|
||||
conn, err = dialAddr(ctx, c.hostname, c.tlsConf, c.config)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn.Store(&conn)
|
||||
|
||||
func (c *SingleDestinationRoundTripper) init() {
|
||||
c.decoder = qpack.NewDecoder(func(hf qpack.HeaderField) {})
|
||||
c.requestWriter = newRequestWriter()
|
||||
c.hconn = newConnection(c.Connection, c.EnableDatagrams, protocol.PerspectiveClient, c.Logger)
|
||||
// send the SETTINGs frame, using 0-RTT data, if possible
|
||||
go func() {
|
||||
if err := c.setupConn(conn); err != nil {
|
||||
c.logger.Debugf("Setting up connection failed: %s", err)
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
|
||||
if err := c.setupConn(c.hconn); err != nil {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("Setting up connection failed", "error", err)
|
||||
}
|
||||
c.hconn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
|
||||
}
|
||||
}()
|
||||
|
||||
if c.opts.StreamHijacker != nil {
|
||||
go c.handleBidirectionalStreams(conn)
|
||||
if c.StreamHijacker != nil {
|
||||
go c.handleBidirectionalStreams()
|
||||
}
|
||||
go c.handleUnidirectionalStreams(conn)
|
||||
return nil
|
||||
go c.hconn.HandleUnidirectionalStreams(c.UniStreamHijacker)
|
||||
}
|
||||
|
||||
func (c *client) setupConn(conn quic.EarlyConnection) error {
|
||||
func (c *SingleDestinationRoundTripper) setupConn(conn *connection) error {
|
||||
// open the control stream
|
||||
str, err := conn.OpenUniStream()
|
||||
if err != nil {
|
||||
@ -159,122 +107,50 @@ func (c *client) setupConn(conn quic.EarlyConnection) error {
|
||||
b := make([]byte, 0, 64)
|
||||
b = quicvarint.Append(b, streamTypeControlStream)
|
||||
// send the SETTINGS frame
|
||||
b = (&settingsFrame{Datagram: c.opts.EnableDatagram, Other: c.opts.AdditionalSettings}).Append(b)
|
||||
b = (&settingsFrame{Datagram: c.EnableDatagrams, Other: c.AdditionalSettings}).Append(b)
|
||||
_, err = str.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) handleBidirectionalStreams(conn quic.EarlyConnection) {
|
||||
func (c *SingleDestinationRoundTripper) handleBidirectionalStreams() {
|
||||
for {
|
||||
str, err := conn.AcceptStream(context.Background())
|
||||
str, err := c.hconn.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
c.logger.Debugf("accepting bidirectional stream failed: %s", err)
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("accepting bidirectional stream failed", "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func(str quic.Stream) {
|
||||
_, err := parseNextFrame(str, func(ft FrameType, e error) (processed bool, err error) {
|
||||
return c.opts.StreamHijacker(ft, conn, str, e)
|
||||
id := c.hconn.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID)
|
||||
return c.StreamHijacker(ft, id, str, e)
|
||||
})
|
||||
if err == errHijacked {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.logger.Debugf("error handling stream: %s", err)
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("error handling stream", "error", err)
|
||||
}
|
||||
}
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "received HTTP/3 frame on bidirectional stream")
|
||||
c.hconn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "received HTTP/3 frame on bidirectional stream")
|
||||
}(str)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
c.logger.Debugf("accepting unidirectional stream failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func(str quic.ReceiveStream) {
|
||||
streamType, err := quicvarint.Read(quicvarint.NewReader(str))
|
||||
if err != nil {
|
||||
if c.opts.UniStreamHijacker != nil && c.opts.UniStreamHijacker(StreamType(streamType), conn, str, err) {
|
||||
return
|
||||
}
|
||||
c.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err)
|
||||
return
|
||||
}
|
||||
// We're only interested in the control stream here.
|
||||
switch streamType {
|
||||
case streamTypeControlStream:
|
||||
case streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream:
|
||||
// Our QPACK implementation doesn't use the dynamic table yet.
|
||||
// TODO: check that only one stream of each type is opened.
|
||||
return
|
||||
case streamTypePushStream:
|
||||
// We never increased the Push ID, so we don't expect any push streams.
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
|
||||
return
|
||||
default:
|
||||
if c.opts.UniStreamHijacker != nil && c.opts.UniStreamHijacker(StreamType(streamType), conn, str, nil) {
|
||||
return
|
||||
}
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
return
|
||||
}
|
||||
sf, ok := f.(*settingsFrame)
|
||||
if !ok {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
|
||||
return
|
||||
}
|
||||
c.settings = &Settings{
|
||||
EnableDatagram: sf.Datagram,
|
||||
EnableExtendedConnect: sf.ExtendedConnect,
|
||||
Other: sf.Other,
|
||||
}
|
||||
close(c.receivedSettings)
|
||||
if !sf.Datagram {
|
||||
return
|
||||
}
|
||||
// If datagram support was enabled on our side as well as on the server side,
|
||||
// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
|
||||
// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
|
||||
if c.opts.EnableDatagram && !conn.ConnectionState().SupportsDatagrams {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
|
||||
}
|
||||
}(str)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Close() error {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
return (*conn).CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
|
||||
}
|
||||
|
||||
func (c *client) maxHeaderBytes() uint64 {
|
||||
if c.opts.MaxHeaderBytes <= 0 {
|
||||
func (c *SingleDestinationRoundTripper) maxHeaderBytes() uint64 {
|
||||
if c.MaxResponseHeaderBytes <= 0 {
|
||||
return defaultMaxResponseHeaderBytes
|
||||
}
|
||||
return uint64(c.opts.MaxHeaderBytes)
|
||||
return uint64(c.MaxResponseHeaderBytes)
|
||||
}
|
||||
|
||||
// RoundTripOpt executes a request and returns a response
|
||||
func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
rsp, err := c.roundTripOpt(req, opt)
|
||||
// RoundTrip executes a request and returns a response
|
||||
func (c *SingleDestinationRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
c.initOnce.Do(func() { c.init() })
|
||||
|
||||
rsp, err := c.roundTrip(req)
|
||||
if err != nil && req.Context().Err() != nil {
|
||||
// if the context was canceled, return the context cancellation error
|
||||
err = req.Context().Err()
|
||||
@ -282,46 +158,48 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
if authorityAddr("https", hostnameFromRequest(req)) != c.hostname {
|
||||
return nil, fmt.Errorf("http3 client BUG: RoundTripOpt called for the wrong client (expected %s, got %s)", c.hostname, req.Host)
|
||||
}
|
||||
|
||||
c.dialOnce.Do(func() {
|
||||
c.handshakeErr = c.dial(req.Context())
|
||||
})
|
||||
if c.handshakeErr != nil {
|
||||
return nil, c.handshakeErr
|
||||
}
|
||||
|
||||
// At this point, c.conn is guaranteed to be set.
|
||||
conn := *c.conn.Load()
|
||||
|
||||
func (c *SingleDestinationRoundTripper) roundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Immediately send out this request, if this is a 0-RTT request.
|
||||
if req.Method == MethodGet0RTT {
|
||||
switch req.Method {
|
||||
case MethodGet0RTT:
|
||||
// don't modify the original request
|
||||
reqCopy := *req
|
||||
req = &reqCopy
|
||||
req.Method = http.MethodGet
|
||||
} else {
|
||||
case MethodHead0RTT:
|
||||
// don't modify the original request
|
||||
reqCopy := *req
|
||||
req = &reqCopy
|
||||
req.Method = http.MethodHead
|
||||
default:
|
||||
// wait for the handshake to complete
|
||||
select {
|
||||
case <-conn.HandshakeComplete():
|
||||
case <-req.Context().Done():
|
||||
return nil, req.Context().Err()
|
||||
earlyConn, ok := c.Connection.(quic.EarlyConnection)
|
||||
if ok {
|
||||
select {
|
||||
case <-earlyConn.HandshakeComplete():
|
||||
case <-req.Context().Done():
|
||||
return nil, req.Context().Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opt.CheckSettings != nil {
|
||||
// It is only possible to send an Extended CONNECT request once the SETTINGS were received.
|
||||
// See section 3 of RFC 8441.
|
||||
if isExtendedConnectRequest(req) {
|
||||
connCtx := c.Connection.Context()
|
||||
// wait for the server's SETTINGS frame to arrive
|
||||
select {
|
||||
case <-c.receivedSettings:
|
||||
case <-conn.Context().Done():
|
||||
return nil, context.Cause(conn.Context())
|
||||
case <-c.hconn.ReceivedSettings():
|
||||
case <-connCtx.Done():
|
||||
return nil, context.Cause(connCtx)
|
||||
}
|
||||
if err := opt.CheckSettings(*c.settings); err != nil {
|
||||
return nil, err
|
||||
if !c.hconn.Settings().EnableExtendedConnect {
|
||||
return nil, errors.New("http3: server didn't enable Extended CONNECT")
|
||||
}
|
||||
}
|
||||
|
||||
str, err := conn.OpenStreamSync(req.Context())
|
||||
reqDone := make(chan struct{})
|
||||
str, err := c.hconn.openRequestStream(req.Context(), c.requestWriter, reqDone, c.DisableCompression, c.maxHeaderBytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -329,7 +207,6 @@ func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||
// Request Cancellation:
|
||||
// This go routine keeps running even after RoundTripOpt() returns.
|
||||
// It is shut down when the application is done processing the body.
|
||||
reqDone := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
@ -341,31 +218,19 @@ func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||
}
|
||||
}()
|
||||
|
||||
doneChan := reqDone
|
||||
if opt.DontCloseRequestStream {
|
||||
doneChan = nil
|
||||
}
|
||||
rsp, rerr := c.doRequest(req, conn, str, opt, doneChan)
|
||||
if rerr.err != nil { // if any error occurred
|
||||
rsp, err := c.doRequest(req, str)
|
||||
if err != nil { // if any error occurred
|
||||
close(reqDone)
|
||||
<-done
|
||||
if rerr.streamErr != 0 { // if it was a stream error
|
||||
str.CancelWrite(quic.StreamErrorCode(rerr.streamErr))
|
||||
}
|
||||
if rerr.connErr != 0 { // if it was a connection error
|
||||
var reason string
|
||||
if rerr.err != nil {
|
||||
reason = rerr.err.Error()
|
||||
}
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason)
|
||||
}
|
||||
return nil, maybeReplaceError(rerr.err)
|
||||
return nil, maybeReplaceError(err)
|
||||
}
|
||||
if opt.DontCloseRequestStream {
|
||||
close(reqDone)
|
||||
<-done
|
||||
}
|
||||
return rsp, maybeReplaceError(rerr.err)
|
||||
return rsp, maybeReplaceError(err)
|
||||
}
|
||||
|
||||
func (c *SingleDestinationRoundTripper) OpenRequestStream(ctx context.Context) (RequestStream, error) {
|
||||
c.initOnce.Do(func() { c.init() })
|
||||
|
||||
return c.hconn.openRequestStream(ctx, c.requestWriter, nil, c.DisableCompression, c.maxHeaderBytes())
|
||||
}
|
||||
|
||||
// cancelingReader reads from the io.Reader.
|
||||
@ -383,7 +248,7 @@ func (r *cancelingReader) Read(b []byte) (int, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *client) sendRequestBody(str Stream, body io.ReadCloser, contentLength int64) error {
|
||||
func (c *SingleDestinationRoundTripper) sendRequestBody(str Stream, body io.ReadCloser, contentLength int64) error {
|
||||
defer body.Close()
|
||||
buf := make([]byte, bodyCopyBufferSize)
|
||||
sr := &cancelingReader{str: str, r: body}
|
||||
@ -407,21 +272,13 @@ func (c *client) sendRequestBody(str Stream, body io.ReadCloser, contentLength i
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) doRequest(req *http.Request, conn quic.EarlyConnection, str quic.Stream, opt RoundTripOpt, reqDone chan<- struct{}) (*http.Response, requestError) {
|
||||
var requestGzip bool
|
||||
if !c.opts.DisableCompression && req.Method != "HEAD" && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
|
||||
requestGzip = true
|
||||
func (c *SingleDestinationRoundTripper) doRequest(req *http.Request, str *requestStream) (*http.Response, error) {
|
||||
if err := str.SendRequestHeader(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.requestWriter.WriteRequestHeader(str, req, requestGzip); err != nil {
|
||||
return nil, newStreamError(ErrCodeInternalError, err)
|
||||
}
|
||||
|
||||
if req.Body == nil && !opt.DontCloseRequestStream {
|
||||
if req.Body == nil {
|
||||
str.Close()
|
||||
}
|
||||
|
||||
hstr := newStream(str, func() { conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "") })
|
||||
if req.Body != nil {
|
||||
} else {
|
||||
// send the request body asynchronously
|
||||
go func() {
|
||||
contentLength := int64(-1)
|
||||
@ -430,89 +287,47 @@ func (c *client) doRequest(req *http.Request, conn quic.EarlyConnection, str qui
|
||||
if req.ContentLength > 0 {
|
||||
contentLength = req.ContentLength
|
||||
}
|
||||
if err := c.sendRequestBody(hstr, req.Body, contentLength); err != nil {
|
||||
c.logger.Errorf("Error writing request: %s", err)
|
||||
}
|
||||
if !opt.DontCloseRequestStream {
|
||||
hstr.Close()
|
||||
if err := c.sendRequestBody(str, req.Body, contentLength); err != nil {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("error writing request", "error", err)
|
||||
}
|
||||
}
|
||||
str.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
frame, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
return nil, newStreamError(ErrCodeFrameError, err)
|
||||
}
|
||||
hf, ok := frame.(*headersFrame)
|
||||
if !ok {
|
||||
return nil, newConnError(ErrCodeFrameUnexpected, errors.New("expected first frame to be a HEADERS frame"))
|
||||
}
|
||||
if hf.Length > c.maxHeaderBytes() {
|
||||
return nil, newStreamError(ErrCodeFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, c.maxHeaderBytes()))
|
||||
}
|
||||
headerBlock := make([]byte, hf.Length)
|
||||
if _, err := io.ReadFull(str, headerBlock); err != nil {
|
||||
return nil, newStreamError(ErrCodeRequestIncomplete, err)
|
||||
}
|
||||
hfs, err := c.decoder.DecodeFull(headerBlock)
|
||||
if err != nil {
|
||||
// TODO: use the right error code
|
||||
return nil, newConnError(ErrCodeGeneralProtocolError, err)
|
||||
}
|
||||
// copy from net/http: support 1xx responses
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
num1xx := 0 // number of informational 1xx headers received
|
||||
const max1xxResponses = 5 // arbitrary bound on number of informational responses
|
||||
|
||||
res, err := responseFromHeaders(hfs)
|
||||
if err != nil {
|
||||
return nil, newStreamError(ErrCodeMessageError, err)
|
||||
var res *http.Response
|
||||
for {
|
||||
var err error
|
||||
res, err = str.ReadResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resCode := res.StatusCode
|
||||
is1xx := 100 <= resCode && resCode <= 199
|
||||
// treat 101 as a terminal status, see https://github.com/golang/go/issues/26161
|
||||
is1xxNonTerminal := is1xx && resCode != http.StatusSwitchingProtocols
|
||||
if is1xxNonTerminal {
|
||||
num1xx++
|
||||
if num1xx > max1xxResponses {
|
||||
return nil, errors.New("http: too many 1xx informational responses")
|
||||
}
|
||||
if trace != nil && trace.Got1xxResponse != nil {
|
||||
if err := trace.Got1xxResponse(resCode, textproto.MIMEHeader(res.Header)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
connState := conn.ConnectionState().TLS
|
||||
connState := c.hconn.ConnectionState().TLS
|
||||
res.TLS = &connState
|
||||
res.Request = req
|
||||
// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
|
||||
// See section 4.1.2 of RFC 9114.
|
||||
var httpStr Stream
|
||||
if _, ok := res.Header["Content-Length"]; ok && res.ContentLength >= 0 {
|
||||
httpStr = newLengthLimitedStream(hstr, res.ContentLength)
|
||||
} else {
|
||||
httpStr = hstr
|
||||
}
|
||||
respBody := newResponseBody(httpStr, conn, reqDone)
|
||||
|
||||
// Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2.
|
||||
_, hasTransferEncoding := res.Header["Transfer-Encoding"]
|
||||
isInformational := res.StatusCode >= 100 && res.StatusCode < 200
|
||||
isNoContent := res.StatusCode == http.StatusNoContent
|
||||
isSuccessfulConnect := req.Method == http.MethodConnect && res.StatusCode >= 200 && res.StatusCode < 300
|
||||
if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect {
|
||||
res.ContentLength = -1
|
||||
if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 {
|
||||
if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil {
|
||||
res.ContentLength = clen64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if requestGzip && res.Header.Get("Content-Encoding") == "gzip" {
|
||||
res.Header.Del("Content-Encoding")
|
||||
res.Header.Del("Content-Length")
|
||||
res.ContentLength = -1
|
||||
res.Body = newGzipReader(respBody)
|
||||
res.Uncompressed = true
|
||||
} else {
|
||||
res.Body = respBody
|
||||
}
|
||||
|
||||
return res, requestError{}
|
||||
}
|
||||
|
||||
func (c *client) HandshakeComplete() bool {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-(*conn).HandshakeComplete():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
283
vendor/github.com/quic-go/quic-go/http3/conn.go
generated
vendored
Normal file
283
vendor/github.com/quic-go/quic-go/http3/conn.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
)
|
||||
|
||||
// Connection is an HTTP/3 connection.
|
||||
// It has all methods from the quic.Connection expect for AcceptStream, AcceptUniStream,
|
||||
// SendDatagram and ReceiveDatagram.
|
||||
type Connection interface {
|
||||
OpenStream() (quic.Stream, error)
|
||||
OpenStreamSync(context.Context) (quic.Stream, error)
|
||||
OpenUniStream() (quic.SendStream, error)
|
||||
OpenUniStreamSync(context.Context) (quic.SendStream, error)
|
||||
LocalAddr() net.Addr
|
||||
RemoteAddr() net.Addr
|
||||
CloseWithError(quic.ApplicationErrorCode, string) error
|
||||
Context() context.Context
|
||||
ConnectionState() quic.ConnectionState
|
||||
|
||||
// ReceivedSettings returns a channel that is closed once the client's SETTINGS frame was received.
|
||||
ReceivedSettings() <-chan struct{}
|
||||
// Settings returns the settings received on this connection.
|
||||
Settings() *Settings
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
quic.Connection
|
||||
|
||||
perspective protocol.Perspective
|
||||
logger *slog.Logger
|
||||
|
||||
enableDatagrams bool
|
||||
|
||||
decoder *qpack.Decoder
|
||||
|
||||
streamMx sync.Mutex
|
||||
streams map[protocol.StreamID]*datagrammer
|
||||
|
||||
settings *Settings
|
||||
receivedSettings chan struct{}
|
||||
}
|
||||
|
||||
func newConnection(
|
||||
quicConn quic.Connection,
|
||||
enableDatagrams bool,
|
||||
perspective protocol.Perspective,
|
||||
logger *slog.Logger,
|
||||
) *connection {
|
||||
c := &connection{
|
||||
Connection: quicConn,
|
||||
perspective: perspective,
|
||||
logger: logger,
|
||||
enableDatagrams: enableDatagrams,
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
receivedSettings: make(chan struct{}),
|
||||
streams: make(map[protocol.StreamID]*datagrammer),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *connection) onStreamStateChange(id quic.StreamID, state streamState, e error) {
|
||||
c.streamMx.Lock()
|
||||
defer c.streamMx.Unlock()
|
||||
|
||||
d, ok := c.streams[id]
|
||||
if !ok { // should never happen
|
||||
return
|
||||
}
|
||||
var isDone bool
|
||||
//nolint:exhaustive // These are all the cases we care about.
|
||||
switch state {
|
||||
case streamStateReceiveClosed:
|
||||
isDone = d.SetReceiveError(e)
|
||||
case streamStateSendClosed:
|
||||
isDone = d.SetSendError(e)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if isDone {
|
||||
delete(c.streams, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) openRequestStream(
|
||||
ctx context.Context,
|
||||
requestWriter *requestWriter,
|
||||
reqDone chan<- struct{},
|
||||
disableCompression bool,
|
||||
maxHeaderBytes uint64,
|
||||
) (*requestStream, error) {
|
||||
str, err := c.Connection.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
datagrams := newDatagrammer(func(b []byte) error { return c.sendDatagram(str.StreamID(), b) })
|
||||
c.streamMx.Lock()
|
||||
c.streams[str.StreamID()] = datagrams
|
||||
c.streamMx.Unlock()
|
||||
qstr := newStateTrackingStream(str, func(s streamState, e error) { c.onStreamStateChange(str.StreamID(), s, e) })
|
||||
hstr := newStream(qstr, c, datagrams)
|
||||
return newRequestStream(hstr, requestWriter, reqDone, c.decoder, disableCompression, maxHeaderBytes), nil
|
||||
}
|
||||
|
||||
func (c *connection) acceptStream(ctx context.Context) (quic.Stream, *datagrammer, error) {
|
||||
str, err := c.AcceptStream(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
datagrams := newDatagrammer(func(b []byte) error { return c.sendDatagram(str.StreamID(), b) })
|
||||
if c.perspective == protocol.PerspectiveServer {
|
||||
c.streamMx.Lock()
|
||||
c.streams[str.StreamID()] = datagrams
|
||||
c.streamMx.Unlock()
|
||||
str = newStateTrackingStream(str, func(s streamState, e error) { c.onStreamStateChange(str.StreamID(), s, e) })
|
||||
}
|
||||
return str, datagrams, nil
|
||||
}
|
||||
|
||||
func (c *connection) HandleUnidirectionalStreams(hijack func(StreamType, quic.ConnectionTracingID, quic.ReceiveStream, error) (hijacked bool)) {
|
||||
var (
|
||||
rcvdControlStr atomic.Bool
|
||||
rcvdQPACKEncoderStr atomic.Bool
|
||||
rcvdQPACKDecoderStr atomic.Bool
|
||||
)
|
||||
|
||||
for {
|
||||
str, err := c.Connection.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
if c.logger != nil {
|
||||
c.logger.Debug("accepting unidirectional stream failed", "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
go func(str quic.ReceiveStream) {
|
||||
streamType, err := quicvarint.Read(quicvarint.NewReader(str))
|
||||
if err != nil {
|
||||
id := c.Connection.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID)
|
||||
if hijack != nil && hijack(StreamType(streamType), id, str, err) {
|
||||
return
|
||||
}
|
||||
if c.logger != nil {
|
||||
c.logger.Debug("reading stream type on stream failed", "stream ID", str.StreamID(), "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// We're only interested in the control stream here.
|
||||
switch streamType {
|
||||
case streamTypeControlStream:
|
||||
case streamTypeQPACKEncoderStream:
|
||||
if isFirst := rcvdQPACKEncoderStr.CompareAndSwap(false, true); !isFirst {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK encoder stream")
|
||||
}
|
||||
// Our QPACK implementation doesn't use the dynamic table yet.
|
||||
return
|
||||
case streamTypeQPACKDecoderStream:
|
||||
if isFirst := rcvdQPACKDecoderStr.CompareAndSwap(false, true); !isFirst {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK decoder stream")
|
||||
}
|
||||
// Our QPACK implementation doesn't use the dynamic table yet.
|
||||
return
|
||||
case streamTypePushStream:
|
||||
switch c.perspective {
|
||||
case protocol.PerspectiveClient:
|
||||
// we never increased the Push ID, so we don't expect any push streams
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
|
||||
case protocol.PerspectiveServer:
|
||||
// only the server can push
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
|
||||
}
|
||||
return
|
||||
default:
|
||||
if hijack != nil {
|
||||
if hijack(
|
||||
StreamType(streamType),
|
||||
c.Connection.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID),
|
||||
str,
|
||||
nil,
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStr.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
return
|
||||
}
|
||||
sf, ok := f.(*settingsFrame)
|
||||
if !ok {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
|
||||
return
|
||||
}
|
||||
c.settings = &Settings{
|
||||
EnableDatagrams: sf.Datagram,
|
||||
EnableExtendedConnect: sf.ExtendedConnect,
|
||||
Other: sf.Other,
|
||||
}
|
||||
close(c.receivedSettings)
|
||||
if !sf.Datagram {
|
||||
return
|
||||
}
|
||||
// If datagram support was enabled on our side as well as on the server side,
|
||||
// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
|
||||
// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
|
||||
if c.enableDatagrams && !c.Connection.ConnectionState().SupportsDatagrams {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := c.receiveDatagrams(); err != nil {
|
||||
if c.logger != nil {
|
||||
c.logger.Debug("receiving datagrams failed", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}(str)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) sendDatagram(streamID protocol.StreamID, b []byte) error {
|
||||
// TODO: this creates a lot of garbage and an additional copy
|
||||
data := make([]byte, 0, len(b)+8)
|
||||
data = quicvarint.Append(data, uint64(streamID/4))
|
||||
data = append(data, b...)
|
||||
return c.Connection.SendDatagram(data)
|
||||
}
|
||||
|
||||
func (c *connection) receiveDatagrams() error {
|
||||
for {
|
||||
b, err := c.Connection.ReceiveDatagram(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: this is quite wasteful in terms of allocations
|
||||
r := bytes.NewReader(b)
|
||||
quarterStreamID, err := quicvarint.Read(r)
|
||||
if err != nil {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
|
||||
return fmt.Errorf("could not read quarter stream id: %w", err)
|
||||
}
|
||||
if quarterStreamID > maxQuarterStreamID {
|
||||
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
|
||||
return fmt.Errorf("invalid quarter stream id: %w", err)
|
||||
}
|
||||
streamID := protocol.StreamID(4 * quarterStreamID)
|
||||
c.streamMx.Lock()
|
||||
dg, ok := c.streams[streamID]
|
||||
if !ok {
|
||||
c.streamMx.Unlock()
|
||||
return nil
|
||||
}
|
||||
c.streamMx.Unlock()
|
||||
dg.enqueue(b[len(b)-r.Len():])
|
||||
}
|
||||
}
|
||||
|
||||
// ReceivedSettings returns a channel that is closed once the peer's SETTINGS frame was received.
|
||||
func (c *connection) ReceivedSettings() <-chan struct{} { return c.receivedSettings }
|
||||
|
||||
// Settings returns the settings received on this connection.
|
||||
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
|
||||
func (c *connection) Settings() *Settings { return c.settings }
|
100
vendor/github.com/quic-go/quic-go/http3/datagram.go
generated
vendored
Normal file
100
vendor/github.com/quic-go/quic-go/http3/datagram.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const maxQuarterStreamID = 1<<60 - 1
|
||||
|
||||
const streamDatagramQueueLen = 32
|
||||
|
||||
type datagrammer struct {
|
||||
sendDatagram func([]byte) error
|
||||
|
||||
hasData chan struct{}
|
||||
queue [][]byte // TODO: use a ring buffer
|
||||
|
||||
mx sync.Mutex
|
||||
sendErr error
|
||||
receiveErr error
|
||||
}
|
||||
|
||||
func newDatagrammer(sendDatagram func([]byte) error) *datagrammer {
|
||||
return &datagrammer{
|
||||
sendDatagram: sendDatagram,
|
||||
hasData: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *datagrammer) SetReceiveError(err error) (isDone bool) {
|
||||
d.mx.Lock()
|
||||
defer d.mx.Unlock()
|
||||
|
||||
d.receiveErr = err
|
||||
d.signalHasData()
|
||||
return d.sendErr != nil
|
||||
}
|
||||
|
||||
func (d *datagrammer) SetSendError(err error) (isDone bool) {
|
||||
d.mx.Lock()
|
||||
defer d.mx.Unlock()
|
||||
|
||||
d.sendErr = err
|
||||
return d.receiveErr != nil
|
||||
}
|
||||
|
||||
func (d *datagrammer) Send(b []byte) error {
|
||||
d.mx.Lock()
|
||||
sendErr := d.sendErr
|
||||
d.mx.Unlock()
|
||||
if sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
|
||||
return d.sendDatagram(b)
|
||||
}
|
||||
|
||||
func (d *datagrammer) signalHasData() {
|
||||
select {
|
||||
case d.hasData <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (d *datagrammer) enqueue(data []byte) {
|
||||
d.mx.Lock()
|
||||
defer d.mx.Unlock()
|
||||
|
||||
if d.receiveErr != nil {
|
||||
return
|
||||
}
|
||||
if len(d.queue) >= streamDatagramQueueLen {
|
||||
return
|
||||
}
|
||||
d.queue = append(d.queue, data)
|
||||
d.signalHasData()
|
||||
}
|
||||
|
||||
func (d *datagrammer) Receive(ctx context.Context) ([]byte, error) {
|
||||
start:
|
||||
d.mx.Lock()
|
||||
if len(d.queue) >= 1 {
|
||||
data := d.queue[0]
|
||||
d.queue = d.queue[1:]
|
||||
d.mx.Unlock()
|
||||
return data, nil
|
||||
}
|
||||
if d.receiveErr != nil {
|
||||
d.mx.Unlock()
|
||||
return nil, d.receiveErr
|
||||
}
|
||||
d.mx.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, context.Cause(ctx)
|
||||
case <-d.hasData:
|
||||
}
|
||||
goto start
|
||||
}
|
3
vendor/github.com/quic-go/quic-go/http3/frames.go
generated
vendored
3
vendor/github.com/quic-go/quic-go/http3/frames.go
generated
vendored
@ -6,7 +6,6 @@
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
)
|
||||
|
||||
@ -160,7 +159,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
||||
|
||||
func (f *settingsFrame) Append(b []byte) []byte {
|
||||
b = quicvarint.Append(b, 0x4)
|
||||
var l protocol.ByteCount
|
||||
var l int
|
||||
for id, val := range f.Other {
|
||||
l += quicvarint.Len(id) + quicvarint.Len(val)
|
||||
}
|
||||
|
6
vendor/github.com/quic-go/quic-go/http3/headers.go
generated
vendored
6
vendor/github.com/quic-go/quic-go/http3/headers.go
generated
vendored
@ -171,9 +171,9 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func hostnameFromRequest(req *http.Request) string {
|
||||
if req.URL != nil {
|
||||
return req.URL.Host
|
||||
func hostnameFromURL(url *url.URL) string {
|
||||
if url != nil {
|
||||
return url.Host
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
218
vendor/github.com/quic-go/quic-go/http3/http_stream.go
generated
vendored
218
vendor/github.com/quic-go/quic-go/http3/http_stream.go
generated
vendored
@ -1,34 +1,64 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
)
|
||||
|
||||
// A Stream is a HTTP/3 stream.
|
||||
// A Stream is an HTTP/3 request stream.
|
||||
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
|
||||
type Stream quic.Stream
|
||||
|
||||
// The stream conforms to the quic.Stream interface, but instead of writing to and reading directly
|
||||
// from the QUIC stream, it writes to and reads from the HTTP stream.
|
||||
type stream struct {
|
||||
type Stream interface {
|
||||
quic.Stream
|
||||
|
||||
buf []byte
|
||||
SendDatagram([]byte) error
|
||||
ReceiveDatagram(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// A RequestStream is an HTTP/3 request stream.
|
||||
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
|
||||
type RequestStream interface {
|
||||
Stream
|
||||
|
||||
// SendRequestHeader sends the HTTP request.
|
||||
// It is invalid to call it more than once.
|
||||
// It is invalid to call it after Write has been called.
|
||||
SendRequestHeader(req *http.Request) error
|
||||
|
||||
// ReadResponse reads the HTTP response from the stream.
|
||||
// It is invalid to call it more than once.
|
||||
// It doesn't set Response.Request and Response.TLS.
|
||||
// It is invalid to call it after Read has been called.
|
||||
ReadResponse() (*http.Response, error)
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
quic.Stream
|
||||
conn *connection
|
||||
|
||||
buf []byte // used as a temporary buffer when writing the HTTP/3 frame headers
|
||||
|
||||
onFrameError func()
|
||||
bytesRemainingInFrame uint64
|
||||
|
||||
datagrams *datagrammer
|
||||
}
|
||||
|
||||
var _ Stream = &stream{}
|
||||
|
||||
func newStream(str quic.Stream, onFrameError func()) *stream {
|
||||
func newStream(str quic.Stream, conn *connection, datagrams *datagrammer) *stream {
|
||||
return &stream{
|
||||
Stream: str,
|
||||
onFrameError: onFrameError,
|
||||
buf: make([]byte, 0, 16),
|
||||
Stream: str,
|
||||
conn: conn,
|
||||
buf: make([]byte, 16),
|
||||
datagrams: datagrams,
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +78,7 @@ func (s *stream) Read(b []byte) (int, error) {
|
||||
s.bytesRemainingInFrame = f.Length
|
||||
break parseLoop
|
||||
default:
|
||||
s.onFrameError()
|
||||
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
|
||||
// parseNextFrame skips over unknown frame types
|
||||
// Therefore, this condition is only entered when we parsed another known frame type.
|
||||
return 0, fmt.Errorf("peer sent an unexpected frame: %T", f)
|
||||
@ -80,44 +110,150 @@ func (s *stream) Write(b []byte) (int, error) {
|
||||
return s.Stream.Write(b)
|
||||
}
|
||||
|
||||
var errTooMuchData = errors.New("peer sent too much data")
|
||||
func (s *stream) writeUnframed(b []byte) (int, error) {
|
||||
return s.Stream.Write(b)
|
||||
}
|
||||
|
||||
type lengthLimitedStream struct {
|
||||
func (s *stream) StreamID() protocol.StreamID {
|
||||
return s.Stream.StreamID()
|
||||
}
|
||||
|
||||
// The stream conforms to the quic.Stream interface, but instead of writing to and reading directly
|
||||
// from the QUIC stream, it writes to and reads from the HTTP stream.
|
||||
type requestStream struct {
|
||||
*stream
|
||||
contentLength int64
|
||||
read int64
|
||||
resetStream bool
|
||||
|
||||
responseBody io.ReadCloser // set by ReadResponse
|
||||
|
||||
decoder *qpack.Decoder
|
||||
requestWriter *requestWriter
|
||||
maxHeaderBytes uint64
|
||||
reqDone chan<- struct{}
|
||||
disableCompression bool
|
||||
|
||||
sentRequest bool
|
||||
requestedGzip bool
|
||||
isConnect bool
|
||||
}
|
||||
|
||||
var _ Stream = &lengthLimitedStream{}
|
||||
var _ RequestStream = &requestStream{}
|
||||
|
||||
func newLengthLimitedStream(str *stream, contentLength int64) *lengthLimitedStream {
|
||||
return &lengthLimitedStream{
|
||||
stream: str,
|
||||
contentLength: contentLength,
|
||||
func newRequestStream(
|
||||
str *stream,
|
||||
requestWriter *requestWriter,
|
||||
reqDone chan<- struct{},
|
||||
decoder *qpack.Decoder,
|
||||
disableCompression bool,
|
||||
maxHeaderBytes uint64,
|
||||
) *requestStream {
|
||||
return &requestStream{
|
||||
stream: str,
|
||||
requestWriter: requestWriter,
|
||||
reqDone: reqDone,
|
||||
decoder: decoder,
|
||||
disableCompression: disableCompression,
|
||||
maxHeaderBytes: maxHeaderBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lengthLimitedStream) checkContentLengthViolation() error {
|
||||
if s.read > s.contentLength || s.read == s.contentLength && s.hasMoreData() {
|
||||
if !s.resetStream {
|
||||
s.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
s.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
s.resetStream = true
|
||||
func (s *requestStream) Read(b []byte) (int, error) {
|
||||
if s.responseBody == nil {
|
||||
return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first")
|
||||
}
|
||||
return s.responseBody.Read(b)
|
||||
}
|
||||
|
||||
func (s *requestStream) SendRequestHeader(req *http.Request) error {
|
||||
if s.sentRequest {
|
||||
return errors.New("http3: invalid duplicate use of SendRequestHeader")
|
||||
}
|
||||
if !s.disableCompression && req.Method != http.MethodHead &&
|
||||
req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
|
||||
s.requestedGzip = true
|
||||
}
|
||||
s.isConnect = req.Method == http.MethodConnect
|
||||
s.sentRequest = true
|
||||
return s.requestWriter.WriteRequestHeader(s.Stream, req, s.requestedGzip)
|
||||
}
|
||||
|
||||
func (s *requestStream) ReadResponse() (*http.Response, error) {
|
||||
frame, err := parseNextFrame(s.Stream, nil)
|
||||
if err != nil {
|
||||
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
return nil, fmt.Errorf("http3: parsing frame failed: %w", err)
|
||||
}
|
||||
hf, ok := frame.(*headersFrame)
|
||||
if !ok {
|
||||
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame")
|
||||
return nil, errors.New("http3: expected first frame to be a HEADERS frame")
|
||||
}
|
||||
if hf.Length > s.maxHeaderBytes {
|
||||
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes)
|
||||
}
|
||||
headerBlock := make([]byte, hf.Length)
|
||||
if _, err := io.ReadFull(s.Stream, headerBlock); err != nil {
|
||||
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
return nil, fmt.Errorf("http3: failed to read response headers: %w", err)
|
||||
}
|
||||
hfs, err := s.decoder.DecodeFull(headerBlock)
|
||||
if err != nil {
|
||||
// TODO: use the right error code
|
||||
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeGeneralProtocolError), "")
|
||||
return nil, fmt.Errorf("http3: failed to decode response headers: %w", err)
|
||||
}
|
||||
|
||||
res, err := responseFromHeaders(hfs)
|
||||
if err != nil {
|
||||
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
return nil, fmt.Errorf("http3: invalid response: %w", err)
|
||||
}
|
||||
|
||||
// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
|
||||
// See section 4.1.2 of RFC 9114.
|
||||
contentLength := int64(-1)
|
||||
if _, ok := res.Header["Content-Length"]; ok && res.ContentLength >= 0 {
|
||||
contentLength = res.ContentLength
|
||||
}
|
||||
respBody := newResponseBody(s.stream, contentLength, s.reqDone)
|
||||
|
||||
// Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2.
|
||||
_, hasTransferEncoding := res.Header["Transfer-Encoding"]
|
||||
isInformational := res.StatusCode >= 100 && res.StatusCode < 200
|
||||
isNoContent := res.StatusCode == http.StatusNoContent
|
||||
isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300
|
||||
if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect {
|
||||
res.ContentLength = -1
|
||||
if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 {
|
||||
if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil {
|
||||
res.ContentLength = clen64
|
||||
}
|
||||
}
|
||||
return errTooMuchData
|
||||
}
|
||||
return nil
|
||||
|
||||
if s.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" {
|
||||
res.Header.Del("Content-Encoding")
|
||||
res.Header.Del("Content-Length")
|
||||
res.ContentLength = -1
|
||||
s.responseBody = newGzipReader(respBody)
|
||||
res.Uncompressed = true
|
||||
} else {
|
||||
s.responseBody = respBody
|
||||
}
|
||||
res.Body = s.responseBody
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *lengthLimitedStream) Read(b []byte) (int, error) {
|
||||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := s.stream.Read(b[:min(int64(len(b)), s.contentLength-s.read)])
|
||||
s.read += int64(n)
|
||||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
func (s *stream) SendDatagram(b []byte) error {
|
||||
// TODO: reject if datagrams are not negotiated (yet)
|
||||
return s.conn.sendDatagram(s.Stream.StreamID(), b)
|
||||
}
|
||||
|
||||
func (s *stream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
|
||||
// TODO: reject if datagrams are not negotiated (yet)
|
||||
return s.datagrams.Receive(ctx)
|
||||
}
|
||||
|
4
vendor/github.com/quic-go/quic-go/http3/mockgen.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/http3/mockgen.go
generated
vendored
@ -2,7 +2,7 @@
|
||||
|
||||
package http3
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser"
|
||||
type RoundTripCloser = roundTripCloser
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package http3 -destination mock_singleroundtripper_test.go github.com/quic-go/quic-go/http3 SingleRoundTripper"
|
||||
type SingleRoundTripper = singleRoundTripper
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener"
|
||||
|
17
vendor/github.com/quic-go/quic-go/http3/request_writer.go
generated
vendored
17
vendor/github.com/quic-go/quic-go/http3/request_writer.go
generated
vendored
@ -17,7 +17,6 @@
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
|
||||
const bodyCopyBufferSize = 8 * 1024
|
||||
@ -26,17 +25,14 @@ type requestWriter struct {
|
||||
mutex sync.Mutex
|
||||
encoder *qpack.Encoder
|
||||
headerBuf *bytes.Buffer
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
func newRequestWriter(logger utils.Logger) *requestWriter {
|
||||
func newRequestWriter() *requestWriter {
|
||||
headerBuf := &bytes.Buffer{}
|
||||
encoder := qpack.NewEncoder(headerBuf)
|
||||
return &requestWriter{
|
||||
encoder: encoder,
|
||||
headerBuf: headerBuf,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +65,10 @@ func (w *requestWriter) writeHeaders(wr io.Writer, req *http.Request, gzip bool)
|
||||
return err
|
||||
}
|
||||
|
||||
func isExtendedConnectRequest(req *http.Request) bool {
|
||||
return req.Method == http.MethodConnect && req.Proto != "" && req.Proto != "HTTP/1.1"
|
||||
}
|
||||
|
||||
// copied from net/transport.go
|
||||
// Modified to support Extended CONNECT:
|
||||
// Contrary to what the godoc for the http.Request says,
|
||||
@ -87,7 +87,7 @@ func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, tra
|
||||
}
|
||||
|
||||
// http.NewRequest sets this field to HTTP/1.1
|
||||
isExtendedConnect := req.Method == http.MethodConnect && req.Proto != "" && req.Proto != "HTTP/1.1"
|
||||
isExtendedConnect := isExtendedConnectRequest(req)
|
||||
|
||||
var path string
|
||||
if req.Method != http.MethodConnect || isExtendedConnect {
|
||||
@ -215,13 +215,10 @@ func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, tra
|
||||
|
||||
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
|
||||
// and returns a host:port. The port 443 is added if needed.
|
||||
func authorityAddr(scheme string, authority string) (addr string) {
|
||||
func authorityAddr(authority string) (addr string) {
|
||||
host, port, err := net.SplitHostPort(authority)
|
||||
if err != nil { // authority didn't have a port
|
||||
port = "443"
|
||||
if scheme == "http" {
|
||||
port = "80"
|
||||
}
|
||||
host = authority
|
||||
}
|
||||
if a, err := idna.ToASCII(host); err == nil {
|
||||
|
276
vendor/github.com/quic-go/quic-go/http3/response_writer.go
generated
vendored
276
vendor/github.com/quic-go/quic-go/http3/response_writer.go
generated
vendored
@ -1,95 +1,68 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
)
|
||||
|
||||
// The HTTPStreamer allows taking over a HTTP/3 stream. The interface is implemented the http.Response.Body.
|
||||
// On the client side, the stream will be closed for writing, unless the DontCloseRequestStream RoundTripOpt was set.
|
||||
// When a stream is taken over, it's the caller's responsibility to close the stream.
|
||||
type HTTPStreamer interface {
|
||||
HTTPStream() Stream
|
||||
}
|
||||
|
||||
// The maximum length of an encoded HTTP/3 frame header is 16:
|
||||
// The frame has a type and length field, both QUIC varints (maximum 8 bytes in length)
|
||||
const frameHeaderLen = 16
|
||||
|
||||
// headerWriter wraps the stream, so that the first Write call flushes the header to the stream
|
||||
type headerWriter struct {
|
||||
str quic.Stream
|
||||
header http.Header
|
||||
status int // status code passed to WriteHeader
|
||||
written bool
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
// writeHeader encodes and flush header to the stream
|
||||
func (hw *headerWriter) writeHeader() error {
|
||||
var headers bytes.Buffer
|
||||
enc := qpack.NewEncoder(&headers)
|
||||
enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(hw.status)})
|
||||
|
||||
for k, v := range hw.header {
|
||||
for index := range v {
|
||||
enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]})
|
||||
}
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, frameHeaderLen+headers.Len())
|
||||
buf = (&headersFrame{Length: uint64(headers.Len())}).Append(buf)
|
||||
hw.logger.Infof("Responding with %d", hw.status)
|
||||
buf = append(buf, headers.Bytes()...)
|
||||
|
||||
_, err := hw.str.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// first Write will trigger flushing header
|
||||
func (hw *headerWriter) Write(p []byte) (int, error) {
|
||||
if !hw.written {
|
||||
if err := hw.writeHeader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hw.written = true
|
||||
}
|
||||
return hw.str.Write(p)
|
||||
}
|
||||
const maxSmallResponseSize = 4096
|
||||
|
||||
type responseWriter struct {
|
||||
*headerWriter
|
||||
conn quic.Connection
|
||||
bufferedStr *bufio.Writer
|
||||
buf []byte
|
||||
str *stream
|
||||
|
||||
contentLen int64 // if handler set valid Content-Length header
|
||||
numWritten int64 // bytes written
|
||||
headerWritten bool
|
||||
isHead bool
|
||||
conn Connection
|
||||
header http.Header
|
||||
buf []byte
|
||||
status int // status code passed to WriteHeader
|
||||
|
||||
// for responses smaller than maxSmallResponseSize, we buffer calls to Write,
|
||||
// and automatically add the Content-Length header
|
||||
smallResponseBuf []byte
|
||||
|
||||
contentLen int64 // if handler set valid Content-Length header
|
||||
numWritten int64 // bytes written
|
||||
headerComplete bool // set once WriteHeader is called with a status code >= 200
|
||||
headerWritten bool // set once the response header has been serialized to the stream
|
||||
isHead bool
|
||||
|
||||
hijacked bool // set on HTTPStream is called
|
||||
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
_ http.ResponseWriter = &responseWriter{}
|
||||
_ http.Flusher = &responseWriter{}
|
||||
_ Hijacker = &responseWriter{}
|
||||
_ HTTPStreamer = &responseWriter{}
|
||||
)
|
||||
|
||||
func newResponseWriter(str quic.Stream, conn quic.Connection, logger utils.Logger) *responseWriter {
|
||||
hw := &headerWriter{
|
||||
str: str,
|
||||
header: http.Header{},
|
||||
logger: logger,
|
||||
}
|
||||
func newResponseWriter(str *stream, conn Connection, isHead bool, logger *slog.Logger) *responseWriter {
|
||||
return &responseWriter{
|
||||
headerWriter: hw,
|
||||
buf: make([]byte, frameHeaderLen),
|
||||
conn: conn,
|
||||
bufferedStr: bufio.NewWriter(hw),
|
||||
str: str,
|
||||
conn: conn,
|
||||
header: http.Header{},
|
||||
buf: make([]byte, frameHeaderLen),
|
||||
isHead: isHead,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +71,7 @@ func (w *responseWriter) Header() http.Header {
|
||||
}
|
||||
|
||||
func (w *responseWriter) WriteHeader(status int) {
|
||||
if w.headerWritten {
|
||||
if w.headerComplete {
|
||||
return
|
||||
}
|
||||
|
||||
@ -106,51 +79,57 @@ func (w *responseWriter) WriteHeader(status int) {
|
||||
if status < 100 || status > 999 {
|
||||
panic(fmt.Sprintf("invalid WriteHeader code %v", status))
|
||||
}
|
||||
|
||||
if status >= 200 {
|
||||
w.headerWritten = true
|
||||
// Add Date header.
|
||||
// This is what the standard library does.
|
||||
// Can be disabled by setting the Date header to nil.
|
||||
if _, ok := w.header["Date"]; !ok {
|
||||
w.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
}
|
||||
// Content-Length checking
|
||||
// use ParseUint instead of ParseInt, as negative values are invalid
|
||||
if clen := w.header.Get("Content-Length"); clen != "" {
|
||||
if cl, err := strconv.ParseUint(clen, 10, 63); err == nil {
|
||||
w.contentLen = int64(cl)
|
||||
} else {
|
||||
// emit a warning for malformed Content-Length and remove it
|
||||
w.logger.Errorf("Malformed Content-Length %s", clen)
|
||||
w.header.Del("Content-Length")
|
||||
}
|
||||
}
|
||||
}
|
||||
w.status = status
|
||||
|
||||
if !w.headerWritten {
|
||||
w.writeHeader()
|
||||
// immediately write 1xx headers
|
||||
if status < 200 {
|
||||
w.writeHeader(status)
|
||||
return
|
||||
}
|
||||
|
||||
// We're done with headers once we write a status >= 200.
|
||||
w.headerComplete = true
|
||||
// Add Date header.
|
||||
// This is what the standard library does.
|
||||
// Can be disabled by setting the Date header to nil.
|
||||
if _, ok := w.header["Date"]; !ok {
|
||||
w.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
}
|
||||
// Content-Length checking
|
||||
// use ParseUint instead of ParseInt, as negative values are invalid
|
||||
if clen := w.header.Get("Content-Length"); clen != "" {
|
||||
if cl, err := strconv.ParseUint(clen, 10, 63); err == nil {
|
||||
w.contentLen = int64(cl)
|
||||
} else {
|
||||
// emit a warning for malformed Content-Length and remove it
|
||||
logger := w.logger
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
logger.Error("Malformed Content-Length", "value", clen)
|
||||
w.header.Del("Content-Length")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseWriter) sniffContentType(p []byte) {
|
||||
// If no content type, apply sniffing algorithm to body.
|
||||
// We can't use `w.header.Get` here since if the Content-Type was set to nil, we shouldn't do sniffing.
|
||||
_, haveType := w.header["Content-Type"]
|
||||
|
||||
// If the Transfer-Encoding or Content-Encoding was set and is non-blank,
|
||||
// we shouldn't sniff the body.
|
||||
hasTE := w.header.Get("Transfer-Encoding") != ""
|
||||
hasCE := w.header.Get("Content-Encoding") != ""
|
||||
if !hasCE && !haveType && !hasTE && len(p) > 0 {
|
||||
w.header.Set("Content-Type", http.DetectContentType(p))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseWriter) Write(p []byte) (int, error) {
|
||||
bodyAllowed := bodyAllowedForStatus(w.status)
|
||||
if !w.headerWritten {
|
||||
// If body is not allowed, we don't need to (and we can't) sniff the content type.
|
||||
if bodyAllowed {
|
||||
// If no content type, apply sniffing algorithm to body.
|
||||
// We can't use `w.header.Get` here since if the Content-Type was set to nil, we shoundn't do sniffing.
|
||||
_, haveType := w.header["Content-Type"]
|
||||
|
||||
// If the Transfer-Encoding or Content-Encoding was set and is non-blank,
|
||||
// we shouldn't sniff the body.
|
||||
hasTE := w.header.Get("Transfer-Encoding") != ""
|
||||
hasCE := w.header.Get("Content-Encoding") != ""
|
||||
if !hasCE && !haveType && !hasTE && len(p) > 0 {
|
||||
w.header.Set("Content-Type", http.DetectContentType(p))
|
||||
}
|
||||
}
|
||||
if !w.headerComplete {
|
||||
w.sniffContentType(p)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
bodyAllowed = true
|
||||
}
|
||||
@ -167,36 +146,101 @@ func (w *responseWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
df := &dataFrame{Length: uint64(len(p))}
|
||||
if !w.headerWritten {
|
||||
// Buffer small responses.
|
||||
// This allows us to automatically set the Content-Length field.
|
||||
if len(w.smallResponseBuf)+len(p) < maxSmallResponseSize {
|
||||
w.smallResponseBuf = append(w.smallResponseBuf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
}
|
||||
return w.doWrite(p)
|
||||
}
|
||||
|
||||
func (w *responseWriter) doWrite(p []byte) (int, error) {
|
||||
if !w.headerWritten {
|
||||
w.sniffContentType(w.smallResponseBuf)
|
||||
if err := w.writeHeader(w.status); err != nil {
|
||||
return 0, maybeReplaceError(err)
|
||||
}
|
||||
w.headerWritten = true
|
||||
}
|
||||
|
||||
l := uint64(len(w.smallResponseBuf) + len(p))
|
||||
if l == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
df := &dataFrame{Length: l}
|
||||
w.buf = w.buf[:0]
|
||||
w.buf = df.Append(w.buf)
|
||||
if _, err := w.bufferedStr.Write(w.buf); err != nil {
|
||||
if _, err := w.str.writeUnframed(w.buf); err != nil {
|
||||
return 0, maybeReplaceError(err)
|
||||
}
|
||||
n, err := w.bufferedStr.Write(p)
|
||||
return n, maybeReplaceError(err)
|
||||
if len(w.smallResponseBuf) > 0 {
|
||||
if _, err := w.str.writeUnframed(w.smallResponseBuf); err != nil {
|
||||
return 0, maybeReplaceError(err)
|
||||
}
|
||||
w.smallResponseBuf = nil
|
||||
}
|
||||
var n int
|
||||
if len(p) > 0 {
|
||||
var err error
|
||||
n, err = w.str.writeUnframed(p)
|
||||
if err != nil {
|
||||
return n, maybeReplaceError(err)
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *responseWriter) writeHeader(status int) error {
|
||||
var headers bytes.Buffer
|
||||
enc := qpack.NewEncoder(&headers)
|
||||
if err := enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range w.header {
|
||||
for index := range v {
|
||||
if err := enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, frameHeaderLen+headers.Len())
|
||||
buf = (&headersFrame{Length: uint64(headers.Len())}).Append(buf)
|
||||
buf = append(buf, headers.Bytes()...)
|
||||
|
||||
_, err := w.str.writeUnframed(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *responseWriter) FlushError() error {
|
||||
if !w.headerWritten {
|
||||
if !w.headerComplete {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if !w.written {
|
||||
if err := w.writeHeader(); err != nil {
|
||||
return maybeReplaceError(err)
|
||||
}
|
||||
w.written = true
|
||||
}
|
||||
return w.bufferedStr.Flush()
|
||||
_, err := w.doWrite(nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *responseWriter) Flush() {
|
||||
if err := w.FlushError(); err != nil {
|
||||
w.logger.Errorf("could not flush to stream: %s", err.Error())
|
||||
if w.logger != nil {
|
||||
w.logger.Debug("could not flush to stream", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseWriter) StreamCreator() StreamCreator {
|
||||
func (w *responseWriter) HTTPStream() Stream {
|
||||
w.hijacked = true
|
||||
w.Flush()
|
||||
return w.str
|
||||
}
|
||||
|
||||
func (w *responseWriter) wasStreamHijacked() bool { return w.hijacked }
|
||||
|
||||
func (w *responseWriter) Connection() Connection {
|
||||
return w.conn
|
||||
}
|
||||
|
||||
|
291
vendor/github.com/quic-go/quic-go/http3/roundtrip.go
generated
vendored
291
vendor/github.com/quic-go/quic-go/http3/roundtrip.go
generated
vendored
@ -15,12 +15,13 @@
|
||||
"golang.org/x/net/http/httpguts"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
// Settings are HTTP/3 settings that apply to the underlying connection.
|
||||
type Settings struct {
|
||||
// Support for HTTP/3 datagrams (RFC 9297)
|
||||
EnableDatagram bool
|
||||
EnableDatagrams bool
|
||||
// Extended CONNECT, RFC 9220
|
||||
EnableExtendedConnect bool
|
||||
// Other settings, defined by the application
|
||||
@ -32,69 +33,43 @@ type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
|
||||
// If set, context cancellations have no effect after the response headers are received.
|
||||
DontCloseRequestStream bool
|
||||
// CheckSettings is run before the request is sent to the server.
|
||||
// If not yet received, it blocks until the server's SETTINGS frame is received.
|
||||
// If an error is returned, the request won't be sent to the server, and the error is returned.
|
||||
CheckSettings func(Settings) error
|
||||
}
|
||||
|
||||
type roundTripCloser interface {
|
||||
RoundTripOpt(*http.Request, RoundTripOpt) (*http.Response, error)
|
||||
HandshakeComplete() bool
|
||||
io.Closer
|
||||
type singleRoundTripper interface {
|
||||
OpenRequestStream(context.Context) (RequestStream, error)
|
||||
RoundTrip(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type roundTripCloserWithCount struct {
|
||||
roundTripCloser
|
||||
type roundTripperWithCount struct {
|
||||
cancel context.CancelFunc
|
||||
dialing chan struct{} // closed as soon as quic.Dial(Early) returned
|
||||
dialErr error
|
||||
conn quic.EarlyConnection
|
||||
rt singleRoundTripper
|
||||
|
||||
useCount atomic.Int64
|
||||
}
|
||||
|
||||
func (r *roundTripperWithCount) Close() error {
|
||||
r.cancel()
|
||||
<-r.dialing
|
||||
if r.conn != nil {
|
||||
return r.conn.CloseWithError(0, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoundTripper implements the http.RoundTripper interface
|
||||
type RoundTripper struct {
|
||||
mutex sync.Mutex
|
||||
|
||||
// DisableCompression, if true, prevents the Transport from
|
||||
// requesting compression with an "Accept-Encoding: gzip"
|
||||
// request header when the Request contains no existing
|
||||
// Accept-Encoding value. If the Transport requests gzip on
|
||||
// its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body. However, if the user
|
||||
// explicitly requested gzip it is not automatically
|
||||
// uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with
|
||||
// tls.Client. If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// QuicConfig is the quic.Config used for dialing new connections.
|
||||
// QUICConfig is the quic.Config used for dialing new connections.
|
||||
// If nil, reasonable default values will be used.
|
||||
QuicConfig *quic.Config
|
||||
|
||||
// Enable support for HTTP/3 datagrams (RFC 9297).
|
||||
// If a QuicConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
|
||||
EnableDatagrams bool
|
||||
|
||||
// Additional HTTP/3 settings.
|
||||
// It is invalid to specify any settings defined by the HTTP/3 draft and the datagram draft.
|
||||
AdditionalSettings map[uint64]uint64
|
||||
|
||||
// When set, this callback is called for the first unknown frame parsed on a bidirectional stream.
|
||||
// It is called right after parsing the frame type.
|
||||
// If parsing the frame type fails, the error is passed to the callback.
|
||||
// In that case, the frame type will not be set.
|
||||
// Callers can either ignore the frame and return control of the stream back to HTTP/3
|
||||
// (by returning hijacked false).
|
||||
// Alternatively, callers can take over the QUIC stream (by returning hijacked true).
|
||||
StreamHijacker func(FrameType, quic.Connection, quic.Stream, error) (hijacked bool, err error)
|
||||
|
||||
// When set, this callback is called for unknown unidirectional stream of unknown stream type.
|
||||
// If parsing the stream type fails, the error is passed to the callback.
|
||||
// In that case, the stream type will not be set.
|
||||
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
||||
QUICConfig *quic.Config
|
||||
|
||||
// Dial specifies an optional dial function for creating QUIC
|
||||
// connections for requests.
|
||||
@ -102,13 +77,32 @@ type RoundTripper struct {
|
||||
// and will be reused for subsequent connections to other servers.
|
||||
Dial func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error)
|
||||
|
||||
// Enable support for HTTP/3 datagrams (RFC 9297).
|
||||
// If a QUICConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
|
||||
EnableDatagrams bool
|
||||
|
||||
// Additional HTTP/3 settings.
|
||||
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
|
||||
AdditionalSettings map[uint64]uint64
|
||||
|
||||
// MaxResponseHeaderBytes specifies a limit on how many response bytes are
|
||||
// allowed in the server's response header.
|
||||
// Zero means to use a default limit.
|
||||
MaxResponseHeaderBytes int64
|
||||
|
||||
newClient func(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) // so we can mock it in tests
|
||||
clients map[string]*roundTripCloserWithCount
|
||||
// DisableCompression, if true, prevents the Transport from requesting compression with an
|
||||
// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
|
||||
// If the Transport requests gzip on its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body.
|
||||
// However, if the user explicitly requested gzip it is not automatically uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
|
||||
newClient func(quic.EarlyConnection) singleRoundTripper
|
||||
|
||||
clients map[string]*roundTripperWithCount
|
||||
transport *quic.Transport
|
||||
}
|
||||
|
||||
@ -122,6 +116,11 @@ type RoundTripper struct {
|
||||
|
||||
// RoundTripOpt is like RoundTrip, but takes options.
|
||||
func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
r.initOnce.Do(func() { r.initErr = r.init() })
|
||||
if r.initErr != nil {
|
||||
return nil, r.initErr
|
||||
}
|
||||
|
||||
if req.URL == nil {
|
||||
closeRequestBody(req)
|
||||
return nil, errors.New("http3: nil Request.URL")
|
||||
@ -154,15 +153,31 @@ func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.
|
||||
return nil, fmt.Errorf("http3: invalid method %q", req.Method)
|
||||
}
|
||||
|
||||
hostname := authorityAddr("https", hostnameFromRequest(req))
|
||||
cl, isReused, err := r.getClient(hostname, opt.OnlyCachedConn)
|
||||
hostname := authorityAddr(hostnameFromURL(req.URL))
|
||||
cl, isReused, err := r.getClient(req.Context(), hostname, opt.OnlyCachedConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-cl.dialing:
|
||||
case <-req.Context().Done():
|
||||
return nil, context.Cause(req.Context())
|
||||
}
|
||||
|
||||
if cl.dialErr != nil {
|
||||
return nil, cl.dialErr
|
||||
}
|
||||
defer cl.useCount.Add(-1)
|
||||
rsp, err := cl.RoundTripOpt(req, opt)
|
||||
rsp, err := cl.rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
r.removeClient(hostname)
|
||||
// non-nil errors on roundtrip are likely due to a problem with the connection
|
||||
// so we remove the client from the cache so that subsequent trips reconnect
|
||||
// context cancelation is excluded as is does not signify a connection error
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
r.removeClient(hostname)
|
||||
}
|
||||
|
||||
if isReused {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
return r.RoundTripOpt(req, opt)
|
||||
@ -177,59 +192,126 @@ func (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return r.RoundTripOpt(req, RoundTripOpt{})
|
||||
}
|
||||
|
||||
func (r *RoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTripCloserWithCount, isReused bool, err error) {
|
||||
func (r *RoundTripper) init() error {
|
||||
if r.newClient == nil {
|
||||
r.newClient = func(conn quic.EarlyConnection) singleRoundTripper {
|
||||
return &SingleDestinationRoundTripper{
|
||||
Connection: conn,
|
||||
EnableDatagrams: r.EnableDatagrams,
|
||||
DisableCompression: r.DisableCompression,
|
||||
AdditionalSettings: r.AdditionalSettings,
|
||||
MaxResponseHeaderBytes: r.MaxResponseHeaderBytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.QUICConfig == nil {
|
||||
r.QUICConfig = defaultQuicConfig.Clone()
|
||||
r.QUICConfig.EnableDatagrams = r.EnableDatagrams
|
||||
}
|
||||
if r.EnableDatagrams && !r.QUICConfig.EnableDatagrams {
|
||||
return errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
|
||||
}
|
||||
if len(r.QUICConfig.Versions) == 0 {
|
||||
r.QUICConfig = r.QUICConfig.Clone()
|
||||
r.QUICConfig.Versions = []quic.Version{protocol.SupportedVersions[0]}
|
||||
}
|
||||
if len(r.QUICConfig.Versions) != 1 {
|
||||
return errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
|
||||
}
|
||||
if r.QUICConfig.MaxIncomingStreams == 0 {
|
||||
r.QUICConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RoundTripper) getClient(ctx context.Context, hostname string, onlyCached bool) (rtc *roundTripperWithCount, isReused bool, err error) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
if r.clients == nil {
|
||||
r.clients = make(map[string]*roundTripCloserWithCount)
|
||||
r.clients = make(map[string]*roundTripperWithCount)
|
||||
}
|
||||
|
||||
client, ok := r.clients[hostname]
|
||||
cl, ok := r.clients[hostname]
|
||||
if !ok {
|
||||
if onlyCached {
|
||||
return nil, false, ErrNoCachedConn
|
||||
}
|
||||
var err error
|
||||
newCl := newClient
|
||||
if r.newClient != nil {
|
||||
newCl = r.newClient
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
cl = &roundTripperWithCount{
|
||||
dialing: make(chan struct{}),
|
||||
cancel: cancel,
|
||||
}
|
||||
dial := r.Dial
|
||||
if dial == nil {
|
||||
if r.transport == nil {
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
r.transport = &quic.Transport{Conn: udpConn}
|
||||
go func() {
|
||||
defer close(cl.dialing)
|
||||
defer cancel()
|
||||
conn, rt, err := r.dial(ctx, hostname)
|
||||
if err != nil {
|
||||
cl.dialErr = err
|
||||
return
|
||||
}
|
||||
dial = r.makeDialer()
|
||||
}
|
||||
c, err := newCl(
|
||||
hostname,
|
||||
r.TLSClientConfig,
|
||||
&roundTripperOpts{
|
||||
EnableDatagram: r.EnableDatagrams,
|
||||
DisableCompression: r.DisableCompression,
|
||||
MaxHeaderBytes: r.MaxResponseHeaderBytes,
|
||||
StreamHijacker: r.StreamHijacker,
|
||||
UniStreamHijacker: r.UniStreamHijacker,
|
||||
AdditionalSettings: r.AdditionalSettings,
|
||||
},
|
||||
r.QuicConfig,
|
||||
dial,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
client = &roundTripCloserWithCount{roundTripCloser: c}
|
||||
r.clients[hostname] = client
|
||||
} else if client.HandshakeComplete() {
|
||||
isReused = true
|
||||
cl.conn = conn
|
||||
cl.rt = rt
|
||||
}()
|
||||
r.clients[hostname] = cl
|
||||
}
|
||||
client.useCount.Add(1)
|
||||
return client, isReused, nil
|
||||
select {
|
||||
case <-cl.dialing:
|
||||
if cl.dialErr != nil {
|
||||
return nil, false, cl.dialErr
|
||||
}
|
||||
select {
|
||||
case <-cl.conn.HandshakeComplete():
|
||||
isReused = true
|
||||
default:
|
||||
}
|
||||
default:
|
||||
}
|
||||
cl.useCount.Add(1)
|
||||
return cl, isReused, nil
|
||||
}
|
||||
|
||||
func (r *RoundTripper) dial(ctx context.Context, hostname string) (quic.EarlyConnection, singleRoundTripper, error) {
|
||||
var tlsConf *tls.Config
|
||||
if r.TLSClientConfig == nil {
|
||||
tlsConf = &tls.Config{}
|
||||
} else {
|
||||
tlsConf = r.TLSClientConfig.Clone()
|
||||
}
|
||||
if tlsConf.ServerName == "" {
|
||||
sni, _, err := net.SplitHostPort(hostname)
|
||||
if err != nil {
|
||||
// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
|
||||
sni = hostname
|
||||
}
|
||||
tlsConf.ServerName = sni
|
||||
}
|
||||
// Replace existing ALPNs by H3
|
||||
tlsConf.NextProtos = []string{versionToALPN(r.QUICConfig.Versions[0])}
|
||||
|
||||
dial := r.Dial
|
||||
if dial == nil {
|
||||
if r.transport == nil {
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r.transport = &quic.Transport{Conn: udpConn}
|
||||
}
|
||||
dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := dial(ctx, hostname, tlsConf, r.QUICConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, r.newClient(conn), nil
|
||||
}
|
||||
|
||||
func (r *RoundTripper) removeClient(hostname string) {
|
||||
@ -246,8 +328,8 @@ func (r *RoundTripper) removeClient(hostname string) {
|
||||
func (r *RoundTripper) Close() error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
for _, client := range r.clients {
|
||||
if err := client.Close(); err != nil {
|
||||
for _, cl := range r.clients {
|
||||
if err := cl.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -292,23 +374,12 @@ func isNotToken(r rune) bool {
|
||||
return !httpguts.IsTokenRune(r)
|
||||
}
|
||||
|
||||
// makeDialer makes a QUIC dialer using r.udpConn.
|
||||
func (r *RoundTripper) makeDialer() func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
return func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RoundTripper) CloseIdleConnections() {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
for hostname, client := range r.clients {
|
||||
if client.useCount.Load() == 0 {
|
||||
client.Close()
|
||||
for hostname, cl := range r.clients {
|
||||
if cl.useCount.Load() == 0 {
|
||||
cl.Close()
|
||||
delete(r.clients, hostname)
|
||||
}
|
||||
}
|
||||
|
277
vendor/github.com/quic-go/quic-go/http3/server.go
generated
vendored
277
vendor/github.com/quic-go/quic-go/http3/server.go
generated
vendored
@ -6,18 +6,17 @@
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
|
||||
"github.com/quic-go/qpack"
|
||||
@ -31,7 +30,6 @@
|
||||
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
|
||||
return quic.ListenAddrEarly(addr, tlsConf, config)
|
||||
}
|
||||
errPanicked = errors.New("panicked")
|
||||
)
|
||||
|
||||
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
|
||||
@ -127,20 +125,6 @@ func (k *contextKey) String() string { return "quic-go/http3 context value " + k
|
||||
// than its string representation.
|
||||
var RemoteAddrContextKey = &contextKey{"remote-addr"}
|
||||
|
||||
type requestError struct {
|
||||
err error
|
||||
streamErr ErrCode
|
||||
connErr ErrCode
|
||||
}
|
||||
|
||||
func newStreamError(code ErrCode, err error) requestError {
|
||||
return requestError{err: err, streamErr: code}
|
||||
}
|
||||
|
||||
func newConnError(code ErrCode, err error) requestError {
|
||||
return requestError{err: err, connErr: code}
|
||||
}
|
||||
|
||||
// listenerInfo contains info about specific listener added with addListener
|
||||
type listenerInfo struct {
|
||||
port int // 0 means that no info about port is available
|
||||
@ -157,10 +141,10 @@ type Server struct {
|
||||
//
|
||||
// Otherwise, if Port is not set and underlying QUIC listeners do not
|
||||
// have valid port numbers, the port part is used in Alt-Svc headers set
|
||||
// with SetQuicHeaders.
|
||||
// with SetQUICHeaders.
|
||||
Addr string
|
||||
|
||||
// Port is used in Alt-Svc response headers set with SetQuicHeaders. If
|
||||
// Port is used in Alt-Svc response headers set with SetQUICHeaders. If
|
||||
// needed Port can be manually set when the Server is created.
|
||||
//
|
||||
// This is useful when a Layer 4 firewall is redirecting UDP traffic and
|
||||
@ -172,20 +156,18 @@ type Server struct {
|
||||
// set for ListenAndServe and Serve methods.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// QuicConfig provides the parameters for QUIC connection created with
|
||||
// Serve. If nil, it uses reasonable default values.
|
||||
// QUICConfig provides the parameters for QUIC connection created with Serve.
|
||||
// If nil, it uses reasonable default values.
|
||||
//
|
||||
// Configured versions are also used in Alt-Svc response header set with
|
||||
// SetQuicHeaders.
|
||||
QuicConfig *quic.Config
|
||||
// Configured versions are also used in Alt-Svc response header set with SetQUICHeaders.
|
||||
QUICConfig *quic.Config
|
||||
|
||||
// Handler is the HTTP request handler to use. If not set, defaults to
|
||||
// http.NotFound.
|
||||
Handler http.Handler
|
||||
|
||||
// EnableDatagrams enables support for HTTP/3 datagrams.
|
||||
// If set to true, QuicConfig.EnableDatagram will be set.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc9297.
|
||||
// EnableDatagrams enables support for HTTP/3 datagrams (RFC 9297).
|
||||
// If set to true, QUICConfig.EnableDatagrams will be set.
|
||||
EnableDatagrams bool
|
||||
|
||||
// MaxHeaderBytes controls the maximum number of bytes the server will
|
||||
@ -195,7 +177,7 @@ type Server struct {
|
||||
MaxHeaderBytes int
|
||||
|
||||
// AdditionalSettings specifies additional HTTP/3 settings.
|
||||
// It is invalid to specify any settings defined by the HTTP/3 draft and the datagram draft.
|
||||
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
|
||||
AdditionalSettings map[uint64]uint64
|
||||
|
||||
// StreamHijacker, when set, is called for the first unknown frame parsed on a bidirectional stream.
|
||||
@ -205,26 +187,26 @@ type Server struct {
|
||||
// Callers can either ignore the frame and return control of the stream back to HTTP/3
|
||||
// (by returning hijacked false).
|
||||
// Alternatively, callers can take over the QUIC stream (by returning hijacked true).
|
||||
StreamHijacker func(FrameType, quic.Connection, quic.Stream, error) (hijacked bool, err error)
|
||||
StreamHijacker func(FrameType, quic.ConnectionTracingID, quic.Stream, error) (hijacked bool, err error)
|
||||
|
||||
// UniStreamHijacker, when set, is called for unknown unidirectional stream of unknown stream type.
|
||||
// If parsing the stream type fails, the error is passed to the callback.
|
||||
// In that case, the stream type will not be set.
|
||||
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
||||
UniStreamHijacker func(StreamType, quic.ConnectionTracingID, quic.ReceiveStream, error) (hijacked bool)
|
||||
|
||||
// ConnContext optionally specifies a function that modifies
|
||||
// the context used for a new connection c. The provided ctx
|
||||
// has a ServerContextKey value.
|
||||
ConnContext func(ctx context.Context, c quic.Connection) context.Context
|
||||
|
||||
Logger *slog.Logger
|
||||
|
||||
mutex sync.RWMutex
|
||||
listeners map[*QUICEarlyListener]listenerInfo
|
||||
|
||||
closed bool
|
||||
|
||||
altSvcHeader string
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
|
||||
@ -261,12 +243,6 @@ func (s *Server) Serve(conn net.PacketConn) error {
|
||||
|
||||
// ServeQUICConn serves a single QUIC connection.
|
||||
func (s *Server) ServeQUICConn(conn quic.Connection) error {
|
||||
s.mutex.Lock()
|
||||
if s.logger == nil {
|
||||
s.logger = utils.DefaultLogger.WithPrefix("server")
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
|
||||
return s.handleConn(conn)
|
||||
}
|
||||
|
||||
@ -290,7 +266,9 @@ func (s *Server) ServeListener(ln QUICEarlyListener) error {
|
||||
}
|
||||
go func() {
|
||||
if err := s.handleConn(conn); err != nil {
|
||||
s.logger.Debugf("handling connection failed: %s", err)
|
||||
if s.Logger != nil {
|
||||
s.Logger.Debug("handling connection failed", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -311,11 +289,11 @@ func (s *Server) serveConn(tlsConf *tls.Config, conn net.PacketConn) error {
|
||||
}
|
||||
|
||||
baseConf := ConfigureTLSConfig(tlsConf)
|
||||
quicConf := s.QuicConfig
|
||||
quicConf := s.QUICConfig
|
||||
if quicConf == nil {
|
||||
quicConf = &quic.Config{Allow0RTT: true}
|
||||
} else {
|
||||
quicConf = s.QuicConfig.Clone()
|
||||
quicConf = s.QUICConfig.Clone()
|
||||
}
|
||||
if s.EnableDatagrams {
|
||||
quicConf.EnableDatagrams = true
|
||||
@ -360,8 +338,8 @@ func (s *Server) generateAltSvcHeader() {
|
||||
|
||||
// This code assumes that we will use protocol.SupportedVersions if no quic.Config is passed.
|
||||
supportedVersions := protocol.SupportedVersions
|
||||
if s.QuicConfig != nil && len(s.QuicConfig.Versions) > 0 {
|
||||
supportedVersions = s.QuicConfig.Versions
|
||||
if s.QUICConfig != nil && len(s.QUICConfig.Versions) > 0 {
|
||||
supportedVersions = s.QUICConfig.Versions
|
||||
}
|
||||
|
||||
// keep track of which have been seen so we don't yield duplicate values
|
||||
@ -417,9 +395,6 @@ func (s *Server) addListener(l *QUICEarlyListener) error {
|
||||
if s.closed {
|
||||
return http.ErrServerClosed
|
||||
}
|
||||
if s.logger == nil {
|
||||
s.logger = utils.DefaultLogger.WithPrefix("server")
|
||||
}
|
||||
if s.listeners == nil {
|
||||
s.listeners = make(map[*QUICEarlyListener]listenerInfo)
|
||||
}
|
||||
@ -428,7 +403,11 @@ func (s *Server) addListener(l *QUICEarlyListener) error {
|
||||
if port, err := extractPort(laddr.String()); err == nil {
|
||||
s.listeners[l] = listenerInfo{port}
|
||||
} else {
|
||||
s.logger.Errorf("Unable to extract port from listener %s, will not be announced using SetQuicHeaders: %s", laddr, err)
|
||||
logger := s.Logger
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
logger.Error("Unable to extract port from listener, will not be announced using SetQUICHeaders", "local addr", laddr, "error", err)
|
||||
s.listeners[l] = listenerInfo{}
|
||||
}
|
||||
s.generateAltSvcHeader()
|
||||
@ -443,8 +422,6 @@ func (s *Server) removeListener(l *QUICEarlyListener) {
|
||||
}
|
||||
|
||||
func (s *Server) handleConn(conn quic.Connection) error {
|
||||
decoder := qpack.NewDecoder(nil)
|
||||
|
||||
// send a SETTINGS frame
|
||||
str, err := conn.OpenUniStream()
|
||||
if err != nil {
|
||||
@ -459,12 +436,17 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
||||
}).Append(b)
|
||||
str.Write(b)
|
||||
|
||||
go s.handleUnidirectionalStreams(conn)
|
||||
|
||||
hconn := newConnection(
|
||||
conn,
|
||||
s.EnableDatagrams,
|
||||
protocol.PerspectiveServer,
|
||||
s.Logger,
|
||||
)
|
||||
go hconn.HandleUnidirectionalStreams(s.UniStreamHijacker)
|
||||
// Process all requests immediately.
|
||||
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
|
||||
for {
|
||||
str, err := conn.AcceptStream(context.Background())
|
||||
str, datagrams, err := hconn.acceptStream(context.Background())
|
||||
if err != nil {
|
||||
var appErr *quic.ApplicationError
|
||||
if errors.As(err, &appErr) && appErr.ErrorCode == quic.ApplicationErrorCode(ErrCodeNoError) {
|
||||
@ -472,93 +454,7 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
||||
}
|
||||
return fmt.Errorf("accepting stream failed: %w", err)
|
||||
}
|
||||
go func() {
|
||||
rerr := s.handleRequest(conn, str, decoder, func() {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
|
||||
})
|
||||
if rerr.err == errHijacked {
|
||||
return
|
||||
}
|
||||
if rerr.err != nil || rerr.streamErr != 0 || rerr.connErr != 0 {
|
||||
s.logger.Debugf("Handling request failed: %s", err)
|
||||
if rerr.streamErr != 0 {
|
||||
str.CancelWrite(quic.StreamErrorCode(rerr.streamErr))
|
||||
}
|
||||
if rerr.connErr != 0 {
|
||||
var reason string
|
||||
if rerr.err != nil {
|
||||
reason = rerr.err.Error()
|
||||
}
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason)
|
||||
}
|
||||
return
|
||||
}
|
||||
str.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
s.logger.Debugf("accepting unidirectional stream failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func(str quic.ReceiveStream) {
|
||||
streamType, err := quicvarint.Read(quicvarint.NewReader(str))
|
||||
if err != nil {
|
||||
if s.UniStreamHijacker != nil && s.UniStreamHijacker(StreamType(streamType), conn, str, err) {
|
||||
return
|
||||
}
|
||||
s.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err)
|
||||
return
|
||||
}
|
||||
// We're only interested in the control stream here.
|
||||
switch streamType {
|
||||
case streamTypeControlStream:
|
||||
case streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream:
|
||||
// Our QPACK implementation doesn't use the dynamic table yet.
|
||||
// TODO: check that only one stream of each type is opened.
|
||||
return
|
||||
case streamTypePushStream: // only the server can push
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
|
||||
return
|
||||
default:
|
||||
if s.UniStreamHijacker != nil && s.UniStreamHijacker(StreamType(streamType), conn, str, nil) {
|
||||
return
|
||||
}
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
return
|
||||
}
|
||||
sf, ok := f.(*settingsFrame)
|
||||
if !ok {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
|
||||
return
|
||||
}
|
||||
if !sf.Datagram {
|
||||
return
|
||||
}
|
||||
// If datagram support was enabled on our side as well as on the client side,
|
||||
// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
|
||||
// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
|
||||
if s.EnableDatagrams && !conn.ConnectionState().SupportsDatagrams {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
|
||||
}
|
||||
}(str)
|
||||
go s.handleRequest(hconn, str, datagrams, hconn.decoder)
|
||||
}
|
||||
}
|
||||
|
||||
@ -569,37 +465,53 @@ func (s *Server) maxHeaderBytes() uint64 {
|
||||
return uint64(s.MaxHeaderBytes)
|
||||
}
|
||||
|
||||
func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *qpack.Decoder, onFrameError func()) requestError {
|
||||
func (s *Server) handleRequest(conn *connection, str quic.Stream, datagrams *datagrammer, decoder *qpack.Decoder) {
|
||||
var ufh unknownFrameHandlerFunc
|
||||
if s.StreamHijacker != nil {
|
||||
ufh = func(ft FrameType, e error) (processed bool, err error) { return s.StreamHijacker(ft, conn, str, e) }
|
||||
ufh = func(ft FrameType, e error) (processed bool, err error) {
|
||||
return s.StreamHijacker(
|
||||
ft,
|
||||
conn.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID),
|
||||
str,
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
frame, err := parseNextFrame(str, ufh)
|
||||
if err != nil {
|
||||
if err == errHijacked {
|
||||
return requestError{err: errHijacked}
|
||||
if !errors.Is(err, errHijacked) {
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
}
|
||||
return newStreamError(ErrCodeRequestIncomplete, err)
|
||||
return
|
||||
}
|
||||
hf, ok := frame.(*headersFrame)
|
||||
if !ok {
|
||||
return newConnError(ErrCodeFrameUnexpected, errors.New("expected first frame to be a HEADERS frame"))
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame")
|
||||
return
|
||||
}
|
||||
if hf.Length > s.maxHeaderBytes() {
|
||||
return newStreamError(ErrCodeFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes()))
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
str.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
return
|
||||
}
|
||||
headerBlock := make([]byte, hf.Length)
|
||||
if _, err := io.ReadFull(str, headerBlock); err != nil {
|
||||
return newStreamError(ErrCodeRequestIncomplete, err)
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||
return
|
||||
}
|
||||
hfs, err := decoder.DecodeFull(headerBlock)
|
||||
if err != nil {
|
||||
// TODO: use the right error code
|
||||
return newConnError(ErrCodeGeneralProtocolError, err)
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeGeneralProtocolError), "expected first frame to be a HEADERS frame")
|
||||
return
|
||||
}
|
||||
req, err := requestFromHeaders(hfs)
|
||||
if err != nil {
|
||||
return newStreamError(ErrCodeMessageError, err)
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
str.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
return
|
||||
}
|
||||
|
||||
connState := conn.ConnectionState().TLS
|
||||
@ -608,19 +520,16 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
||||
|
||||
// Check that the client doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
|
||||
// See section 4.1.2 of RFC 9114.
|
||||
var httpStr Stream
|
||||
contentLength := int64(-1)
|
||||
if _, ok := req.Header["Content-Length"]; ok && req.ContentLength >= 0 {
|
||||
httpStr = newLengthLimitedStream(newStream(str, onFrameError), req.ContentLength)
|
||||
} else {
|
||||
httpStr = newStream(str, onFrameError)
|
||||
contentLength = req.ContentLength
|
||||
}
|
||||
body := newRequestBody(httpStr)
|
||||
hstr := newStream(str, conn, datagrams)
|
||||
body := newRequestBody(hstr, contentLength, conn.Context(), conn.ReceivedSettings(), conn.Settings)
|
||||
req.Body = body
|
||||
|
||||
if s.logger.Debug() {
|
||||
s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID())
|
||||
} else {
|
||||
s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
|
||||
if s.Logger != nil {
|
||||
s.Logger.Debug("handling request", "method", req.Method, "host", req.Host, "uri", req.RequestURI)
|
||||
}
|
||||
|
||||
ctx := str.Context()
|
||||
@ -634,15 +543,13 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
||||
}
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
r := newResponseWriter(str, conn, s.logger)
|
||||
if req.Method == http.MethodHead {
|
||||
r.isHead = true
|
||||
}
|
||||
r := newResponseWriter(hstr, conn, req.Method == http.MethodHead, s.Logger)
|
||||
handler := s.Handler
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
}
|
||||
|
||||
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
|
||||
var panicked bool
|
||||
func() {
|
||||
defer func() {
|
||||
@ -655,34 +562,42 @@ func() {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
s.logger.Errorf("http: panic serving: %v\n%s", p, buf)
|
||||
logger := s.Logger
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
logger.Error("http: panic serving", "arg", p, "trace", buf)
|
||||
}
|
||||
}()
|
||||
handler.ServeHTTP(r, req)
|
||||
}()
|
||||
|
||||
if body.wasStreamHijacked() {
|
||||
return requestError{err: errHijacked}
|
||||
if r.wasStreamHijacked() {
|
||||
return
|
||||
}
|
||||
|
||||
// only write response when there is no panic
|
||||
if !panicked {
|
||||
// response not written to the client yet, set Content-Length
|
||||
if !r.written {
|
||||
if !r.headerWritten {
|
||||
if _, haveCL := r.header["Content-Length"]; !haveCL {
|
||||
r.header.Set("Content-Length", strconv.FormatInt(r.numWritten, 10))
|
||||
}
|
||||
}
|
||||
r.Flush()
|
||||
}
|
||||
// If the EOF was read by the handler, CancelRead() is a no-op.
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
|
||||
// abort the stream when there is a panic
|
||||
if panicked {
|
||||
return newStreamError(ErrCodeInternalError, errPanicked)
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeInternalError))
|
||||
str.CancelWrite(quic.StreamErrorCode(ErrCodeInternalError))
|
||||
return
|
||||
}
|
||||
return requestError{}
|
||||
|
||||
// If the EOF was read by the handler, CancelRead() is a no-op.
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
|
||||
str.Close()
|
||||
}
|
||||
|
||||
// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
|
||||
@ -709,32 +624,36 @@ func (s *Server) CloseGracefully(timeout time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrNoAltSvcPort is the error returned by SetQuicHeaders when no port was found
|
||||
// ErrNoAltSvcPort is the error returned by SetQUICHeaders when no port was found
|
||||
// for Alt-Svc to announce. This can happen if listening on a PacketConn without a port
|
||||
// (UNIX socket, for example) and no port is specified in Server.Port or Server.Addr.
|
||||
var ErrNoAltSvcPort = errors.New("no port can be announced, specify it explicitly using Server.Port or Server.Addr")
|
||||
|
||||
// SetQuicHeaders can be used to set the proper headers that announce that this server supports HTTP/3.
|
||||
// The values set by default advertise all of the ports the server is listening on, but can be
|
||||
// changed to a specific port by setting Server.Port before launching the serverr.
|
||||
// SetQUICHeaders can be used to set the proper headers that announce that this server supports HTTP/3.
|
||||
// The values set by default advertise all the ports the server is listening on, but can be
|
||||
// changed to a specific port by setting Server.Port before launching the server.
|
||||
// If no listener's Addr().String() returns an address with a valid port, Server.Addr will be used
|
||||
// to extract the port, if specified.
|
||||
// For example, a server launched using ListenAndServe on an address with port 443 would set:
|
||||
//
|
||||
// Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
||||
func (s *Server) SetQuicHeaders(hdr http.Header) error {
|
||||
// Alt-Svc: h3=":443"; ma=2592000
|
||||
func (s *Server) SetQUICHeaders(hdr http.Header) error {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
if s.altSvcHeader == "" {
|
||||
return ErrNoAltSvcPort
|
||||
}
|
||||
// use the map directly to avoid constant canonicalization
|
||||
// since the key is already canonicalized
|
||||
// use the map directly to avoid constant canonicalization since the key is already canonicalized
|
||||
hdr["Alt-Svc"] = append(hdr["Alt-Svc"], s.altSvcHeader)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use SetQUICHeaders instead.
|
||||
func (s *Server) SetQuicHeaders(hdr http.Header) error {
|
||||
return s.SetQUICHeaders(hdr)
|
||||
}
|
||||
|
||||
// ListenAndServeQUIC listens on the UDP network address addr and calls the
|
||||
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
|
||||
// used when handler is nil.
|
||||
@ -792,7 +711,7 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error
|
||||
qErr := make(chan error, 1)
|
||||
go func() {
|
||||
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
quicServer.SetQuicHeaders(w.Header())
|
||||
quicServer.SetQUICHeaders(w.Header())
|
||||
handler.ServeHTTP(w, r)
|
||||
}))
|
||||
}()
|
||||
|
91
vendor/github.com/quic-go/quic-go/http3/state_tracking_stream.go
generated
vendored
Normal file
91
vendor/github.com/quic-go/quic-go/http3/state_tracking_stream.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package http3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
type streamState uint8
|
||||
|
||||
const (
|
||||
streamStateOpen streamState = iota
|
||||
streamStateReceiveClosed
|
||||
streamStateSendClosed
|
||||
streamStateSendAndReceiveClosed
|
||||
)
|
||||
|
||||
type stateTrackingStream struct {
|
||||
quic.Stream
|
||||
|
||||
mx sync.Mutex
|
||||
state streamState
|
||||
|
||||
onStateChange func(streamState, error)
|
||||
}
|
||||
|
||||
func newStateTrackingStream(s quic.Stream, onStateChange func(streamState, error)) *stateTrackingStream {
|
||||
return &stateTrackingStream{
|
||||
Stream: s,
|
||||
state: streamStateOpen,
|
||||
onStateChange: onStateChange,
|
||||
}
|
||||
}
|
||||
|
||||
var _ quic.Stream = &stateTrackingStream{}
|
||||
|
||||
func (s *stateTrackingStream) closeSend(e error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.state == streamStateReceiveClosed || s.state == streamStateSendAndReceiveClosed {
|
||||
s.state = streamStateSendAndReceiveClosed
|
||||
} else {
|
||||
s.state = streamStateSendClosed
|
||||
}
|
||||
s.onStateChange(s.state, e)
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) closeReceive(e error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.state == streamStateSendClosed || s.state == streamStateSendAndReceiveClosed {
|
||||
s.state = streamStateSendAndReceiveClosed
|
||||
} else {
|
||||
s.state = streamStateReceiveClosed
|
||||
}
|
||||
s.onStateChange(s.state, e)
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) Close() error {
|
||||
s.closeSend(errors.New("write on closed stream"))
|
||||
return s.Stream.Close()
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) CancelWrite(e quic.StreamErrorCode) {
|
||||
s.closeSend(&quic.StreamError{StreamID: s.Stream.StreamID(), ErrorCode: e})
|
||||
s.Stream.CancelWrite(e)
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) Write(b []byte) (int, error) {
|
||||
n, err := s.Stream.Write(b)
|
||||
if err != nil {
|
||||
s.closeSend(err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) CancelRead(e quic.StreamErrorCode) {
|
||||
s.closeReceive(&quic.StreamError{StreamID: s.Stream.StreamID(), ErrorCode: e})
|
||||
s.Stream.CancelRead(e)
|
||||
}
|
||||
|
||||
func (s *stateTrackingStream) Read(b []byte) (int, error) {
|
||||
n, err := s.Stream.Read(b)
|
||||
if err != nil {
|
||||
s.closeReceive(err)
|
||||
}
|
||||
return n, err
|
||||
}
|
7
vendor/github.com/quic-go/quic-go/interface.go
generated
vendored
7
vendor/github.com/quic-go/quic-go/interface.go
generated
vendored
@ -59,6 +59,9 @@ type TokenStore interface {
|
||||
// as well as on the context passed to logging.Tracer.NewConnectionTracer.
|
||||
var ConnectionTracingKey = connTracingCtxKey{}
|
||||
|
||||
// ConnectionTracingID is the type of the context value saved under the ConnectionTracingKey.
|
||||
type ConnectionTracingID uint64
|
||||
|
||||
type connTracingCtxKey struct{}
|
||||
|
||||
// QUICVersionContextKey can be used to find out the QUIC version of a TLS handshake from the
|
||||
@ -121,7 +124,9 @@ type SendStream interface {
|
||||
// CancelWrite aborts sending on this stream.
|
||||
// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.
|
||||
// Write will unblock immediately, and future calls to Write will fail.
|
||||
// When called multiple times or after closing the stream it is a no-op.
|
||||
// When called multiple times it is a no-op.
|
||||
// When called after Close, it aborts delivery. Note that there is no guarantee if
|
||||
// the peer will receive the FIN or the reset first.
|
||||
CancelWrite(StreamErrorCode)
|
||||
// The Context is canceled as soon as the write-side of the stream is closed.
|
||||
// This happens when Close() or CancelWrite() is called, or when the peer
|
||||
|
1
vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go
generated
vendored
1
vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go
generated
vendored
@ -111,6 +111,7 @@ func (c *streamFlowController) AddBytesRead(n protocol.ByteCount) {
|
||||
func (c *streamFlowController) Abandon() {
|
||||
c.mutex.Lock()
|
||||
unread := c.highestReceived - c.bytesRead
|
||||
c.bytesRead = c.highestReceived
|
||||
c.mutex.Unlock()
|
||||
if unread > 0 {
|
||||
c.connection.AddBytesRead(unread)
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/protocol/params.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/protocol/params.go
generated
vendored
@ -3,10 +3,10 @@
|
||||
import "time"
|
||||
|
||||
// DesiredReceiveBufferSize is the kernel UDP receive buffer size that we'd like to use.
|
||||
const DesiredReceiveBufferSize = (1 << 20) * 2 // 2 MB
|
||||
const DesiredReceiveBufferSize = (1 << 20) * 7 // 7 MB
|
||||
|
||||
// DesiredSendBufferSize is the kernel UDP send buffer size that we'd like to use.
|
||||
const DesiredSendBufferSize = (1 << 20) * 2 // 2 MB
|
||||
const DesiredSendBufferSize = (1 << 20) * 7 // 7 MB
|
||||
|
||||
// InitialPacketSizeIPv4 is the maximum packet size that we use for sending IPv4 packets.
|
||||
const InitialPacketSizeIPv4 = 1252
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go
generated
vendored
@ -163,7 +163,7 @@ func (f *AckFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
length += quicvarint.Len(f.ECT1)
|
||||
length += quicvarint.Len(f.ECNCE)
|
||||
}
|
||||
return length
|
||||
return protocol.ByteCount(length)
|
||||
}
|
||||
|
||||
// gets the number of ACK ranges that can be encoded
|
||||
@ -174,7 +174,7 @@ func (f *AckFrame) numEncodableAckRanges() int {
|
||||
for i := 1; i < len(f.AckRanges); i++ {
|
||||
gap, len := f.encodeAckRange(i)
|
||||
rangeLen := quicvarint.Len(gap) + quicvarint.Len(len)
|
||||
if length+rangeLen > protocol.MaxAckFrameSize {
|
||||
if protocol.ByteCount(length+rangeLen) > protocol.MaxAckFrameSize {
|
||||
// Writing range i would exceed the MaxAckFrameSize.
|
||||
// So encode one range less than that.
|
||||
return i - 1
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/wire/connection_close_frame.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/wire/connection_close_frame.go
generated
vendored
@ -54,9 +54,9 @@ func parseConnectionCloseFrame(r *bytes.Reader, typ uint64, _ protocol.Version)
|
||||
|
||||
// Length of a written frame
|
||||
func (f *ConnectionCloseFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
length := 1 + quicvarint.Len(f.ErrorCode) + quicvarint.Len(uint64(len(f.ReasonPhrase))) + protocol.ByteCount(len(f.ReasonPhrase))
|
||||
length := 1 + protocol.ByteCount(quicvarint.Len(f.ErrorCode)+quicvarint.Len(uint64(len(f.ReasonPhrase)))) + protocol.ByteCount(len(f.ReasonPhrase))
|
||||
if !f.IsApplicationError {
|
||||
length += quicvarint.Len(f.FrameType) // for the frame type
|
||||
length += protocol.ByteCount(quicvarint.Len(f.FrameType)) // for the frame type
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/wire/crypto_frame.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/wire/crypto_frame.go
generated
vendored
@ -48,14 +48,14 @@ func (f *CryptoFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
|
||||
// Length of a written frame
|
||||
func (f *CryptoFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.Offset)) + quicvarint.Len(uint64(len(f.Data))) + protocol.ByteCount(len(f.Data))
|
||||
return protocol.ByteCount(1 + quicvarint.Len(uint64(f.Offset)) + quicvarint.Len(uint64(len(f.Data))) + len(f.Data))
|
||||
}
|
||||
|
||||
// MaxDataLen returns the maximum data length
|
||||
func (f *CryptoFrame) MaxDataLen(maxSize protocol.ByteCount) protocol.ByteCount {
|
||||
// pretend that the data size will be 1 bytes
|
||||
// if it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterwards
|
||||
headerLen := 1 + quicvarint.Len(uint64(f.Offset)) + 1
|
||||
headerLen := protocol.ByteCount(1 + quicvarint.Len(uint64(f.Offset)) + 1)
|
||||
if headerLen > maxSize {
|
||||
return 0
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/data_blocked_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/data_blocked_frame.go
generated
vendored
@ -27,5 +27,5 @@ func (f *DataBlockedFrame) Append(b []byte, version protocol.Version) ([]byte, e
|
||||
|
||||
// Length of a written frame
|
||||
func (f *DataBlockedFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.MaximumData))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaximumData)))
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/datagram_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/datagram_frame.go
generated
vendored
@ -80,7 +80,7 @@ func (f *DatagramFrame) MaxDataLen(maxSize protocol.ByteCount, version protocol.
|
||||
func (f *DatagramFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
length := 1 + protocol.ByteCount(len(f.Data))
|
||||
if f.DataLenPresent {
|
||||
length += quicvarint.Len(uint64(len(f.Data)))
|
||||
length += protocol.ByteCount(quicvarint.Len(uint64(len(f.Data))))
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/extended_header.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/extended_header.go
generated
vendored
@ -165,7 +165,7 @@ func (h *ExtendedHeader) ParsedLen() protocol.ByteCount {
|
||||
func (h *ExtendedHeader) GetLength(_ protocol.Version) protocol.ByteCount {
|
||||
length := 1 /* type byte */ + 4 /* version */ + 1 /* dest conn ID len */ + protocol.ByteCount(h.DestConnectionID.Len()) + 1 /* src conn ID len */ + protocol.ByteCount(h.SrcConnectionID.Len()) + protocol.ByteCount(h.PacketNumberLen) + 2 /* length */
|
||||
if h.Type == protocol.PacketTypeInitial {
|
||||
length += quicvarint.Len(uint64(len(h.Token))) + protocol.ByteCount(len(h.Token))
|
||||
length += protocol.ByteCount(quicvarint.Len(uint64(len(h.Token))) + len(h.Token))
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/max_data_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/max_data_frame.go
generated
vendored
@ -31,5 +31,5 @@ func (f *MaxDataFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
|
||||
// Length of a written frame
|
||||
func (f *MaxDataFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.MaximumData))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaximumData)))
|
||||
}
|
||||
|
6
vendor/github.com/quic-go/quic-go/internal/wire/max_stream_data_frame.go
generated
vendored
6
vendor/github.com/quic-go/quic-go/internal/wire/max_stream_data_frame.go
generated
vendored
@ -29,7 +29,7 @@ func parseMaxStreamDataFrame(r *bytes.Reader, _ protocol.Version) (*MaxStreamDat
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *MaxStreamDataFrame) Append(b []byte, version protocol.Version) ([]byte, error) {
|
||||
func (f *MaxStreamDataFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
b = append(b, maxStreamDataFrameType)
|
||||
b = quicvarint.Append(b, uint64(f.StreamID))
|
||||
b = quicvarint.Append(b, uint64(f.MaximumStreamData))
|
||||
@ -37,6 +37,6 @@ func (f *MaxStreamDataFrame) Append(b []byte, version protocol.Version) ([]byte,
|
||||
}
|
||||
|
||||
// Length of a written frame
|
||||
func (f *MaxStreamDataFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.MaximumStreamData))
|
||||
func (f *MaxStreamDataFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.MaximumStreamData)))
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/max_streams_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/max_streams_frame.go
generated
vendored
@ -46,5 +46,5 @@ func (f *MaxStreamsFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
|
||||
// Length of a written frame
|
||||
func (f *MaxStreamsFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.MaxStreamNum))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaxStreamNum)))
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/new_connection_id_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/new_connection_id_frame.go
generated
vendored
@ -73,5 +73,5 @@ func (f *NewConnectionIDFrame) Append(b []byte, _ protocol.Version) ([]byte, err
|
||||
|
||||
// Length of a written frame
|
||||
func (f *NewConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(f.SequenceNumber) + quicvarint.Len(f.RetirePriorTo) + 1 /* connection ID length */ + protocol.ByteCount(f.ConnectionID.Len()) + 16
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(f.SequenceNumber)+quicvarint.Len(f.RetirePriorTo)+1 /* connection ID length */ +f.ConnectionID.Len()) + 16
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/new_token_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/new_token_frame.go
generated
vendored
@ -41,5 +41,5 @@ func (f *NewTokenFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
|
||||
// Length of a written frame
|
||||
func (f *NewTokenFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(len(f.Token))) + protocol.ByteCount(len(f.Token))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(len(f.Token)))+len(f.Token))
|
||||
}
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/wire/reset_stream_frame.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/wire/reset_stream_frame.go
generated
vendored
@ -49,6 +49,6 @@ func (f *ResetStreamFrame) Append(b []byte, _ protocol.Version) ([]byte, error)
|
||||
}
|
||||
|
||||
// Length of a written frame
|
||||
func (f *ResetStreamFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.ErrorCode)) + quicvarint.Len(uint64(f.FinalSize))
|
||||
func (f *ResetStreamFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.ErrorCode))+quicvarint.Len(uint64(f.FinalSize)))
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/retire_connection_id_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/retire_connection_id_frame.go
generated
vendored
@ -28,5 +28,5 @@ func (f *RetireConnectionIDFrame) Append(b []byte, _ protocol.Version) ([]byte,
|
||||
|
||||
// Length of a written frame
|
||||
func (f *RetireConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(f.SequenceNumber)
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(f.SequenceNumber))
|
||||
}
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/stop_sending_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/stop_sending_frame.go
generated
vendored
@ -33,7 +33,7 @@ func parseStopSendingFrame(r *bytes.Reader, _ protocol.Version) (*StopSendingFra
|
||||
|
||||
// Length of a written frame
|
||||
func (f *StopSendingFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.ErrorCode))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.ErrorCode)))
|
||||
}
|
||||
|
||||
func (f *StopSendingFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
|
4
vendor/github.com/quic-go/quic-go/internal/wire/stream_data_blocked_frame.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/internal/wire/stream_data_blocked_frame.go
generated
vendored
@ -37,6 +37,6 @@ func (f *StreamDataBlockedFrame) Append(b []byte, _ protocol.Version) ([]byte, e
|
||||
}
|
||||
|
||||
// Length of a written frame
|
||||
func (f *StreamDataBlockedFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.MaximumStreamData))
|
||||
func (f *StreamDataBlockedFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.MaximumStreamData)))
|
||||
}
|
||||
|
14
vendor/github.com/quic-go/quic-go/internal/wire/stream_frame.go
generated
vendored
14
vendor/github.com/quic-go/quic-go/internal/wire/stream_frame.go
generated
vendored
@ -108,7 +108,7 @@ func (f *StreamFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Length returns the total length of the STREAM frame
|
||||
func (f *StreamFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
func (f *StreamFrame) Length(protocol.Version) protocol.ByteCount {
|
||||
length := 1 + quicvarint.Len(uint64(f.StreamID))
|
||||
if f.Offset != 0 {
|
||||
length += quicvarint.Len(uint64(f.Offset))
|
||||
@ -116,7 +116,7 @@ func (f *StreamFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||
if f.DataLenPresent {
|
||||
length += quicvarint.Len(uint64(f.DataLen()))
|
||||
}
|
||||
return length + f.DataLen()
|
||||
return protocol.ByteCount(length) + f.DataLen()
|
||||
}
|
||||
|
||||
// DataLen gives the length of data in bytes
|
||||
@ -126,14 +126,14 @@ func (f *StreamFrame) DataLen() protocol.ByteCount {
|
||||
|
||||
// MaxDataLen returns the maximum data length
|
||||
// If 0 is returned, writing will fail (a STREAM frame must contain at least 1 byte of data).
|
||||
func (f *StreamFrame) MaxDataLen(maxSize protocol.ByteCount, version protocol.Version) protocol.ByteCount {
|
||||
headerLen := 1 + quicvarint.Len(uint64(f.StreamID))
|
||||
func (f *StreamFrame) MaxDataLen(maxSize protocol.ByteCount, _ protocol.Version) protocol.ByteCount {
|
||||
headerLen := 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID)))
|
||||
if f.Offset != 0 {
|
||||
headerLen += quicvarint.Len(uint64(f.Offset))
|
||||
headerLen += protocol.ByteCount(quicvarint.Len(uint64(f.Offset)))
|
||||
}
|
||||
if f.DataLenPresent {
|
||||
// pretend that the data size will be 1 bytes
|
||||
// if it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterwards
|
||||
// Pretend that the data size will be 1 byte.
|
||||
// If it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterward
|
||||
headerLen++
|
||||
}
|
||||
if headerLen > maxSize {
|
||||
|
2
vendor/github.com/quic-go/quic-go/internal/wire/streams_blocked_frame.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/internal/wire/streams_blocked_frame.go
generated
vendored
@ -46,5 +46,5 @@ func (f *StreamsBlockedFrame) Append(b []byte, _ protocol.Version) ([]byte, erro
|
||||
|
||||
// Length of a written frame
|
||||
func (f *StreamsBlockedFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return 1 + quicvarint.Len(uint64(f.StreamLimit))
|
||||
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamLimit)))
|
||||
}
|
||||
|
10
vendor/github.com/quic-go/quic-go/quicvarint/varint.go
generated
vendored
10
vendor/github.com/quic-go/quic-go/quicvarint/varint.go
generated
vendored
@ -3,8 +3,6 @@
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
// taken from the QUIC draft
|
||||
@ -91,7 +89,7 @@ func Append(b []byte, i uint64) []byte {
|
||||
}
|
||||
|
||||
// AppendWithLen append i in the QUIC varint format with the desired length.
|
||||
func AppendWithLen(b []byte, i uint64, length protocol.ByteCount) []byte {
|
||||
func AppendWithLen(b []byte, i uint64, length int) []byte {
|
||||
if length != 1 && length != 2 && length != 4 && length != 8 {
|
||||
panic("invalid varint length")
|
||||
}
|
||||
@ -109,17 +107,17 @@ func AppendWithLen(b []byte, i uint64, length protocol.ByteCount) []byte {
|
||||
} else if length == 8 {
|
||||
b = append(b, 0b11000000)
|
||||
}
|
||||
for j := protocol.ByteCount(1); j < length-l; j++ {
|
||||
for j := 1; j < length-l; j++ {
|
||||
b = append(b, 0)
|
||||
}
|
||||
for j := protocol.ByteCount(0); j < l; j++ {
|
||||
for j := 0; j < l; j++ {
|
||||
b = append(b, uint8(i>>(8*(l-1-j))))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Len determines the number of bytes that will be needed to write the number i.
|
||||
func Len(i uint64) protocol.ByteCount {
|
||||
func Len(i uint64) int {
|
||||
if i <= maxVarInt1 {
|
||||
return 1
|
||||
}
|
||||
|
136
vendor/github.com/quic-go/quic-go/receive_stream.go
generated
vendored
136
vendor/github.com/quic-go/quic-go/receive_stream.go
generated
vendored
@ -37,10 +37,14 @@ type receiveStream struct {
|
||||
readPosInFrame int
|
||||
currentFrameIsLast bool // is the currentFrame the last frame on this stream
|
||||
|
||||
finRead bool // set once we read a frame with a Fin
|
||||
// Set once we read the io.EOF or the cancellation error.
|
||||
// Note that for local cancellations, this doesn't necessarily mean that we know the final offset yet.
|
||||
errorRead bool
|
||||
completed bool // set once we've called streamSender.onStreamCompleted
|
||||
cancelledRemotely bool
|
||||
cancelledLocally bool
|
||||
cancelErr *StreamError
|
||||
closeForShutdownErr error
|
||||
cancelReadErr error
|
||||
resetRemotelyErr *StreamError
|
||||
|
||||
readChan chan struct{}
|
||||
readOnce chan struct{} // cap: 1, to protect against concurrent use of Read
|
||||
@ -83,7 +87,8 @@ func (s *receiveStream) Read(p []byte) (int, error) {
|
||||
defer func() { <-s.readOnce }()
|
||||
|
||||
s.mutex.Lock()
|
||||
completed, n, err := s.readImpl(p)
|
||||
n, err := s.readImpl(p)
|
||||
completed := s.isNewlyCompleted()
|
||||
s.mutex.Unlock()
|
||||
|
||||
if completed {
|
||||
@ -92,18 +97,38 @@ func (s *receiveStream) Read(p []byte) (int, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, error) {
|
||||
if s.finRead {
|
||||
return false, 0, io.EOF
|
||||
func (s *receiveStream) isNewlyCompleted() bool {
|
||||
if s.completed {
|
||||
return false
|
||||
}
|
||||
if s.cancelReadErr != nil {
|
||||
return false, 0, s.cancelReadErr
|
||||
// We need to know the final offset (either via FIN or RESET_STREAM) for flow control accounting.
|
||||
if s.finalOffset == protocol.MaxByteCount {
|
||||
return false
|
||||
}
|
||||
if s.resetRemotelyErr != nil {
|
||||
return false, 0, s.resetRemotelyErr
|
||||
// We're done with the stream if it was cancelled locally...
|
||||
if s.cancelledLocally {
|
||||
s.completed = true
|
||||
return true
|
||||
}
|
||||
// ... or if the error (either io.EOF or the reset error) was read
|
||||
if s.errorRead {
|
||||
s.completed = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *receiveStream) readImpl(p []byte) (int, error) {
|
||||
if s.currentFrameIsLast && s.currentFrame == nil {
|
||||
s.errorRead = true
|
||||
return 0, io.EOF
|
||||
}
|
||||
if s.cancelledRemotely || s.cancelledLocally {
|
||||
s.errorRead = true
|
||||
return 0, s.cancelErr
|
||||
}
|
||||
if s.closeForShutdownErr != nil {
|
||||
return false, 0, s.closeForShutdownErr
|
||||
return 0, s.closeForShutdownErr
|
||||
}
|
||||
|
||||
var bytesRead int
|
||||
@ -113,25 +138,23 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
|
||||
s.dequeueNextFrame()
|
||||
}
|
||||
if s.currentFrame == nil && bytesRead > 0 {
|
||||
return false, bytesRead, s.closeForShutdownErr
|
||||
return bytesRead, s.closeForShutdownErr
|
||||
}
|
||||
|
||||
for {
|
||||
// Stop waiting on errors
|
||||
if s.closeForShutdownErr != nil {
|
||||
return false, bytesRead, s.closeForShutdownErr
|
||||
return bytesRead, s.closeForShutdownErr
|
||||
}
|
||||
if s.cancelReadErr != nil {
|
||||
return false, bytesRead, s.cancelReadErr
|
||||
}
|
||||
if s.resetRemotelyErr != nil {
|
||||
return false, bytesRead, s.resetRemotelyErr
|
||||
if s.cancelledRemotely || s.cancelledLocally {
|
||||
s.errorRead = true
|
||||
return 0, s.cancelErr
|
||||
}
|
||||
|
||||
deadline := s.deadline
|
||||
if !deadline.IsZero() {
|
||||
if !time.Now().Before(deadline) {
|
||||
return false, bytesRead, errDeadline
|
||||
return bytesRead, errDeadline
|
||||
}
|
||||
if deadlineTimer == nil {
|
||||
deadlineTimer = utils.NewTimer()
|
||||
@ -161,10 +184,10 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
|
||||
}
|
||||
|
||||
if bytesRead > len(p) {
|
||||
return false, bytesRead, fmt.Errorf("BUG: bytesRead (%d) > len(p) (%d) in stream.Read", bytesRead, len(p))
|
||||
return bytesRead, fmt.Errorf("BUG: bytesRead (%d) > len(p) (%d) in stream.Read", bytesRead, len(p))
|
||||
}
|
||||
if s.readPosInFrame > len(s.currentFrame) {
|
||||
return false, bytesRead, fmt.Errorf("BUG: readPosInFrame (%d) > frame.DataLen (%d) in stream.Read", s.readPosInFrame, len(s.currentFrame))
|
||||
return bytesRead, fmt.Errorf("BUG: readPosInFrame (%d) > frame.DataLen (%d) in stream.Read", s.readPosInFrame, len(s.currentFrame))
|
||||
}
|
||||
|
||||
m := copy(p[bytesRead:], s.currentFrame[s.readPosInFrame:])
|
||||
@ -173,20 +196,20 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
|
||||
|
||||
// when a RESET_STREAM was received, the flow controller was already
|
||||
// informed about the final byteOffset for this stream
|
||||
if s.resetRemotelyErr == nil {
|
||||
if !s.cancelledRemotely {
|
||||
s.flowController.AddBytesRead(protocol.ByteCount(m))
|
||||
}
|
||||
|
||||
if s.readPosInFrame >= len(s.currentFrame) && s.currentFrameIsLast {
|
||||
s.finRead = true
|
||||
s.currentFrame = nil
|
||||
if s.currentFrameDone != nil {
|
||||
s.currentFrameDone()
|
||||
}
|
||||
return true, bytesRead, io.EOF
|
||||
s.errorRead = true
|
||||
return bytesRead, io.EOF
|
||||
}
|
||||
}
|
||||
return false, bytesRead, nil
|
||||
return bytesRead, nil
|
||||
}
|
||||
|
||||
func (s *receiveStream) dequeueNextFrame() {
|
||||
@ -202,7 +225,8 @@ func (s *receiveStream) dequeueNextFrame() {
|
||||
|
||||
func (s *receiveStream) CancelRead(errorCode StreamErrorCode) {
|
||||
s.mutex.Lock()
|
||||
completed := s.cancelReadImpl(errorCode)
|
||||
s.cancelReadImpl(errorCode)
|
||||
completed := s.isNewlyCompleted()
|
||||
s.mutex.Unlock()
|
||||
|
||||
if completed {
|
||||
@ -211,23 +235,26 @@ func (s *receiveStream) CancelRead(errorCode StreamErrorCode) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *receiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) bool /* completed */ {
|
||||
if s.finRead || s.cancelReadErr != nil || s.resetRemotelyErr != nil {
|
||||
return false
|
||||
func (s *receiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) {
|
||||
if s.cancelledLocally { // duplicate call to CancelRead
|
||||
return
|
||||
}
|
||||
s.cancelReadErr = &StreamError{StreamID: s.streamID, ErrorCode: errorCode, Remote: false}
|
||||
s.cancelledLocally = true
|
||||
if s.errorRead || s.cancelledRemotely {
|
||||
return
|
||||
}
|
||||
s.cancelErr = &StreamError{StreamID: s.streamID, ErrorCode: errorCode, Remote: false}
|
||||
s.signalRead()
|
||||
s.sender.queueControlFrame(&wire.StopSendingFrame{
|
||||
StreamID: s.streamID,
|
||||
ErrorCode: errorCode,
|
||||
})
|
||||
// We're done with this stream if the final offset was already received.
|
||||
return s.finalOffset != protocol.MaxByteCount
|
||||
}
|
||||
|
||||
func (s *receiveStream) handleStreamFrame(frame *wire.StreamFrame) error {
|
||||
s.mutex.Lock()
|
||||
completed, err := s.handleStreamFrameImpl(frame)
|
||||
err := s.handleStreamFrameImpl(frame)
|
||||
completed := s.isNewlyCompleted()
|
||||
s.mutex.Unlock()
|
||||
|
||||
if completed {
|
||||
@ -237,59 +264,58 @@ func (s *receiveStream) handleStreamFrame(frame *wire.StreamFrame) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *receiveStream) handleStreamFrameImpl(frame *wire.StreamFrame) (bool /* completed */, error) {
|
||||
func (s *receiveStream) handleStreamFrameImpl(frame *wire.StreamFrame) error {
|
||||
maxOffset := frame.Offset + frame.DataLen()
|
||||
if err := s.flowController.UpdateHighestReceived(maxOffset, frame.Fin); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
var newlyRcvdFinalOffset bool
|
||||
if frame.Fin {
|
||||
newlyRcvdFinalOffset = s.finalOffset == protocol.MaxByteCount
|
||||
s.finalOffset = maxOffset
|
||||
}
|
||||
if s.cancelReadErr != nil {
|
||||
return newlyRcvdFinalOffset, nil
|
||||
if s.cancelledLocally {
|
||||
return nil
|
||||
}
|
||||
if err := s.frameQueue.Push(frame.Data, frame.Offset, frame.PutBack); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
s.signalRead()
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *receiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
|
||||
s.mutex.Lock()
|
||||
completed, err := s.handleResetStreamFrameImpl(frame)
|
||||
err := s.handleResetStreamFrameImpl(frame)
|
||||
completed := s.isNewlyCompleted()
|
||||
s.mutex.Unlock()
|
||||
|
||||
if completed {
|
||||
s.flowController.Abandon()
|
||||
s.sender.onStreamCompleted(s.streamID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *receiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame) (bool /*completed */, error) {
|
||||
func (s *receiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame) error {
|
||||
if s.closeForShutdownErr != nil {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
if err := s.flowController.UpdateHighestReceived(frame.FinalSize, true); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
newlyRcvdFinalOffset := s.finalOffset == protocol.MaxByteCount
|
||||
s.finalOffset = frame.FinalSize
|
||||
|
||||
// ignore duplicate RESET_STREAM frames for this stream (after checking their final offset)
|
||||
if s.resetRemotelyErr != nil {
|
||||
return false, nil
|
||||
if s.cancelledRemotely {
|
||||
return nil
|
||||
}
|
||||
s.resetRemotelyErr = &StreamError{
|
||||
StreamID: s.streamID,
|
||||
ErrorCode: frame.ErrorCode,
|
||||
Remote: true,
|
||||
s.flowController.Abandon()
|
||||
// don't save the error if the RESET_STREAM frames was received after CancelRead was called
|
||||
if s.cancelledLocally {
|
||||
return nil
|
||||
}
|
||||
s.cancelledRemotely = true
|
||||
s.cancelErr = &StreamError{StreamID: s.streamID, ErrorCode: frame.ErrorCode, Remote: true}
|
||||
s.signalRead()
|
||||
return newlyRcvdFinalOffset, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *receiveStream) SetReadDeadline(t time.Time) error {
|
||||
|
81
vendor/github.com/quic-go/quic-go/send_stream.go
generated
vendored
81
vendor/github.com/quic-go/quic-go/send_stream.go
generated
vendored
@ -42,7 +42,11 @@ type sendStream struct {
|
||||
|
||||
finishedWriting bool // set once Close() is called
|
||||
finSent bool // set when a STREAM_FRAME with FIN bit has been sent
|
||||
completed bool // set when this stream has been reported to the streamSender as completed
|
||||
// Set when the application knows about the cancellation.
|
||||
// This can happen because the application called CancelWrite,
|
||||
// or because Write returned the error (for remote cancellations).
|
||||
cancellationFlagged bool
|
||||
completed bool // set when this stream has been reported to the streamSender as completed
|
||||
|
||||
dataForWriting []byte // during a Write() call, this slice is the part of p that still needs to be sent out
|
||||
nextFrame *wire.StreamFrame
|
||||
@ -60,6 +64,7 @@ type sendStream struct {
|
||||
)
|
||||
|
||||
func newSendStream(
|
||||
ctx context.Context,
|
||||
streamID protocol.StreamID,
|
||||
sender streamSender,
|
||||
flowController flowcontrol.StreamFlowController,
|
||||
@ -71,7 +76,7 @@ func newSendStream(
|
||||
writeChan: make(chan struct{}, 1),
|
||||
writeOnce: make(chan struct{}, 1), // cap: 1, to protect against concurrent use of Write
|
||||
}
|
||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.Background())
|
||||
s.ctx, s.ctxCancel = context.WithCancelCause(ctx)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -86,23 +91,32 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||
s.writeOnce <- struct{}{}
|
||||
defer func() { <-s.writeOnce }()
|
||||
|
||||
isNewlyCompleted, n, err := s.write(p)
|
||||
if isNewlyCompleted {
|
||||
s.sender.onStreamCompleted(s.streamID)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *sendStream) write(p []byte) (bool /* is newly completed */, int, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.finishedWriting {
|
||||
return 0, fmt.Errorf("write on closed stream %d", s.streamID)
|
||||
return false, 0, fmt.Errorf("write on closed stream %d", s.streamID)
|
||||
}
|
||||
if s.cancelWriteErr != nil {
|
||||
return 0, s.cancelWriteErr
|
||||
s.cancellationFlagged = true
|
||||
return s.isNewlyCompleted(), 0, s.cancelWriteErr
|
||||
}
|
||||
if s.closeForShutdownErr != nil {
|
||||
return 0, s.closeForShutdownErr
|
||||
return false, 0, s.closeForShutdownErr
|
||||
}
|
||||
if !s.deadline.IsZero() && !time.Now().Before(s.deadline) {
|
||||
return 0, errDeadline
|
||||
return false, 0, errDeadline
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
s.dataForWriting = p
|
||||
@ -143,7 +157,7 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||
if !deadline.IsZero() {
|
||||
if !time.Now().Before(deadline) {
|
||||
s.dataForWriting = nil
|
||||
return bytesWritten, errDeadline
|
||||
return false, bytesWritten, errDeadline
|
||||
}
|
||||
if deadlineTimer == nil {
|
||||
deadlineTimer = utils.NewTimer()
|
||||
@ -178,14 +192,15 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
if bytesWritten == len(p) {
|
||||
return bytesWritten, nil
|
||||
return false, bytesWritten, nil
|
||||
}
|
||||
if s.closeForShutdownErr != nil {
|
||||
return bytesWritten, s.closeForShutdownErr
|
||||
return false, bytesWritten, s.closeForShutdownErr
|
||||
} else if s.cancelWriteErr != nil {
|
||||
return bytesWritten, s.cancelWriteErr
|
||||
s.cancellationFlagged = true
|
||||
return s.isNewlyCompleted(), bytesWritten, s.cancelWriteErr
|
||||
}
|
||||
return bytesWritten, nil
|
||||
return false, bytesWritten, nil
|
||||
}
|
||||
|
||||
func (s *sendStream) canBufferStreamFrame() bool {
|
||||
@ -348,8 +363,24 @@ func (s *sendStream) getDataForWriting(f *wire.StreamFrame, maxBytes protocol.By
|
||||
}
|
||||
|
||||
func (s *sendStream) isNewlyCompleted() bool {
|
||||
completed := (s.finSent || s.cancelWriteErr != nil) && s.numOutstandingFrames == 0 && len(s.retransmissionQueue) == 0
|
||||
if completed && !s.completed {
|
||||
if s.completed {
|
||||
return false
|
||||
}
|
||||
// We need to keep the stream around until all frames have been sent and acknowledged.
|
||||
if s.numOutstandingFrames > 0 || len(s.retransmissionQueue) > 0 {
|
||||
return false
|
||||
}
|
||||
// The stream is completed if we sent the FIN.
|
||||
if s.finSent {
|
||||
s.completed = true
|
||||
return true
|
||||
}
|
||||
// The stream is also completed if:
|
||||
// 1. the application called CancelWrite, or
|
||||
// 2. we received a STOP_SENDING, and
|
||||
// * the application consumed the error via Write, or
|
||||
// * the application called CLsoe
|
||||
if s.cancelWriteErr != nil && (s.cancellationFlagged || s.finishedWriting) {
|
||||
s.completed = true
|
||||
return true
|
||||
}
|
||||
@ -362,15 +393,23 @@ func (s *sendStream) Close() error {
|
||||
s.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
if s.cancelWriteErr != nil {
|
||||
s.mutex.Unlock()
|
||||
return fmt.Errorf("close called for canceled stream %d", s.streamID)
|
||||
}
|
||||
s.ctxCancel(nil)
|
||||
s.finishedWriting = true
|
||||
cancelWriteErr := s.cancelWriteErr
|
||||
if cancelWriteErr != nil {
|
||||
s.cancellationFlagged = true
|
||||
}
|
||||
completed := s.isNewlyCompleted()
|
||||
s.mutex.Unlock()
|
||||
|
||||
if completed {
|
||||
s.sender.onStreamCompleted(s.streamID)
|
||||
}
|
||||
if cancelWriteErr != nil {
|
||||
return fmt.Errorf("close called for canceled stream %d", s.streamID)
|
||||
}
|
||||
s.sender.onHasStreamData(s.streamID) // need to send the FIN, must be called without holding the mutex
|
||||
|
||||
s.ctxCancel(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -378,9 +417,11 @@ func (s *sendStream) CancelWrite(errorCode StreamErrorCode) {
|
||||
s.cancelWriteImpl(errorCode, false)
|
||||
}
|
||||
|
||||
// must be called after locking the mutex
|
||||
func (s *sendStream) cancelWriteImpl(errorCode qerr.StreamErrorCode, remote bool) {
|
||||
s.mutex.Lock()
|
||||
if !remote {
|
||||
s.cancellationFlagged = true
|
||||
}
|
||||
if s.cancelWriteErr != nil {
|
||||
s.mutex.Unlock()
|
||||
return
|
||||
|
2
vendor/github.com/quic-go/quic-go/server.go
generated
vendored
2
vendor/github.com/quic-go/quic-go/server.go
generated
vendored
@ -92,7 +92,7 @@ type baseServer struct {
|
||||
*handshake.TokenGenerator,
|
||||
bool, /* client address validated by an address validation token */
|
||||
*logging.ConnectionTracer,
|
||||
uint64,
|
||||
ConnectionTracingID,
|
||||
utils.Logger,
|
||||
protocol.Version,
|
||||
) quicConn
|
||||
|
7
vendor/github.com/quic-go/quic-go/stream.go
generated
vendored
7
vendor/github.com/quic-go/quic-go/stream.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
@ -85,7 +86,9 @@ type stream struct {
|
||||
var _ Stream = &stream{}
|
||||
|
||||
// newStream creates a new Stream
|
||||
func newStream(streamID protocol.StreamID,
|
||||
func newStream(
|
||||
ctx context.Context,
|
||||
streamID protocol.StreamID,
|
||||
sender streamSender,
|
||||
flowController flowcontrol.StreamFlowController,
|
||||
) *stream {
|
||||
@ -99,7 +102,7 @@ func newStream(streamID protocol.StreamID,
|
||||
s.completedMutex.Unlock()
|
||||
},
|
||||
}
|
||||
s.sendStream = *newSendStream(streamID, senderForSendStream, flowController)
|
||||
s.sendStream = *newSendStream(ctx, streamID, senderForSendStream, flowController)
|
||||
senderForReceiveStream := &uniStreamSender{
|
||||
streamSender: sender,
|
||||
onStreamCompletedImpl: func() {
|
||||
|
9
vendor/github.com/quic-go/quic-go/streams_map.go
generated
vendored
9
vendor/github.com/quic-go/quic-go/streams_map.go
generated
vendored
@ -45,6 +45,7 @@ func (streamOpenErr) Timeout() bool { return false }
|
||||
var errTooManyOpenStreams = errors.New("too many open streams")
|
||||
|
||||
type streamsMap struct {
|
||||
ctx context.Context // not used for cancellations, but carries the values associated with the connection
|
||||
perspective protocol.Perspective
|
||||
|
||||
maxIncomingBidiStreams uint64
|
||||
@ -64,6 +65,7 @@ type streamsMap struct {
|
||||
var _ streamManager = &streamsMap{}
|
||||
|
||||
func newStreamsMap(
|
||||
ctx context.Context,
|
||||
sender streamSender,
|
||||
newFlowController func(protocol.StreamID) flowcontrol.StreamFlowController,
|
||||
maxIncomingBidiStreams uint64,
|
||||
@ -71,6 +73,7 @@ func newStreamsMap(
|
||||
perspective protocol.Perspective,
|
||||
) streamManager {
|
||||
m := &streamsMap{
|
||||
ctx: ctx,
|
||||
perspective: perspective,
|
||||
newFlowController: newFlowController,
|
||||
maxIncomingBidiStreams: maxIncomingBidiStreams,
|
||||
@ -86,7 +89,7 @@ func (m *streamsMap) initMaps() {
|
||||
protocol.StreamTypeBidi,
|
||||
func(num protocol.StreamNum) streamI {
|
||||
id := num.StreamID(protocol.StreamTypeBidi, m.perspective)
|
||||
return newStream(id, m.sender, m.newFlowController(id))
|
||||
return newStream(m.ctx, id, m.sender, m.newFlowController(id))
|
||||
},
|
||||
m.sender.queueControlFrame,
|
||||
)
|
||||
@ -94,7 +97,7 @@ func(num protocol.StreamNum) streamI {
|
||||
protocol.StreamTypeBidi,
|
||||
func(num protocol.StreamNum) streamI {
|
||||
id := num.StreamID(protocol.StreamTypeBidi, m.perspective.Opposite())
|
||||
return newStream(id, m.sender, m.newFlowController(id))
|
||||
return newStream(m.ctx, id, m.sender, m.newFlowController(id))
|
||||
},
|
||||
m.maxIncomingBidiStreams,
|
||||
m.sender.queueControlFrame,
|
||||
@ -103,7 +106,7 @@ func(num protocol.StreamNum) streamI {
|
||||
protocol.StreamTypeUni,
|
||||
func(num protocol.StreamNum) sendStreamI {
|
||||
id := num.StreamID(protocol.StreamTypeUni, m.perspective)
|
||||
return newSendStream(id, m.sender, m.newFlowController(id))
|
||||
return newSendStream(m.ctx, id, m.sender, m.newFlowController(id))
|
||||
},
|
||||
m.sender.queueControlFrame,
|
||||
)
|
||||
|
4
vendor/github.com/quic-go/quic-go/sys_conn_helper_darwin.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/sys_conn_helper_darwin.go
generated
vendored
@ -33,4 +33,6 @@ func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
|
||||
return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
|
||||
}
|
||||
|
||||
func isGSOSupported(syscall.RawConn) bool { return false }
|
||||
func isGSOEnabled(syscall.RawConn) bool { return false }
|
||||
|
||||
func isECNEnabled() bool { return !isECNDisabledUsingEnv() }
|
||||
|
4
vendor/github.com/quic-go/quic-go/sys_conn_helper_freebsd.go
generated
vendored
4
vendor/github.com/quic-go/quic-go/sys_conn_helper_freebsd.go
generated
vendored
@ -28,4 +28,6 @@ func parseIPv4PktInfo(body []byte) (ip netip.Addr, _ uint32, ok bool) {
|
||||
return netip.AddrFrom4(*(*[4]byte)(body)), 0, true
|
||||
}
|
||||
|
||||
func isGSOSupported(syscall.RawConn) bool { return false }
|
||||
func isGSOEnabled(syscall.RawConn) bool { return false }
|
||||
|
||||
func isECNEnabled() bool { return !isECNDisabledUsingEnv() }
|
||||
|
50
vendor/github.com/quic-go/quic-go/sys_conn_helper_linux.go
generated
vendored
50
vendor/github.com/quic-go/quic-go/sys_conn_helper_linux.go
generated
vendored
@ -23,6 +23,12 @@
|
||||
|
||||
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
|
||||
|
||||
var kernelVersionMajor int
|
||||
|
||||
func init() {
|
||||
kernelVersionMajor, _ = kernelVersion()
|
||||
}
|
||||
|
||||
func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
|
||||
var serr error
|
||||
if err := c.Control(func(fd uintptr) {
|
||||
@ -55,9 +61,12 @@ func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
|
||||
return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
|
||||
}
|
||||
|
||||
// isGSOSupported tests if the kernel supports GSO.
|
||||
// isGSOEnabled tests if the kernel supports GSO.
|
||||
// Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
|
||||
func isGSOSupported(conn syscall.RawConn) bool {
|
||||
func isGSOEnabled(conn syscall.RawConn) bool {
|
||||
if kernelVersionMajor < 5 {
|
||||
return false
|
||||
}
|
||||
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
|
||||
if err == nil && disabled {
|
||||
return false
|
||||
@ -108,3 +117,40 @@ func isPermissionError(err error) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isECNEnabled() bool {
|
||||
return kernelVersionMajor >= 5 && !isECNDisabledUsingEnv()
|
||||
}
|
||||
|
||||
// kernelVersion returns major and minor kernel version numbers, parsed from
|
||||
// the syscall.Uname's Release field, or 0, 0 if the version can't be obtained
|
||||
// or parsed.
|
||||
//
|
||||
// copied from the standard library's internal/syscall/unix/kernel_version_linux.go
|
||||
func kernelVersion() (major, minor int) {
|
||||
var uname syscall.Utsname
|
||||
if err := syscall.Uname(&uname); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
values [2]int
|
||||
value, vi int
|
||||
)
|
||||
for _, c := range uname.Release {
|
||||
if '0' <= c && c <= '9' {
|
||||
value = (value * 10) + int(c-'0')
|
||||
} else {
|
||||
// Note that we're assuming N.N.N here.
|
||||
// If we see anything else, we are likely to mis-parse it.
|
||||
values[vi] = value
|
||||
vi++
|
||||
if vi >= len(values) {
|
||||
break
|
||||
}
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
return values[0], values[1]
|
||||
}
|
||||
|
8
vendor/github.com/quic-go/quic-go/sys_conn_oob.go
generated
vendored
8
vendor/github.com/quic-go/quic-go/sys_conn_oob.go
generated
vendored
@ -59,7 +59,7 @@ func inspectWriteBuffer(c syscall.RawConn) (int, error) {
|
||||
return size, serr
|
||||
}
|
||||
|
||||
func isECNDisabled() bool {
|
||||
func isECNDisabledUsingEnv() bool {
|
||||
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
|
||||
return err == nil && disabled
|
||||
}
|
||||
@ -147,8 +147,8 @@ func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) {
|
||||
readPos: batchSize,
|
||||
cap: connCapabilities{
|
||||
DF: supportsDF,
|
||||
GSO: isGSOSupported(rawConn),
|
||||
ECN: !isECNDisabled(),
|
||||
GSO: isGSOEnabled(rawConn),
|
||||
ECN: isECNEnabled(),
|
||||
},
|
||||
}
|
||||
for i := 0; i < batchSize; i++ {
|
||||
@ -247,7 +247,7 @@ func (c *oobConn) WritePacket(b []byte, addr net.Addr, packetInfoOOB []byte, gso
|
||||
}
|
||||
if ecn != protocol.ECNUnsupported {
|
||||
if !c.capabilities().ECN {
|
||||
panic("tried to send a ECN-marked packet although ECN is disabled")
|
||||
panic("tried to send an ECN-marked packet although ECN is disabled")
|
||||
}
|
||||
if remoteUDPAddr, ok := addr.(*net.UDPAddr); ok {
|
||||
if remoteUDPAddr.IP.To4() != nil {
|
||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -104,7 +104,7 @@ github.com/powerman/deepequal
|
||||
# github.com/quic-go/qpack v0.4.0
|
||||
## explicit; go 1.18
|
||||
github.com/quic-go/qpack
|
||||
# github.com/quic-go/quic-go v0.42.0
|
||||
# github.com/quic-go/quic-go v0.43.0
|
||||
## explicit; go 1.21
|
||||
github.com/quic-go/quic-go
|
||||
github.com/quic-go/quic-go/http3
|
||||
|
Loading…
Reference in New Issue
Block a user