mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2025-04-05 22:27:37 +03:00
Update quic-go
This commit is contained in:
parent
f8ce22d9b9
commit
34a1f2ebf5
53 changed files with 1608 additions and 1312 deletions
2
go.mod
2
go.mod
|
@ -20,7 +20,7 @@ require (
|
||||||
github.com/miekg/dns v1.1.59
|
github.com/miekg/dns v1.1.59
|
||||||
github.com/opencoff/go-sieve v0.2.1
|
github.com/opencoff/go-sieve v0.2.1
|
||||||
github.com/powerman/check v1.7.0
|
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/crypto v0.22.0
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/sys v0.19.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/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 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
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.43.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g=
|
||||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
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 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
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:
|
misspell:
|
||||||
ignore-words:
|
ignore-words:
|
||||||
- ect
|
- ect
|
||||||
|
depguard:
|
||||||
|
rules:
|
||||||
|
quicvarint:
|
||||||
|
list-mode: strict
|
||||||
|
files:
|
||||||
|
- "**/github.com/quic-go/quic-go/quicvarint/*"
|
||||||
|
- "!$test"
|
||||||
|
allow:
|
||||||
|
- $gostd
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- asciicheck
|
- asciicheck
|
||||||
|
- depguard
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- goimports
|
- 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>
|
<img src="docs/quic.png" width=303 height=124>
|
||||||
|
|
||||||
|
[](https://quic-go.net/docs/)
|
||||||
[](https://pkg.go.dev/github.com/quic-go/quic-go)
|
[](https://pkg.go.dev/github.com/quic-go/quic-go)
|
||||||
[](https://codecov.io/gh/quic-go/quic-go/)
|
[](https://codecov.io/gh/quic-go/quic-go/)
|
||||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go)
|
[](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:
|
In addition to these base RFCs, it also implements the following RFCs:
|
||||||
* Unreliable Datagram Extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221))
|
* 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).
|
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
|
Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/).
|
||||||
|
|
||||||
### 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{},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Projects using quic-go
|
## 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
|
conn quicConn
|
||||||
|
|
||||||
tracer *logging.ConnectionTracer
|
tracer *logging.ConnectionTracer
|
||||||
tracingID uint64
|
tracingID ConnectionTracingID
|
||||||
logger utils.Logger
|
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"
|
return "closing connection in order to recreate it"
|
||||||
}
|
}
|
||||||
|
|
||||||
var connTracingID uint64 // to be accessed atomically
|
var connTracingID atomic.Uint64 // to be accessed atomically
|
||||||
func nextConnTracingID() uint64 { return atomic.AddUint64(&connTracingID, 1) }
|
func nextConnTracingID() ConnectionTracingID { return ConnectionTracingID(connTracingID.Add(1)) }
|
||||||
|
|
||||||
// A Connection is a QUIC connection
|
// A Connection is a QUIC connection
|
||||||
type connection struct {
|
type connection struct {
|
||||||
|
@ -234,7 +234,7 @@ var newConnection = func(
|
||||||
tokenGenerator *handshake.TokenGenerator,
|
tokenGenerator *handshake.TokenGenerator,
|
||||||
clientAddressValidated bool,
|
clientAddressValidated bool,
|
||||||
tracer *logging.ConnectionTracer,
|
tracer *logging.ConnectionTracer,
|
||||||
tracingID uint64,
|
tracingID ConnectionTracingID,
|
||||||
logger utils.Logger,
|
logger utils.Logger,
|
||||||
v protocol.Version,
|
v protocol.Version,
|
||||||
) quicConn {
|
) quicConn {
|
||||||
|
@ -272,8 +272,8 @@ var newConnection = func(
|
||||||
s.queueControlFrame,
|
s.queueControlFrame,
|
||||||
connIDGenerator,
|
connIDGenerator,
|
||||||
)
|
)
|
||||||
s.preSetup()
|
|
||||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
||||||
|
s.preSetup()
|
||||||
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
||||||
0,
|
0,
|
||||||
getMaxPacketSize(s.conn.RemoteAddr()),
|
getMaxPacketSize(s.conn.RemoteAddr()),
|
||||||
|
@ -347,7 +347,7 @@ var newClientConnection = func(
|
||||||
enable0RTT bool,
|
enable0RTT bool,
|
||||||
hasNegotiatedVersion bool,
|
hasNegotiatedVersion bool,
|
||||||
tracer *logging.ConnectionTracer,
|
tracer *logging.ConnectionTracer,
|
||||||
tracingID uint64,
|
tracingID ConnectionTracingID,
|
||||||
logger utils.Logger,
|
logger utils.Logger,
|
||||||
v protocol.Version,
|
v protocol.Version,
|
||||||
) quicConn {
|
) quicConn {
|
||||||
|
@ -381,8 +381,8 @@ var newClientConnection = func(
|
||||||
s.queueControlFrame,
|
s.queueControlFrame,
|
||||||
connIDGenerator,
|
connIDGenerator,
|
||||||
)
|
)
|
||||||
s.preSetup()
|
|
||||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
||||||
|
s.preSetup()
|
||||||
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
||||||
initialPacketNumber,
|
initialPacketNumber,
|
||||||
getMaxPacketSize(s.conn.RemoteAddr()),
|
getMaxPacketSize(s.conn.RemoteAddr()),
|
||||||
|
@ -471,6 +471,7 @@ func (s *connection) preSetup() {
|
||||||
)
|
)
|
||||||
s.earlyConnReadyChan = make(chan struct{})
|
s.earlyConnReadyChan = make(chan struct{})
|
||||||
s.streamsMap = newStreamsMap(
|
s.streamsMap = newStreamsMap(
|
||||||
|
s.ctx,
|
||||||
s,
|
s,
|
||||||
s.newFlowController,
|
s.newFlowController,
|
||||||
uint64(s.config.MaxIncomingStreams),
|
uint64(s.config.MaxIncomingStreams),
|
||||||
|
@ -2357,10 +2358,9 @@ func (s *connection) SendDatagram(p []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &wire.DatagramFrame{DataLenPresent: true}
|
f := &wire.DatagramFrame{DataLenPresent: true}
|
||||||
if protocol.ByteCount(len(p)) > f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version) {
|
maxDataLen := f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version)
|
||||||
return &DatagramTooLargeError{
|
if protocol.ByteCount(len(p)) > maxDataLen {
|
||||||
PeerMaxDatagramFrameSize: int64(s.peerParams.MaxDatagramFrameSize),
|
return &DatagramTooLargeError{MaxDatagramPayloadSize: int64(maxDataLen)}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f.Data = make([]byte, len(p))
|
f.Data = make([]byte, len(p))
|
||||||
copy(f.Data, 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.
|
// DatagramTooLargeError is returned from Connection.SendDatagram if the payload is too large to be sent.
|
||||||
type DatagramTooLargeError struct {
|
type DatagramTooLargeError struct {
|
||||||
PeerMaxDatagramFrameSize int64
|
MaxDatagramPayloadSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DatagramTooLargeError) Is(target error) bool {
|
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.
|
// For the last STREAM frame, we'll remove the DataLen field later.
|
||||||
// Therefore, we can pretend to have more bytes available when popping
|
// Therefore, we can pretend to have more bytes available when popping
|
||||||
// the STREAM frame (which will always have the DataLen set).
|
// 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)
|
frame, ok, hasMoreData := str.popStreamFrame(remainingLen, v)
|
||||||
if hasMoreData { // put the stream back in the queue (at the end)
|
if hasMoreData { // put the stream back in the queue (at the end)
|
||||||
f.streamQueue.PushBack(id)
|
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
|
# HTTP/3
|
||||||
|
|
||||||
|
[](https://quic-go.net/docs/)
|
||||||
[](https://pkg.go.dev/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.
|
It aims to provide feature parity with the standard library's HTTP/1.1 and HTTP/2 implementation.
|
||||||
|
|
||||||
## Serving HTTP/3
|
Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/).
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
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 @@ package http3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"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.
|
// 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.
|
// It is used by WebTransport to create WebTransport streams after a session has been established.
|
||||||
type Hijacker interface {
|
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 {
|
type body struct {
|
||||||
str quic.Stream
|
str *stream
|
||||||
|
|
||||||
wasHijacked bool // set when HTTPStream is called
|
remainingContentLength int64
|
||||||
|
violatedContentLength bool
|
||||||
|
hasContentLength bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func newBody(str *stream, contentLength int64) *body {
|
||||||
_ io.ReadCloser = &body{}
|
b := &body{str: str}
|
||||||
_ HTTPStreamer = &body{}
|
if contentLength >= 0 {
|
||||||
)
|
b.hasContentLength = true
|
||||||
|
b.remainingContentLength = contentLength
|
||||||
func newRequestBody(str Stream) *body {
|
}
|
||||||
return &body{str: str}
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *body) HTTPStream() Stream {
|
func (r *body) StreamID() quic.StreamID { return r.str.StreamID() }
|
||||||
r.wasHijacked = true
|
|
||||||
return r.str
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *body) wasStreamHijacked() bool {
|
func (r *body) checkContentLengthViolation() error {
|
||||||
return r.wasHijacked
|
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) {
|
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)
|
n, err := r.str.Read(b)
|
||||||
|
r.remainingContentLength -= int64(n)
|
||||||
|
if err := r.checkContentLengthViolation(); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
return n, maybeReplaceError(err)
|
return n, maybeReplaceError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +71,26 @@ func (r *body) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type hijackableBody struct {
|
type requestBody struct {
|
||||||
body
|
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
|
// only set for the http.Response
|
||||||
// The channel is closed when the user is done with this response:
|
// The channel is closed when the user is done with this response:
|
||||||
|
@ -83,27 +99,17 @@ type hijackableBody struct {
|
||||||
reqDoneClosed bool
|
reqDoneClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _ io.ReadCloser = &hijackableBody{}
|
||||||
_ Hijacker = &hijackableBody{}
|
|
||||||
_ HTTPStreamer = &hijackableBody{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newResponseBody(str Stream, conn quic.Connection, done chan<- struct{}) *hijackableBody {
|
func newResponseBody(str *stream, contentLength int64, done chan<- struct{}) *hijackableBody {
|
||||||
return &hijackableBody{
|
return &hijackableBody{
|
||||||
body: body{
|
body: *newBody(str, contentLength),
|
||||||
str: str,
|
|
||||||
},
|
|
||||||
reqDone: done,
|
reqDone: done,
|
||||||
conn: conn,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *hijackableBody) StreamCreator() StreamCreator {
|
|
||||||
return r.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hijackableBody) Read(b []byte) (int, error) {
|
func (r *hijackableBody) Read(b []byte) (int, error) {
|
||||||
n, err := r.str.Read(b)
|
n, err := r.body.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.requestDone()
|
r.requestDone()
|
||||||
}
|
}
|
||||||
|
@ -120,17 +126,9 @@ func (r *hijackableBody) requestDone() {
|
||||||
r.reqDoneClosed = true
|
r.reqDoneClosed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *body) StreamID() quic.StreamID {
|
|
||||||
return r.str.StreamID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hijackableBody) Close() error {
|
func (r *hijackableBody) Close() error {
|
||||||
r.requestDone()
|
r.requestDone()
|
||||||
// If the EOF was read, CancelRead() is a no-op.
|
// 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
|
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 @@ package http3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"net/http/httptrace"
|
||||||
|
"net/textproto"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/internal/protocol"
|
"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/quic-go/quicvarint"
|
||||||
|
|
||||||
"github.com/quic-go/qpack"
|
"github.com/quic-go/qpack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodGet0RTT allows a GET request to be sent using 0-RTT.
|
const (
|
||||||
// Note that 0-RTT data doesn't provide replay protection.
|
// MethodGet0RTT allows a GET request to be sent using 0-RTT.
|
||||||
const MethodGet0RTT = "GET_0RTT"
|
// 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 (
|
const (
|
||||||
defaultUserAgent = "quic-go HTTP/3"
|
defaultUserAgent = "quic-go HTTP/3"
|
||||||
|
@ -35,122 +38,67 @@ var defaultQuicConfig = &quic.Config{
|
||||||
KeepAlivePeriod: 10 * time.Second,
|
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 {
|
// Additional HTTP/3 settings.
|
||||||
DisableCompression bool
|
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
|
||||||
EnableDatagram bool
|
|
||||||
MaxHeaderBytes int64
|
|
||||||
AdditionalSettings map[uint64]uint64
|
AdditionalSettings map[uint64]uint64
|
||||||
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 func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
UniStreamHijacker func(StreamType, quic.ConnectionTracingID, quic.ReceiveStream, error) (hijacked bool)
|
||||||
}
|
|
||||||
|
|
||||||
// client is a HTTP3 client doing requests
|
// MaxResponseHeaderBytes specifies a limit on how many response bytes are
|
||||||
type client struct {
|
// allowed in the server's response header.
|
||||||
tlsConf *tls.Config
|
// Zero means to use a default limit.
|
||||||
config *quic.Config
|
MaxResponseHeaderBytes int64
|
||||||
opts *roundTripperOpts
|
|
||||||
|
|
||||||
dialOnce sync.Once
|
// DisableCompression, if true, prevents the Transport from requesting compression with an
|
||||||
dialer dialFunc
|
// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
|
||||||
handshakeErr error
|
// 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
|
Logger *slog.Logger
|
||||||
settings *Settings // set once receivedSettings is closed
|
|
||||||
|
|
||||||
|
initOnce sync.Once
|
||||||
|
hconn *connection
|
||||||
requestWriter *requestWriter
|
requestWriter *requestWriter
|
||||||
|
decoder *qpack.Decoder
|
||||||
decoder *qpack.Decoder
|
|
||||||
|
|
||||||
hostname string
|
|
||||||
conn atomic.Pointer[quic.EarlyConnection]
|
|
||||||
|
|
||||||
logger utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ roundTripCloser = &client{}
|
var _ http.RoundTripper = &SingleDestinationRoundTripper{}
|
||||||
|
|
||||||
func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) {
|
func (c *SingleDestinationRoundTripper) Start() Connection {
|
||||||
if conf == nil {
|
c.initOnce.Do(func() { c.init() })
|
||||||
conf = defaultQuicConfig.Clone()
|
return c.hconn
|
||||||
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 *client) dial(ctx context.Context) error {
|
func (c *SingleDestinationRoundTripper) init() {
|
||||||
var err error
|
c.decoder = qpack.NewDecoder(func(hf qpack.HeaderField) {})
|
||||||
var conn quic.EarlyConnection
|
c.requestWriter = newRequestWriter()
|
||||||
if c.dialer != nil {
|
c.hconn = newConnection(c.Connection, c.EnableDatagrams, protocol.PerspectiveClient, c.Logger)
|
||||||
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)
|
|
||||||
|
|
||||||
// send the SETTINGs frame, using 0-RTT data, if possible
|
// send the SETTINGs frame, using 0-RTT data, if possible
|
||||||
go func() {
|
go func() {
|
||||||
if err := c.setupConn(conn); err != nil {
|
if err := c.setupConn(c.hconn); err != nil {
|
||||||
c.logger.Debugf("Setting up connection failed: %s", err)
|
if c.Logger != nil {
|
||||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
|
c.Logger.Debug("Setting up connection failed", "error", err)
|
||||||
|
}
|
||||||
|
c.hconn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if c.StreamHijacker != nil {
|
||||||
if c.opts.StreamHijacker != nil {
|
go c.handleBidirectionalStreams()
|
||||||
go c.handleBidirectionalStreams(conn)
|
|
||||||
}
|
}
|
||||||
go c.handleUnidirectionalStreams(conn)
|
go c.hconn.HandleUnidirectionalStreams(c.UniStreamHijacker)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) setupConn(conn quic.EarlyConnection) error {
|
func (c *SingleDestinationRoundTripper) setupConn(conn *connection) error {
|
||||||
// open the control stream
|
// open the control stream
|
||||||
str, err := conn.OpenUniStream()
|
str, err := conn.OpenUniStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -159,122 +107,50 @@ func (c *client) setupConn(conn quic.EarlyConnection) error {
|
||||||
b := make([]byte, 0, 64)
|
b := make([]byte, 0, 64)
|
||||||
b = quicvarint.Append(b, streamTypeControlStream)
|
b = quicvarint.Append(b, streamTypeControlStream)
|
||||||
// send the SETTINGS frame
|
// 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)
|
_, err = str.Write(b)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) handleBidirectionalStreams(conn quic.EarlyConnection) {
|
func (c *SingleDestinationRoundTripper) handleBidirectionalStreams() {
|
||||||
for {
|
for {
|
||||||
str, err := conn.AcceptStream(context.Background())
|
str, err := c.hconn.AcceptStream(context.Background())
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
go func(str quic.Stream) {
|
go func(str quic.Stream) {
|
||||||
_, err := parseNextFrame(str, func(ft FrameType, e error) (processed bool, err error) {
|
_, 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 {
|
if err == errHijacked {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
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)
|
}(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
func (c *SingleDestinationRoundTripper) maxHeaderBytes() uint64 {
|
||||||
var rcvdControlStream atomic.Bool
|
if c.MaxResponseHeaderBytes <= 0 {
|
||||||
|
|
||||||
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 {
|
|
||||||
return defaultMaxResponseHeaderBytes
|
return defaultMaxResponseHeaderBytes
|
||||||
}
|
}
|
||||||
return uint64(c.opts.MaxHeaderBytes)
|
return uint64(c.MaxResponseHeaderBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTripOpt executes a request and returns a response
|
// RoundTrip executes a request and returns a response
|
||||||
func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
func (c *SingleDestinationRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
rsp, err := c.roundTripOpt(req, opt)
|
c.initOnce.Do(func() { c.init() })
|
||||||
|
|
||||||
|
rsp, err := c.roundTrip(req)
|
||||||
if err != nil && req.Context().Err() != nil {
|
if err != nil && req.Context().Err() != nil {
|
||||||
// if the context was canceled, return the context cancellation error
|
// if the context was canceled, return the context cancellation error
|
||||||
err = req.Context().Err()
|
err = req.Context().Err()
|
||||||
|
@ -282,46 +158,48 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||||
return rsp, err
|
return rsp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
func (c *SingleDestinationRoundTripper) roundTrip(req *http.Request) (*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()
|
|
||||||
|
|
||||||
// Immediately send out this request, if this is a 0-RTT request.
|
// 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
|
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
|
// wait for the handshake to complete
|
||||||
select {
|
earlyConn, ok := c.Connection.(quic.EarlyConnection)
|
||||||
case <-conn.HandshakeComplete():
|
if ok {
|
||||||
case <-req.Context().Done():
|
select {
|
||||||
return nil, req.Context().Err()
|
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
|
// wait for the server's SETTINGS frame to arrive
|
||||||
select {
|
select {
|
||||||
case <-c.receivedSettings:
|
case <-c.hconn.ReceivedSettings():
|
||||||
case <-conn.Context().Done():
|
case <-connCtx.Done():
|
||||||
return nil, context.Cause(conn.Context())
|
return nil, context.Cause(connCtx)
|
||||||
}
|
}
|
||||||
if err := opt.CheckSettings(*c.settings); err != nil {
|
if !c.hconn.Settings().EnableExtendedConnect {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -329,7 +207,6 @@ func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||||
// Request Cancellation:
|
// Request Cancellation:
|
||||||
// This go routine keeps running even after RoundTripOpt() returns.
|
// This go routine keeps running even after RoundTripOpt() returns.
|
||||||
// It is shut down when the application is done processing the body.
|
// It is shut down when the application is done processing the body.
|
||||||
reqDone := make(chan struct{})
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
@ -341,31 +218,19 @@ func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
doneChan := reqDone
|
rsp, err := c.doRequest(req, str)
|
||||||
if opt.DontCloseRequestStream {
|
if err != nil { // if any error occurred
|
||||||
doneChan = nil
|
|
||||||
}
|
|
||||||
rsp, rerr := c.doRequest(req, conn, str, opt, doneChan)
|
|
||||||
if rerr.err != nil { // if any error occurred
|
|
||||||
close(reqDone)
|
close(reqDone)
|
||||||
<-done
|
<-done
|
||||||
if rerr.streamErr != 0 { // if it was a stream error
|
return nil, maybeReplaceError(err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
if opt.DontCloseRequestStream {
|
return rsp, maybeReplaceError(err)
|
||||||
close(reqDone)
|
}
|
||||||
<-done
|
|
||||||
}
|
func (c *SingleDestinationRoundTripper) OpenRequestStream(ctx context.Context) (RequestStream, error) {
|
||||||
return rsp, maybeReplaceError(rerr.err)
|
c.initOnce.Do(func() { c.init() })
|
||||||
|
|
||||||
|
return c.hconn.openRequestStream(ctx, c.requestWriter, nil, c.DisableCompression, c.maxHeaderBytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancelingReader reads from the io.Reader.
|
// cancelingReader reads from the io.Reader.
|
||||||
|
@ -383,7 +248,7 @@ func (r *cancelingReader) Read(b []byte) (int, error) {
|
||||||
return n, err
|
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()
|
defer body.Close()
|
||||||
buf := make([]byte, bodyCopyBufferSize)
|
buf := make([]byte, bodyCopyBufferSize)
|
||||||
sr := &cancelingReader{str: str, r: body}
|
sr := &cancelingReader{str: str, r: body}
|
||||||
|
@ -407,21 +272,13 @@ func (c *client) sendRequestBody(str Stream, body io.ReadCloser, contentLength i
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) doRequest(req *http.Request, conn quic.EarlyConnection, str quic.Stream, opt RoundTripOpt, reqDone chan<- struct{}) (*http.Response, requestError) {
|
func (c *SingleDestinationRoundTripper) doRequest(req *http.Request, str *requestStream) (*http.Response, error) {
|
||||||
var requestGzip bool
|
if err := str.SendRequestHeader(req); err != nil {
|
||||||
if !c.opts.DisableCompression && req.Method != "HEAD" && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
|
return nil, err
|
||||||
requestGzip = true
|
|
||||||
}
|
}
|
||||||
if err := c.requestWriter.WriteRequestHeader(str, req, requestGzip); err != nil {
|
if req.Body == nil {
|
||||||
return nil, newStreamError(ErrCodeInternalError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Body == nil && !opt.DontCloseRequestStream {
|
|
||||||
str.Close()
|
str.Close()
|
||||||
}
|
} else {
|
||||||
|
|
||||||
hstr := newStream(str, func() { conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "") })
|
|
||||||
if req.Body != nil {
|
|
||||||
// send the request body asynchronously
|
// send the request body asynchronously
|
||||||
go func() {
|
go func() {
|
||||||
contentLength := int64(-1)
|
contentLength := int64(-1)
|
||||||
|
@ -430,89 +287,47 @@ func (c *client) doRequest(req *http.Request, conn quic.EarlyConnection, str qui
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
contentLength = req.ContentLength
|
contentLength = req.ContentLength
|
||||||
}
|
}
|
||||||
if err := c.sendRequestBody(hstr, req.Body, contentLength); err != nil {
|
if err := c.sendRequestBody(str, req.Body, contentLength); err != nil {
|
||||||
c.logger.Errorf("Error writing request: %s", err)
|
if c.Logger != nil {
|
||||||
}
|
c.Logger.Debug("error writing request", "error", err)
|
||||||
if !opt.DontCloseRequestStream {
|
}
|
||||||
hstr.Close()
|
|
||||||
}
|
}
|
||||||
|
str.Close()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
frame, err := parseNextFrame(str, nil)
|
// copy from net/http: support 1xx responses
|
||||||
if err != nil {
|
trace := httptrace.ContextClientTrace(req.Context())
|
||||||
return nil, newStreamError(ErrCodeFrameError, err)
|
num1xx := 0 // number of informational 1xx headers received
|
||||||
}
|
const max1xxResponses = 5 // arbitrary bound on number of informational responses
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := responseFromHeaders(hfs)
|
var res *http.Response
|
||||||
if err != nil {
|
for {
|
||||||
return nil, newStreamError(ErrCodeMessageError, err)
|
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.TLS = &connState
|
||||||
res.Request = req
|
res.Request = req
|
||||||
// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
|
return res, nil
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
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 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go/internal/protocol"
|
|
||||||
"github.com/quic-go/quic-go/quicvarint"
|
"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 {
|
func (f *settingsFrame) Append(b []byte) []byte {
|
||||||
b = quicvarint.Append(b, 0x4)
|
b = quicvarint.Append(b, 0x4)
|
||||||
var l protocol.ByteCount
|
var l int
|
||||||
for id, val := range f.Other {
|
for id, val := range f.Other {
|
||||||
l += quicvarint.Len(id) + quicvarint.Len(val)
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostnameFromRequest(req *http.Request) string {
|
func hostnameFromURL(url *url.URL) string {
|
||||||
if req.URL != nil {
|
if url != nil {
|
||||||
return req.URL.Host
|
return url.Host
|
||||||
}
|
}
|
||||||
return ""
|
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
|
package http3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"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.
|
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
|
||||||
type Stream quic.Stream
|
type Stream interface {
|
||||||
|
|
||||||
// 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 {
|
|
||||||
quic.Stream
|
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
|
bytesRemainingInFrame uint64
|
||||||
|
|
||||||
|
datagrams *datagrammer
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Stream = &stream{}
|
var _ Stream = &stream{}
|
||||||
|
|
||||||
func newStream(str quic.Stream, onFrameError func()) *stream {
|
func newStream(str quic.Stream, conn *connection, datagrams *datagrammer) *stream {
|
||||||
return &stream{
|
return &stream{
|
||||||
Stream: str,
|
Stream: str,
|
||||||
onFrameError: onFrameError,
|
conn: conn,
|
||||||
buf: make([]byte, 0, 16),
|
buf: make([]byte, 16),
|
||||||
|
datagrams: datagrams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +78,7 @@ func (s *stream) Read(b []byte) (int, error) {
|
||||||
s.bytesRemainingInFrame = f.Length
|
s.bytesRemainingInFrame = f.Length
|
||||||
break parseLoop
|
break parseLoop
|
||||||
default:
|
default:
|
||||||
s.onFrameError()
|
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
|
||||||
// parseNextFrame skips over unknown frame types
|
// parseNextFrame skips over unknown frame types
|
||||||
// Therefore, this condition is only entered when we parsed another known frame type.
|
// Therefore, this condition is only entered when we parsed another known frame type.
|
||||||
return 0, fmt.Errorf("peer sent an unexpected frame: %T", f)
|
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)
|
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
|
*stream
|
||||||
contentLength int64
|
|
||||||
read int64
|
responseBody io.ReadCloser // set by ReadResponse
|
||||||
resetStream bool
|
|
||||||
|
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 {
|
func newRequestStream(
|
||||||
return &lengthLimitedStream{
|
str *stream,
|
||||||
stream: str,
|
requestWriter *requestWriter,
|
||||||
contentLength: contentLength,
|
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 {
|
func (s *requestStream) Read(b []byte) (int, error) {
|
||||||
if s.read > s.contentLength || s.read == s.contentLength && s.hasMoreData() {
|
if s.responseBody == nil {
|
||||||
if !s.resetStream {
|
return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first")
|
||||||
s.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
}
|
||||||
s.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
return s.responseBody.Read(b)
|
||||||
s.resetStream = true
|
}
|
||||||
|
|
||||||
|
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) {
|
func (s *stream) SendDatagram(b []byte) error {
|
||||||
if err := s.checkContentLengthViolation(); err != nil {
|
// TODO: reject if datagrams are not negotiated (yet)
|
||||||
return 0, err
|
return s.conn.sendDatagram(s.Stream.StreamID(), b)
|
||||||
}
|
}
|
||||||
n, err := s.stream.Read(b[:min(int64(len(b)), s.contentLength-s.read)])
|
|
||||||
s.read += int64(n)
|
func (s *stream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
|
||||||
if err := s.checkContentLengthViolation(); err != nil {
|
// TODO: reject if datagrams are not negotiated (yet)
|
||||||
return n, err
|
return s.datagrams.Receive(ctx)
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
|
|
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
|
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"
|
//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 RoundTripCloser = roundTripCloser
|
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"
|
//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 @@ import (
|
||||||
|
|
||||||
"github.com/quic-go/qpack"
|
"github.com/quic-go/qpack"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const bodyCopyBufferSize = 8 * 1024
|
const bodyCopyBufferSize = 8 * 1024
|
||||||
|
@ -26,17 +25,14 @@ type requestWriter struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
encoder *qpack.Encoder
|
encoder *qpack.Encoder
|
||||||
headerBuf *bytes.Buffer
|
headerBuf *bytes.Buffer
|
||||||
|
|
||||||
logger utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRequestWriter(logger utils.Logger) *requestWriter {
|
func newRequestWriter() *requestWriter {
|
||||||
headerBuf := &bytes.Buffer{}
|
headerBuf := &bytes.Buffer{}
|
||||||
encoder := qpack.NewEncoder(headerBuf)
|
encoder := qpack.NewEncoder(headerBuf)
|
||||||
return &requestWriter{
|
return &requestWriter{
|
||||||
encoder: encoder,
|
encoder: encoder,
|
||||||
headerBuf: headerBuf,
|
headerBuf: headerBuf,
|
||||||
logger: logger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +65,10 @@ func (w *requestWriter) writeHeaders(wr io.Writer, req *http.Request, gzip bool)
|
||||||
return err
|
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
|
// copied from net/transport.go
|
||||||
// Modified to support Extended CONNECT:
|
// Modified to support Extended CONNECT:
|
||||||
// Contrary to what the godoc for the http.Request says,
|
// 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
|
// 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
|
var path string
|
||||||
if req.Method != http.MethodConnect || isExtendedConnect {
|
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)
|
// 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.
|
// 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)
|
host, port, err := net.SplitHostPort(authority)
|
||||||
if err != nil { // authority didn't have a port
|
if err != nil { // authority didn't have a port
|
||||||
port = "443"
|
port = "443"
|
||||||
if scheme == "http" {
|
|
||||||
port = "80"
|
|
||||||
}
|
|
||||||
host = authority
|
host = authority
|
||||||
}
|
}
|
||||||
if a, err := idna.ToASCII(host); err == nil {
|
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
|
package http3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
|
||||||
"github.com/quic-go/quic-go/internal/utils"
|
|
||||||
|
|
||||||
"github.com/quic-go/qpack"
|
"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 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)
|
// The frame has a type and length field, both QUIC varints (maximum 8 bytes in length)
|
||||||
const frameHeaderLen = 16
|
const frameHeaderLen = 16
|
||||||
|
|
||||||
// headerWriter wraps the stream, so that the first Write call flushes the header to the stream
|
const maxSmallResponseSize = 4096
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
*headerWriter
|
str *stream
|
||||||
conn quic.Connection
|
|
||||||
bufferedStr *bufio.Writer
|
|
||||||
buf []byte
|
|
||||||
|
|
||||||
contentLen int64 // if handler set valid Content-Length header
|
conn Connection
|
||||||
numWritten int64 // bytes written
|
header http.Header
|
||||||
headerWritten bool
|
buf []byte
|
||||||
isHead bool
|
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 (
|
var (
|
||||||
_ http.ResponseWriter = &responseWriter{}
|
_ http.ResponseWriter = &responseWriter{}
|
||||||
_ http.Flusher = &responseWriter{}
|
_ http.Flusher = &responseWriter{}
|
||||||
_ Hijacker = &responseWriter{}
|
_ Hijacker = &responseWriter{}
|
||||||
|
_ HTTPStreamer = &responseWriter{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newResponseWriter(str quic.Stream, conn quic.Connection, logger utils.Logger) *responseWriter {
|
func newResponseWriter(str *stream, conn Connection, isHead bool, logger *slog.Logger) *responseWriter {
|
||||||
hw := &headerWriter{
|
|
||||||
str: str,
|
|
||||||
header: http.Header{},
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return &responseWriter{
|
return &responseWriter{
|
||||||
headerWriter: hw,
|
str: str,
|
||||||
buf: make([]byte, frameHeaderLen),
|
conn: conn,
|
||||||
conn: conn,
|
header: http.Header{},
|
||||||
bufferedStr: bufio.NewWriter(hw),
|
buf: make([]byte, frameHeaderLen),
|
||||||
|
isHead: isHead,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +71,7 @@ func (w *responseWriter) Header() http.Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeader(status int) {
|
func (w *responseWriter) WriteHeader(status int) {
|
||||||
if w.headerWritten {
|
if w.headerComplete {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,51 +79,57 @@ func (w *responseWriter) WriteHeader(status int) {
|
||||||
if status < 100 || status > 999 {
|
if status < 100 || status > 999 {
|
||||||
panic(fmt.Sprintf("invalid WriteHeader code %v", status))
|
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
|
w.status = status
|
||||||
|
|
||||||
if !w.headerWritten {
|
// immediately write 1xx headers
|
||||||
w.writeHeader()
|
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) {
|
func (w *responseWriter) Write(p []byte) (int, error) {
|
||||||
bodyAllowed := bodyAllowedForStatus(w.status)
|
bodyAllowed := bodyAllowedForStatus(w.status)
|
||||||
if !w.headerWritten {
|
if !w.headerComplete {
|
||||||
// If body is not allowed, we don't need to (and we can't) sniff the content type.
|
w.sniffContentType(p)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
bodyAllowed = true
|
bodyAllowed = true
|
||||||
}
|
}
|
||||||
|
@ -167,36 +146,101 @@ func (w *responseWriter) Write(p []byte) (int, error) {
|
||||||
return len(p), nil
|
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 = w.buf[:0]
|
||||||
w.buf = df.Append(w.buf)
|
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)
|
return 0, maybeReplaceError(err)
|
||||||
}
|
}
|
||||||
n, err := w.bufferedStr.Write(p)
|
if len(w.smallResponseBuf) > 0 {
|
||||||
return n, maybeReplaceError(err)
|
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 {
|
func (w *responseWriter) FlushError() error {
|
||||||
if !w.headerWritten {
|
if !w.headerComplete {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
if !w.written {
|
_, err := w.doWrite(nil)
|
||||||
if err := w.writeHeader(); err != nil {
|
return err
|
||||||
return maybeReplaceError(err)
|
|
||||||
}
|
|
||||||
w.written = true
|
|
||||||
}
|
|
||||||
return w.bufferedStr.Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Flush() {
|
func (w *responseWriter) Flush() {
|
||||||
if err := w.FlushError(); err != nil {
|
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
|
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 @@ import (
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"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.
|
// Settings are HTTP/3 settings that apply to the underlying connection.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
// Support for HTTP/3 datagrams (RFC 9297)
|
// Support for HTTP/3 datagrams (RFC 9297)
|
||||||
EnableDatagram bool
|
EnableDatagrams bool
|
||||||
// Extended CONNECT, RFC 9220
|
// Extended CONNECT, RFC 9220
|
||||||
EnableExtendedConnect bool
|
EnableExtendedConnect bool
|
||||||
// Other settings, defined by the application
|
// Other settings, defined by the application
|
||||||
|
@ -32,69 +33,43 @@ type RoundTripOpt struct {
|
||||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||||
OnlyCachedConn bool
|
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 {
|
type singleRoundTripper interface {
|
||||||
RoundTripOpt(*http.Request, RoundTripOpt) (*http.Response, error)
|
OpenRequestStream(context.Context) (RequestStream, error)
|
||||||
HandshakeComplete() bool
|
RoundTrip(*http.Request) (*http.Response, error)
|
||||||
io.Closer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type roundTripCloserWithCount struct {
|
type roundTripperWithCount struct {
|
||||||
roundTripCloser
|
cancel context.CancelFunc
|
||||||
|
dialing chan struct{} // closed as soon as quic.Dial(Early) returned
|
||||||
|
dialErr error
|
||||||
|
conn quic.EarlyConnection
|
||||||
|
rt singleRoundTripper
|
||||||
|
|
||||||
useCount atomic.Int64
|
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
|
// RoundTripper implements the http.RoundTripper interface
|
||||||
type RoundTripper struct {
|
type RoundTripper struct {
|
||||||
mutex sync.Mutex
|
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
|
// TLSClientConfig specifies the TLS configuration to use with
|
||||||
// tls.Client. If nil, the default configuration is used.
|
// tls.Client. If nil, the default configuration is used.
|
||||||
TLSClientConfig *tls.Config
|
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.
|
// If nil, reasonable default values will be used.
|
||||||
QuicConfig *quic.Config
|
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)
|
|
||||||
|
|
||||||
// Dial specifies an optional dial function for creating QUIC
|
// Dial specifies an optional dial function for creating QUIC
|
||||||
// connections for requests.
|
// connections for requests.
|
||||||
|
@ -102,13 +77,32 @@ type RoundTripper struct {
|
||||||
// and will be reused for subsequent connections to other servers.
|
// 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)
|
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
|
// MaxResponseHeaderBytes specifies a limit on how many response bytes are
|
||||||
// allowed in the server's response header.
|
// allowed in the server's response header.
|
||||||
// Zero means to use a default limit.
|
// Zero means to use a default limit.
|
||||||
MaxResponseHeaderBytes int64
|
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
|
// DisableCompression, if true, prevents the Transport from requesting compression with an
|
||||||
clients map[string]*roundTripCloserWithCount
|
// "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
|
transport *quic.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +116,11 @@ var ErrNoCachedConn = errors.New("http3: no cached connection was available")
|
||||||
|
|
||||||
// RoundTripOpt is like RoundTrip, but takes options.
|
// RoundTripOpt is like RoundTrip, but takes options.
|
||||||
func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
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 {
|
if req.URL == nil {
|
||||||
closeRequestBody(req)
|
closeRequestBody(req)
|
||||||
return nil, errors.New("http3: nil Request.URL")
|
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)
|
return nil, fmt.Errorf("http3: invalid method %q", req.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname := authorityAddr("https", hostnameFromRequest(req))
|
hostname := authorityAddr(hostnameFromURL(req.URL))
|
||||||
cl, isReused, err := r.getClient(hostname, opt.OnlyCachedConn)
|
cl, isReused, err := r.getClient(req.Context(), hostname, opt.OnlyCachedConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
defer cl.useCount.Add(-1)
|
||||||
rsp, err := cl.RoundTripOpt(req, opt)
|
rsp, err := cl.rt.RoundTrip(req)
|
||||||
if err != nil {
|
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 isReused {
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||||
return r.RoundTripOpt(req, opt)
|
return r.RoundTripOpt(req, opt)
|
||||||
|
@ -177,59 +192,126 @@ func (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
return r.RoundTripOpt(req, RoundTripOpt{})
|
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()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
if r.clients == nil {
|
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 !ok {
|
||||||
if onlyCached {
|
if onlyCached {
|
||||||
return nil, false, ErrNoCachedConn
|
return nil, false, ErrNoCachedConn
|
||||||
}
|
}
|
||||||
var err error
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
newCl := newClient
|
cl = &roundTripperWithCount{
|
||||||
if r.newClient != nil {
|
dialing: make(chan struct{}),
|
||||||
newCl = r.newClient
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
dial := r.Dial
|
go func() {
|
||||||
if dial == nil {
|
defer close(cl.dialing)
|
||||||
if r.transport == nil {
|
defer cancel()
|
||||||
udpConn, err := net.ListenUDP("udp", nil)
|
conn, rt, err := r.dial(ctx, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
cl.dialErr = err
|
||||||
}
|
return
|
||||||
r.transport = &quic.Transport{Conn: udpConn}
|
|
||||||
}
|
}
|
||||||
dial = r.makeDialer()
|
cl.conn = conn
|
||||||
}
|
cl.rt = rt
|
||||||
c, err := newCl(
|
}()
|
||||||
hostname,
|
r.clients[hostname] = cl
|
||||||
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
|
|
||||||
}
|
}
|
||||||
client.useCount.Add(1)
|
select {
|
||||||
return client, isReused, nil
|
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) {
|
func (r *RoundTripper) removeClient(hostname string) {
|
||||||
|
@ -246,8 +328,8 @@ func (r *RoundTripper) removeClient(hostname string) {
|
||||||
func (r *RoundTripper) Close() error {
|
func (r *RoundTripper) Close() error {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
for _, client := range r.clients {
|
for _, cl := range r.clients {
|
||||||
if err := client.Close(); err != nil {
|
if err := cl.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,23 +374,12 @@ func isNotToken(r rune) bool {
|
||||||
return !httpguts.IsTokenRune(r)
|
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() {
|
func (r *RoundTripper) CloseIdleConnections() {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
for hostname, client := range r.clients {
|
for hostname, cl := range r.clients {
|
||||||
if client.useCount.Load() == 0 {
|
if cl.useCount.Load() == 0 {
|
||||||
client.Close()
|
cl.Close()
|
||||||
delete(r.clients, hostname)
|
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 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/internal/protocol"
|
"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/quic-go/quicvarint"
|
||||||
|
|
||||||
"github.com/quic-go/qpack"
|
"github.com/quic-go/qpack"
|
||||||
|
@ -31,7 +30,6 @@ var (
|
||||||
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
|
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
|
||||||
return quic.ListenAddrEarly(addr, tlsConf, config)
|
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.
|
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
|
||||||
|
@ -127,20 +125,6 @@ var ServerContextKey = &contextKey{"http3-server"}
|
||||||
// than its string representation.
|
// than its string representation.
|
||||||
var RemoteAddrContextKey = &contextKey{"remote-addr"}
|
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
|
// listenerInfo contains info about specific listener added with addListener
|
||||||
type listenerInfo struct {
|
type listenerInfo struct {
|
||||||
port int // 0 means that no info about port is available
|
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
|
// 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
|
// have valid port numbers, the port part is used in Alt-Svc headers set
|
||||||
// with SetQuicHeaders.
|
// with SetQUICHeaders.
|
||||||
Addr string
|
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.
|
// needed Port can be manually set when the Server is created.
|
||||||
//
|
//
|
||||||
// This is useful when a Layer 4 firewall is redirecting UDP traffic and
|
// 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.
|
// set for ListenAndServe and Serve methods.
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
// QuicConfig provides the parameters for QUIC connection created with
|
// QUICConfig provides the parameters for QUIC connection created with Serve.
|
||||||
// Serve. If nil, it uses reasonable default values.
|
// If nil, it uses reasonable default values.
|
||||||
//
|
//
|
||||||
// Configured versions are also used in Alt-Svc response header set with
|
// Configured versions are also used in Alt-Svc response header set with SetQUICHeaders.
|
||||||
// SetQuicHeaders.
|
QUICConfig *quic.Config
|
||||||
QuicConfig *quic.Config
|
|
||||||
|
|
||||||
// Handler is the HTTP request handler to use. If not set, defaults to
|
// Handler is the HTTP request handler to use. If not set, defaults to
|
||||||
// http.NotFound.
|
// http.NotFound.
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
|
|
||||||
// EnableDatagrams enables support for HTTP/3 datagrams.
|
// EnableDatagrams enables support for HTTP/3 datagrams (RFC 9297).
|
||||||
// If set to true, QuicConfig.EnableDatagram will be set.
|
// If set to true, QUICConfig.EnableDatagrams will be set.
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc9297.
|
|
||||||
EnableDatagrams bool
|
EnableDatagrams bool
|
||||||
|
|
||||||
// MaxHeaderBytes controls the maximum number of bytes the server will
|
// MaxHeaderBytes controls the maximum number of bytes the server will
|
||||||
|
@ -195,7 +177,7 @@ type Server struct {
|
||||||
MaxHeaderBytes int
|
MaxHeaderBytes int
|
||||||
|
|
||||||
// AdditionalSettings specifies additional HTTP/3 settings.
|
// 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
|
AdditionalSettings map[uint64]uint64
|
||||||
|
|
||||||
// StreamHijacker, when set, is called for the first unknown frame parsed on a bidirectional stream.
|
// 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
|
// Callers can either ignore the frame and return control of the stream back to HTTP/3
|
||||||
// (by returning hijacked false).
|
// (by returning hijacked false).
|
||||||
// Alternatively, callers can take over the QUIC stream (by returning hijacked true).
|
// 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.
|
// 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.
|
// If parsing the stream type fails, the error is passed to the callback.
|
||||||
// In that case, the stream type will not be set.
|
// 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
|
// ConnContext optionally specifies a function that modifies
|
||||||
// the context used for a new connection c. The provided ctx
|
// the context used for a new connection c. The provided ctx
|
||||||
// has a ServerContextKey value.
|
// has a ServerContextKey value.
|
||||||
ConnContext func(ctx context.Context, c quic.Connection) context.Context
|
ConnContext func(ctx context.Context, c quic.Connection) context.Context
|
||||||
|
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
listeners map[*QUICEarlyListener]listenerInfo
|
listeners map[*QUICEarlyListener]listenerInfo
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
|
|
||||||
altSvcHeader string
|
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.
|
// 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.
|
// ServeQUICConn serves a single QUIC connection.
|
||||||
func (s *Server) ServeQUICConn(conn quic.Connection) error {
|
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)
|
return s.handleConn(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +266,9 @@ func (s *Server) ServeListener(ln QUICEarlyListener) error {
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.handleConn(conn); err != nil {
|
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)
|
baseConf := ConfigureTLSConfig(tlsConf)
|
||||||
quicConf := s.QuicConfig
|
quicConf := s.QUICConfig
|
||||||
if quicConf == nil {
|
if quicConf == nil {
|
||||||
quicConf = &quic.Config{Allow0RTT: true}
|
quicConf = &quic.Config{Allow0RTT: true}
|
||||||
} else {
|
} else {
|
||||||
quicConf = s.QuicConfig.Clone()
|
quicConf = s.QUICConfig.Clone()
|
||||||
}
|
}
|
||||||
if s.EnableDatagrams {
|
if s.EnableDatagrams {
|
||||||
quicConf.EnableDatagrams = true
|
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.
|
// This code assumes that we will use protocol.SupportedVersions if no quic.Config is passed.
|
||||||
supportedVersions := protocol.SupportedVersions
|
supportedVersions := protocol.SupportedVersions
|
||||||
if s.QuicConfig != nil && len(s.QuicConfig.Versions) > 0 {
|
if s.QUICConfig != nil && len(s.QUICConfig.Versions) > 0 {
|
||||||
supportedVersions = s.QuicConfig.Versions
|
supportedVersions = s.QUICConfig.Versions
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep track of which have been seen so we don't yield duplicate values
|
// 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 {
|
if s.closed {
|
||||||
return http.ErrServerClosed
|
return http.ErrServerClosed
|
||||||
}
|
}
|
||||||
if s.logger == nil {
|
|
||||||
s.logger = utils.DefaultLogger.WithPrefix("server")
|
|
||||||
}
|
|
||||||
if s.listeners == nil {
|
if s.listeners == nil {
|
||||||
s.listeners = make(map[*QUICEarlyListener]listenerInfo)
|
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 {
|
if port, err := extractPort(laddr.String()); err == nil {
|
||||||
s.listeners[l] = listenerInfo{port}
|
s.listeners[l] = listenerInfo{port}
|
||||||
} else {
|
} 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.listeners[l] = listenerInfo{}
|
||||||
}
|
}
|
||||||
s.generateAltSvcHeader()
|
s.generateAltSvcHeader()
|
||||||
|
@ -443,8 +422,6 @@ func (s *Server) removeListener(l *QUICEarlyListener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleConn(conn quic.Connection) error {
|
func (s *Server) handleConn(conn quic.Connection) error {
|
||||||
decoder := qpack.NewDecoder(nil)
|
|
||||||
|
|
||||||
// send a SETTINGS frame
|
// send a SETTINGS frame
|
||||||
str, err := conn.OpenUniStream()
|
str, err := conn.OpenUniStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -459,12 +436,17 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
||||||
}).Append(b)
|
}).Append(b)
|
||||||
str.Write(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.
|
// Process all requests immediately.
|
||||||
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
|
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
|
||||||
for {
|
for {
|
||||||
str, err := conn.AcceptStream(context.Background())
|
str, datagrams, err := hconn.acceptStream(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var appErr *quic.ApplicationError
|
var appErr *quic.ApplicationError
|
||||||
if errors.As(err, &appErr) && appErr.ErrorCode == quic.ApplicationErrorCode(ErrCodeNoError) {
|
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)
|
return fmt.Errorf("accepting stream failed: %w", err)
|
||||||
}
|
}
|
||||||
go func() {
|
go s.handleRequest(hconn, str, datagrams, hconn.decoder)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,37 +465,53 @@ func (s *Server) maxHeaderBytes() uint64 {
|
||||||
return uint64(s.MaxHeaderBytes)
|
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
|
var ufh unknownFrameHandlerFunc
|
||||||
if s.StreamHijacker != nil {
|
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)
|
frame, err := parseNextFrame(str, ufh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errHijacked {
|
if !errors.Is(err, errHijacked) {
|
||||||
return requestError{err: errHijacked}
|
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||||
|
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
||||||
}
|
}
|
||||||
return newStreamError(ErrCodeRequestIncomplete, err)
|
return
|
||||||
}
|
}
|
||||||
hf, ok := frame.(*headersFrame)
|
hf, ok := frame.(*headersFrame)
|
||||||
if !ok {
|
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() {
|
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)
|
headerBlock := make([]byte, hf.Length)
|
||||||
if _, err := io.ReadFull(str, headerBlock); err != nil {
|
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)
|
hfs, err := decoder.DecodeFull(headerBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: use the right error code
|
// 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)
|
req, err := requestFromHeaders(hfs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newStreamError(ErrCodeMessageError, err)
|
str.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
||||||
|
str.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
connState := conn.ConnectionState().TLS
|
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).
|
// 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.
|
// See section 4.1.2 of RFC 9114.
|
||||||
var httpStr Stream
|
contentLength := int64(-1)
|
||||||
if _, ok := req.Header["Content-Length"]; ok && req.ContentLength >= 0 {
|
if _, ok := req.Header["Content-Length"]; ok && req.ContentLength >= 0 {
|
||||||
httpStr = newLengthLimitedStream(newStream(str, onFrameError), req.ContentLength)
|
contentLength = req.ContentLength
|
||||||
} else {
|
|
||||||
httpStr = newStream(str, onFrameError)
|
|
||||||
}
|
}
|
||||||
body := newRequestBody(httpStr)
|
hstr := newStream(str, conn, datagrams)
|
||||||
|
body := newRequestBody(hstr, contentLength, conn.Context(), conn.ReceivedSettings(), conn.Settings)
|
||||||
req.Body = body
|
req.Body = body
|
||||||
|
|
||||||
if s.logger.Debug() {
|
if s.Logger != nil {
|
||||||
s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID())
|
s.Logger.Debug("handling request", "method", req.Method, "host", req.Host, "uri", req.RequestURI)
|
||||||
} else {
|
|
||||||
s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := str.Context()
|
ctx := str.Context()
|
||||||
|
@ -634,15 +543,13 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
r := newResponseWriter(str, conn, s.logger)
|
r := newResponseWriter(hstr, conn, req.Method == http.MethodHead, s.Logger)
|
||||||
if req.Method == http.MethodHead {
|
|
||||||
r.isHead = true
|
|
||||||
}
|
|
||||||
handler := s.Handler
|
handler := s.Handler
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
handler = http.DefaultServeMux
|
handler = http.DefaultServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
|
||||||
var panicked bool
|
var panicked bool
|
||||||
func() {
|
func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -655,34 +562,42 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
||||||
const size = 64 << 10
|
const size = 64 << 10
|
||||||
buf := make([]byte, size)
|
buf := make([]byte, size)
|
||||||
buf = buf[:runtime.Stack(buf, false)]
|
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)
|
handler.ServeHTTP(r, req)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if body.wasStreamHijacked() {
|
if r.wasStreamHijacked() {
|
||||||
return requestError{err: errHijacked}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// only write response when there is no panic
|
// only write response when there is no panic
|
||||||
if !panicked {
|
if !panicked {
|
||||||
// response not written to the client yet, set Content-Length
|
// response not written to the client yet, set Content-Length
|
||||||
if !r.written {
|
if !r.headerWritten {
|
||||||
if _, haveCL := r.header["Content-Length"]; !haveCL {
|
if _, haveCL := r.header["Content-Length"]; !haveCL {
|
||||||
r.header.Set("Content-Length", strconv.FormatInt(r.numWritten, 10))
|
r.header.Set("Content-Length", strconv.FormatInt(r.numWritten, 10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.Flush()
|
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
|
// abort the stream when there is a panic
|
||||||
if panicked {
|
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.
|
// 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
|
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
|
// 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.
|
// (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")
|
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.
|
// 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
|
// 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 serverr.
|
// 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
|
// If no listener's Addr().String() returns an address with a valid port, Server.Addr will be used
|
||||||
// to extract the port, if specified.
|
// to extract the port, if specified.
|
||||||
// For example, a server launched using ListenAndServe on an address with port 443 would set:
|
// 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
|
// Alt-Svc: h3=":443"; ma=2592000
|
||||||
func (s *Server) SetQuicHeaders(hdr http.Header) error {
|
func (s *Server) SetQUICHeaders(hdr http.Header) error {
|
||||||
s.mutex.RLock()
|
s.mutex.RLock()
|
||||||
defer s.mutex.RUnlock()
|
defer s.mutex.RUnlock()
|
||||||
|
|
||||||
if s.altSvcHeader == "" {
|
if s.altSvcHeader == "" {
|
||||||
return ErrNoAltSvcPort
|
return ErrNoAltSvcPort
|
||||||
}
|
}
|
||||||
// use the map directly to avoid constant canonicalization
|
// use the map directly to avoid constant canonicalization since the key is already canonicalized
|
||||||
// since the key is already canonicalized
|
|
||||||
hdr["Alt-Svc"] = append(hdr["Alt-Svc"], s.altSvcHeader)
|
hdr["Alt-Svc"] = append(hdr["Alt-Svc"], s.altSvcHeader)
|
||||||
return nil
|
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
|
// ListenAndServeQUIC listens on the UDP network address addr and calls the
|
||||||
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
|
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
|
||||||
// used when handler is nil.
|
// used when handler is nil.
|
||||||
|
@ -792,7 +711,7 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error
|
||||||
qErr := make(chan error, 1)
|
qErr := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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)
|
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 @@ var Err0RTTRejected = errors.New("0-RTT rejected")
|
||||||
// as well as on the context passed to logging.Tracer.NewConnectionTracer.
|
// as well as on the context passed to logging.Tracer.NewConnectionTracer.
|
||||||
var ConnectionTracingKey = connTracingCtxKey{}
|
var ConnectionTracingKey = connTracingCtxKey{}
|
||||||
|
|
||||||
|
// ConnectionTracingID is the type of the context value saved under the ConnectionTracingKey.
|
||||||
|
type ConnectionTracingID uint64
|
||||||
|
|
||||||
type connTracingCtxKey struct{}
|
type connTracingCtxKey struct{}
|
||||||
|
|
||||||
// QUICVersionContextKey can be used to find out the QUIC version of a TLS handshake from the
|
// 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.
|
// CancelWrite aborts sending on this stream.
|
||||||
// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.
|
// 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.
|
// 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)
|
CancelWrite(StreamErrorCode)
|
||||||
// The Context is canceled as soon as the write-side of the stream is closed.
|
// 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
|
// 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() {
|
func (c *streamFlowController) Abandon() {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
unread := c.highestReceived - c.bytesRead
|
unread := c.highestReceived - c.bytesRead
|
||||||
|
c.bytesRead = c.highestReceived
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
if unread > 0 {
|
if unread > 0 {
|
||||||
c.connection.AddBytesRead(unread)
|
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 @@ package protocol
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// DesiredReceiveBufferSize is the kernel UDP receive buffer size that we'd like to use.
|
// 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.
|
// 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.
|
// InitialPacketSizeIPv4 is the maximum packet size that we use for sending IPv4 packets.
|
||||||
const InitialPacketSizeIPv4 = 1252
|
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.ECT1)
|
||||||
length += quicvarint.Len(f.ECNCE)
|
length += quicvarint.Len(f.ECNCE)
|
||||||
}
|
}
|
||||||
return length
|
return protocol.ByteCount(length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets the number of ACK ranges that can be encoded
|
// 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++ {
|
for i := 1; i < len(f.AckRanges); i++ {
|
||||||
gap, len := f.encodeAckRange(i)
|
gap, len := f.encodeAckRange(i)
|
||||||
rangeLen := quicvarint.Len(gap) + quicvarint.Len(len)
|
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.
|
// Writing range i would exceed the MaxAckFrameSize.
|
||||||
// So encode one range less than that.
|
// So encode one range less than that.
|
||||||
return i - 1
|
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
|
// Length of a written frame
|
||||||
func (f *ConnectionCloseFrame) Length(protocol.Version) protocol.ByteCount {
|
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 {
|
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
|
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
|
// Length of a written frame
|
||||||
func (f *CryptoFrame) Length(_ protocol.Version) protocol.ByteCount {
|
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
|
// MaxDataLen returns the maximum data length
|
||||||
func (f *CryptoFrame) MaxDataLen(maxSize protocol.ByteCount) protocol.ByteCount {
|
func (f *CryptoFrame) MaxDataLen(maxSize protocol.ByteCount) protocol.ByteCount {
|
||||||
// pretend that the data size will be 1 bytes
|
// 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
|
// 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 {
|
if headerLen > maxSize {
|
||||||
return 0
|
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
|
// Length of a written frame
|
||||||
func (f *DataBlockedFrame) Length(version protocol.Version) protocol.ByteCount {
|
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 {
|
func (f *DatagramFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||||
length := 1 + protocol.ByteCount(len(f.Data))
|
length := 1 + protocol.ByteCount(len(f.Data))
|
||||||
if f.DataLenPresent {
|
if f.DataLenPresent {
|
||||||
length += quicvarint.Len(uint64(len(f.Data)))
|
length += protocol.ByteCount(quicvarint.Len(uint64(len(f.Data))))
|
||||||
}
|
}
|
||||||
return length
|
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 {
|
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 */
|
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 {
|
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
|
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
|
// Length of a written frame
|
||||||
func (f *MaxDataFrame) Length(_ protocol.Version) protocol.ByteCount {
|
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
|
}, 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 = append(b, maxStreamDataFrameType)
|
||||||
b = quicvarint.Append(b, uint64(f.StreamID))
|
b = quicvarint.Append(b, uint64(f.StreamID))
|
||||||
b = quicvarint.Append(b, uint64(f.MaximumStreamData))
|
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
|
// Length of a written frame
|
||||||
func (f *MaxStreamDataFrame) Length(version protocol.Version) protocol.ByteCount {
|
func (f *MaxStreamDataFrame) Length(protocol.Version) protocol.ByteCount {
|
||||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.MaximumStreamData))
|
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
|
// Length of a written frame
|
||||||
func (f *MaxStreamsFrame) Length(protocol.Version) protocol.ByteCount {
|
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
|
// Length of a written frame
|
||||||
func (f *NewConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
|
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
|
// Length of a written frame
|
||||||
func (f *NewTokenFrame) Length(protocol.Version) protocol.ByteCount {
|
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
|
// Length of a written frame
|
||||||
func (f *ResetStreamFrame) Length(version protocol.Version) protocol.ByteCount {
|
func (f *ResetStreamFrame) Length(protocol.Version) protocol.ByteCount {
|
||||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.ErrorCode)) + quicvarint.Len(uint64(f.FinalSize))
|
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
|
// Length of a written frame
|
||||||
func (f *RetireConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
|
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
|
// Length of a written frame
|
||||||
func (f *StopSendingFrame) Length(_ protocol.Version) protocol.ByteCount {
|
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) {
|
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
|
// Length of a written frame
|
||||||
func (f *StreamDataBlockedFrame) Length(version protocol.Version) protocol.ByteCount {
|
func (f *StreamDataBlockedFrame) Length(protocol.Version) protocol.ByteCount {
|
||||||
return 1 + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.MaximumStreamData))
|
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
|
// 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))
|
length := 1 + quicvarint.Len(uint64(f.StreamID))
|
||||||
if f.Offset != 0 {
|
if f.Offset != 0 {
|
||||||
length += quicvarint.Len(uint64(f.Offset))
|
length += quicvarint.Len(uint64(f.Offset))
|
||||||
|
@ -116,7 +116,7 @@ func (f *StreamFrame) Length(version protocol.Version) protocol.ByteCount {
|
||||||
if f.DataLenPresent {
|
if f.DataLenPresent {
|
||||||
length += quicvarint.Len(uint64(f.DataLen()))
|
length += quicvarint.Len(uint64(f.DataLen()))
|
||||||
}
|
}
|
||||||
return length + f.DataLen()
|
return protocol.ByteCount(length) + f.DataLen()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataLen gives the length of data in bytes
|
// DataLen gives the length of data in bytes
|
||||||
|
@ -126,14 +126,14 @@ func (f *StreamFrame) DataLen() protocol.ByteCount {
|
||||||
|
|
||||||
// MaxDataLen returns the maximum data length
|
// MaxDataLen returns the maximum data length
|
||||||
// If 0 is returned, writing will fail (a STREAM frame must contain at least 1 byte of data).
|
// 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 {
|
func (f *StreamFrame) MaxDataLen(maxSize protocol.ByteCount, _ protocol.Version) protocol.ByteCount {
|
||||||
headerLen := 1 + quicvarint.Len(uint64(f.StreamID))
|
headerLen := 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID)))
|
||||||
if f.Offset != 0 {
|
if f.Offset != 0 {
|
||||||
headerLen += quicvarint.Len(uint64(f.Offset))
|
headerLen += protocol.ByteCount(quicvarint.Len(uint64(f.Offset)))
|
||||||
}
|
}
|
||||||
if f.DataLenPresent {
|
if f.DataLenPresent {
|
||||||
// pretend that the data size will be 1 bytes
|
// 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 afterwards
|
// If it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterward
|
||||||
headerLen++
|
headerLen++
|
||||||
}
|
}
|
||||||
if headerLen > maxSize {
|
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
|
// Length of a written frame
|
||||||
func (f *StreamsBlockedFrame) Length(_ protocol.Version) protocol.ByteCount {
|
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 @@ package quicvarint
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go/internal/protocol"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// taken from the QUIC draft
|
// 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.
|
// 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 {
|
if length != 1 && length != 2 && length != 4 && length != 8 {
|
||||||
panic("invalid varint length")
|
panic("invalid varint length")
|
||||||
}
|
}
|
||||||
|
@ -109,17 +107,17 @@ func AppendWithLen(b []byte, i uint64, length protocol.ByteCount) []byte {
|
||||||
} else if length == 8 {
|
} else if length == 8 {
|
||||||
b = append(b, 0b11000000)
|
b = append(b, 0b11000000)
|
||||||
}
|
}
|
||||||
for j := protocol.ByteCount(1); j < length-l; j++ {
|
for j := 1; j < length-l; j++ {
|
||||||
b = append(b, 0)
|
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))))
|
b = append(b, uint8(i>>(8*(l-1-j))))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len determines the number of bytes that will be needed to write the number i.
|
// 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 {
|
if i <= maxVarInt1 {
|
||||||
return 1
|
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
|
readPosInFrame int
|
||||||
currentFrameIsLast bool // is the currentFrame the last frame on this stream
|
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
|
closeForShutdownErr error
|
||||||
cancelReadErr error
|
|
||||||
resetRemotelyErr *StreamError
|
|
||||||
|
|
||||||
readChan chan struct{}
|
readChan chan struct{}
|
||||||
readOnce chan struct{} // cap: 1, to protect against concurrent use of Read
|
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 }()
|
defer func() { <-s.readOnce }()
|
||||||
|
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
completed, n, err := s.readImpl(p)
|
n, err := s.readImpl(p)
|
||||||
|
completed := s.isNewlyCompleted()
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if completed {
|
if completed {
|
||||||
|
@ -92,18 +97,38 @@ func (s *receiveStream) Read(p []byte) (int, error) {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, error) {
|
func (s *receiveStream) isNewlyCompleted() bool {
|
||||||
if s.finRead {
|
if s.completed {
|
||||||
return false, 0, io.EOF
|
return false
|
||||||
}
|
}
|
||||||
if s.cancelReadErr != nil {
|
// We need to know the final offset (either via FIN or RESET_STREAM) for flow control accounting.
|
||||||
return false, 0, s.cancelReadErr
|
if s.finalOffset == protocol.MaxByteCount {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if s.resetRemotelyErr != nil {
|
// We're done with the stream if it was cancelled locally...
|
||||||
return false, 0, s.resetRemotelyErr
|
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 {
|
if s.closeForShutdownErr != nil {
|
||||||
return false, 0, s.closeForShutdownErr
|
return 0, s.closeForShutdownErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytesRead int
|
var bytesRead int
|
||||||
|
@ -113,25 +138,23 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
|
||||||
s.dequeueNextFrame()
|
s.dequeueNextFrame()
|
||||||
}
|
}
|
||||||
if s.currentFrame == nil && bytesRead > 0 {
|
if s.currentFrame == nil && bytesRead > 0 {
|
||||||
return false, bytesRead, s.closeForShutdownErr
|
return bytesRead, s.closeForShutdownErr
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Stop waiting on errors
|
// Stop waiting on errors
|
||||||
if s.closeForShutdownErr != nil {
|
if s.closeForShutdownErr != nil {
|
||||||
return false, bytesRead, s.closeForShutdownErr
|
return bytesRead, s.closeForShutdownErr
|
||||||
}
|
}
|
||||||
if s.cancelReadErr != nil {
|
if s.cancelledRemotely || s.cancelledLocally {
|
||||||
return false, bytesRead, s.cancelReadErr
|
s.errorRead = true
|
||||||
}
|
return 0, s.cancelErr
|
||||||
if s.resetRemotelyErr != nil {
|
|
||||||
return false, bytesRead, s.resetRemotelyErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline := s.deadline
|
deadline := s.deadline
|
||||||
if !deadline.IsZero() {
|
if !deadline.IsZero() {
|
||||||
if !time.Now().Before(deadline) {
|
if !time.Now().Before(deadline) {
|
||||||
return false, bytesRead, errDeadline
|
return bytesRead, errDeadline
|
||||||
}
|
}
|
||||||
if deadlineTimer == nil {
|
if deadlineTimer == nil {
|
||||||
deadlineTimer = utils.NewTimer()
|
deadlineTimer = utils.NewTimer()
|
||||||
|
@ -161,10 +184,10 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytesRead > len(p) {
|
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) {
|
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:])
|
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
|
// when a RESET_STREAM was received, the flow controller was already
|
||||||
// informed about the final byteOffset for this stream
|
// informed about the final byteOffset for this stream
|
||||||
if s.resetRemotelyErr == nil {
|
if !s.cancelledRemotely {
|
||||||
s.flowController.AddBytesRead(protocol.ByteCount(m))
|
s.flowController.AddBytesRead(protocol.ByteCount(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.readPosInFrame >= len(s.currentFrame) && s.currentFrameIsLast {
|
if s.readPosInFrame >= len(s.currentFrame) && s.currentFrameIsLast {
|
||||||
s.finRead = true
|
|
||||||
s.currentFrame = nil
|
s.currentFrame = nil
|
||||||
if s.currentFrameDone != nil {
|
if s.currentFrameDone != nil {
|
||||||
s.currentFrameDone()
|
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() {
|
func (s *receiveStream) dequeueNextFrame() {
|
||||||
|
@ -202,7 +225,8 @@ func (s *receiveStream) dequeueNextFrame() {
|
||||||
|
|
||||||
func (s *receiveStream) CancelRead(errorCode StreamErrorCode) {
|
func (s *receiveStream) CancelRead(errorCode StreamErrorCode) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
completed := s.cancelReadImpl(errorCode)
|
s.cancelReadImpl(errorCode)
|
||||||
|
completed := s.isNewlyCompleted()
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if completed {
|
if completed {
|
||||||
|
@ -211,23 +235,26 @@ func (s *receiveStream) CancelRead(errorCode StreamErrorCode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *receiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) bool /* completed */ {
|
func (s *receiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) {
|
||||||
if s.finRead || s.cancelReadErr != nil || s.resetRemotelyErr != nil {
|
if s.cancelledLocally { // duplicate call to CancelRead
|
||||||
return false
|
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.signalRead()
|
||||||
s.sender.queueControlFrame(&wire.StopSendingFrame{
|
s.sender.queueControlFrame(&wire.StopSendingFrame{
|
||||||
StreamID: s.streamID,
|
StreamID: s.streamID,
|
||||||
ErrorCode: errorCode,
|
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 {
|
func (s *receiveStream) handleStreamFrame(frame *wire.StreamFrame) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
completed, err := s.handleStreamFrameImpl(frame)
|
err := s.handleStreamFrameImpl(frame)
|
||||||
|
completed := s.isNewlyCompleted()
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if completed {
|
if completed {
|
||||||
|
@ -237,59 +264,58 @@ func (s *receiveStream) handleStreamFrame(frame *wire.StreamFrame) error {
|
||||||
return err
|
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()
|
maxOffset := frame.Offset + frame.DataLen()
|
||||||
if err := s.flowController.UpdateHighestReceived(maxOffset, frame.Fin); err != nil {
|
if err := s.flowController.UpdateHighestReceived(maxOffset, frame.Fin); err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
var newlyRcvdFinalOffset bool
|
|
||||||
if frame.Fin {
|
if frame.Fin {
|
||||||
newlyRcvdFinalOffset = s.finalOffset == protocol.MaxByteCount
|
|
||||||
s.finalOffset = maxOffset
|
s.finalOffset = maxOffset
|
||||||
}
|
}
|
||||||
if s.cancelReadErr != nil {
|
if s.cancelledLocally {
|
||||||
return newlyRcvdFinalOffset, nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := s.frameQueue.Push(frame.Data, frame.Offset, frame.PutBack); err != nil {
|
if err := s.frameQueue.Push(frame.Data, frame.Offset, frame.PutBack); err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
s.signalRead()
|
s.signalRead()
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *receiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
|
func (s *receiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
completed, err := s.handleResetStreamFrameImpl(frame)
|
err := s.handleResetStreamFrameImpl(frame)
|
||||||
|
completed := s.isNewlyCompleted()
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if completed {
|
if completed {
|
||||||
s.flowController.Abandon()
|
|
||||||
s.sender.onStreamCompleted(s.streamID)
|
s.sender.onStreamCompleted(s.streamID)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *receiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame) (bool /*completed */, error) {
|
func (s *receiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame) error {
|
||||||
if s.closeForShutdownErr != nil {
|
if s.closeForShutdownErr != nil {
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := s.flowController.UpdateHighestReceived(frame.FinalSize, true); err != 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
|
s.finalOffset = frame.FinalSize
|
||||||
|
|
||||||
// ignore duplicate RESET_STREAM frames for this stream (after checking their final offset)
|
// ignore duplicate RESET_STREAM frames for this stream (after checking their final offset)
|
||||||
if s.resetRemotelyErr != nil {
|
if s.cancelledRemotely {
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
s.resetRemotelyErr = &StreamError{
|
s.flowController.Abandon()
|
||||||
StreamID: s.streamID,
|
// don't save the error if the RESET_STREAM frames was received after CancelRead was called
|
||||||
ErrorCode: frame.ErrorCode,
|
if s.cancelledLocally {
|
||||||
Remote: true,
|
return nil
|
||||||
}
|
}
|
||||||
|
s.cancelledRemotely = true
|
||||||
|
s.cancelErr = &StreamError{StreamID: s.streamID, ErrorCode: frame.ErrorCode, Remote: true}
|
||||||
s.signalRead()
|
s.signalRead()
|
||||||
return newlyRcvdFinalOffset, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *receiveStream) SetReadDeadline(t time.Time) error {
|
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
|
finishedWriting bool // set once Close() is called
|
||||||
finSent bool // set when a STREAM_FRAME with FIN bit has been sent
|
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
|
dataForWriting []byte // during a Write() call, this slice is the part of p that still needs to be sent out
|
||||||
nextFrame *wire.StreamFrame
|
nextFrame *wire.StreamFrame
|
||||||
|
@ -60,6 +64,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newSendStream(
|
func newSendStream(
|
||||||
|
ctx context.Context,
|
||||||
streamID protocol.StreamID,
|
streamID protocol.StreamID,
|
||||||
sender streamSender,
|
sender streamSender,
|
||||||
flowController flowcontrol.StreamFlowController,
|
flowController flowcontrol.StreamFlowController,
|
||||||
|
@ -71,7 +76,7 @@ func newSendStream(
|
||||||
writeChan: make(chan struct{}, 1),
|
writeChan: make(chan struct{}, 1),
|
||||||
writeOnce: make(chan struct{}, 1), // cap: 1, to protect against concurrent use of Write
|
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
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,23 +91,32 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||||
s.writeOnce <- struct{}{}
|
s.writeOnce <- struct{}{}
|
||||||
defer func() { <-s.writeOnce }()
|
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()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if s.finishedWriting {
|
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 {
|
if s.cancelWriteErr != nil {
|
||||||
return 0, s.cancelWriteErr
|
s.cancellationFlagged = true
|
||||||
|
return s.isNewlyCompleted(), 0, s.cancelWriteErr
|
||||||
}
|
}
|
||||||
if s.closeForShutdownErr != nil {
|
if s.closeForShutdownErr != nil {
|
||||||
return 0, s.closeForShutdownErr
|
return false, 0, s.closeForShutdownErr
|
||||||
}
|
}
|
||||||
if !s.deadline.IsZero() && !time.Now().Before(s.deadline) {
|
if !s.deadline.IsZero() && !time.Now().Before(s.deadline) {
|
||||||
return 0, errDeadline
|
return false, 0, errDeadline
|
||||||
}
|
}
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
return 0, nil
|
return false, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.dataForWriting = p
|
s.dataForWriting = p
|
||||||
|
@ -143,7 +157,7 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||||
if !deadline.IsZero() {
|
if !deadline.IsZero() {
|
||||||
if !time.Now().Before(deadline) {
|
if !time.Now().Before(deadline) {
|
||||||
s.dataForWriting = nil
|
s.dataForWriting = nil
|
||||||
return bytesWritten, errDeadline
|
return false, bytesWritten, errDeadline
|
||||||
}
|
}
|
||||||
if deadlineTimer == nil {
|
if deadlineTimer == nil {
|
||||||
deadlineTimer = utils.NewTimer()
|
deadlineTimer = utils.NewTimer()
|
||||||
|
@ -178,14 +192,15 @@ func (s *sendStream) Write(p []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytesWritten == len(p) {
|
if bytesWritten == len(p) {
|
||||||
return bytesWritten, nil
|
return false, bytesWritten, nil
|
||||||
}
|
}
|
||||||
if s.closeForShutdownErr != nil {
|
if s.closeForShutdownErr != nil {
|
||||||
return bytesWritten, s.closeForShutdownErr
|
return false, bytesWritten, s.closeForShutdownErr
|
||||||
} else if s.cancelWriteErr != nil {
|
} 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 {
|
func (s *sendStream) canBufferStreamFrame() bool {
|
||||||
|
@ -348,8 +363,24 @@ func (s *sendStream) getDataForWriting(f *wire.StreamFrame, maxBytes protocol.By
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sendStream) isNewlyCompleted() bool {
|
func (s *sendStream) isNewlyCompleted() bool {
|
||||||
completed := (s.finSent || s.cancelWriteErr != nil) && s.numOutstandingFrames == 0 && len(s.retransmissionQueue) == 0
|
if s.completed {
|
||||||
if completed && !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
|
s.completed = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -362,15 +393,23 @@ func (s *sendStream) Close() error {
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
return nil
|
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
|
s.finishedWriting = true
|
||||||
|
cancelWriteErr := s.cancelWriteErr
|
||||||
|
if cancelWriteErr != nil {
|
||||||
|
s.cancellationFlagged = true
|
||||||
|
}
|
||||||
|
completed := s.isNewlyCompleted()
|
||||||
s.mutex.Unlock()
|
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.sender.onHasStreamData(s.streamID) // need to send the FIN, must be called without holding the mutex
|
||||||
|
|
||||||
|
s.ctxCancel(nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +417,11 @@ func (s *sendStream) CancelWrite(errorCode StreamErrorCode) {
|
||||||
s.cancelWriteImpl(errorCode, false)
|
s.cancelWriteImpl(errorCode, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be called after locking the mutex
|
|
||||||
func (s *sendStream) cancelWriteImpl(errorCode qerr.StreamErrorCode, remote bool) {
|
func (s *sendStream) cancelWriteImpl(errorCode qerr.StreamErrorCode, remote bool) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
|
if !remote {
|
||||||
|
s.cancellationFlagged = true
|
||||||
|
}
|
||||||
if s.cancelWriteErr != nil {
|
if s.cancelWriteErr != nil {
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
return
|
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,
|
*handshake.TokenGenerator,
|
||||||
bool, /* client address validated by an address validation token */
|
bool, /* client address validated by an address validation token */
|
||||||
*logging.ConnectionTracer,
|
*logging.ConnectionTracer,
|
||||||
uint64,
|
ConnectionTracingID,
|
||||||
utils.Logger,
|
utils.Logger,
|
||||||
protocol.Version,
|
protocol.Version,
|
||||||
) quicConn
|
) 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
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -85,7 +86,9 @@ type stream struct {
|
||||||
var _ Stream = &stream{}
|
var _ Stream = &stream{}
|
||||||
|
|
||||||
// newStream creates a new Stream
|
// newStream creates a new Stream
|
||||||
func newStream(streamID protocol.StreamID,
|
func newStream(
|
||||||
|
ctx context.Context,
|
||||||
|
streamID protocol.StreamID,
|
||||||
sender streamSender,
|
sender streamSender,
|
||||||
flowController flowcontrol.StreamFlowController,
|
flowController flowcontrol.StreamFlowController,
|
||||||
) *stream {
|
) *stream {
|
||||||
|
@ -99,7 +102,7 @@ func newStream(streamID protocol.StreamID,
|
||||||
s.completedMutex.Unlock()
|
s.completedMutex.Unlock()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.sendStream = *newSendStream(streamID, senderForSendStream, flowController)
|
s.sendStream = *newSendStream(ctx, streamID, senderForSendStream, flowController)
|
||||||
senderForReceiveStream := &uniStreamSender{
|
senderForReceiveStream := &uniStreamSender{
|
||||||
streamSender: sender,
|
streamSender: sender,
|
||||||
onStreamCompletedImpl: func() {
|
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")
|
var errTooManyOpenStreams = errors.New("too many open streams")
|
||||||
|
|
||||||
type streamsMap struct {
|
type streamsMap struct {
|
||||||
|
ctx context.Context // not used for cancellations, but carries the values associated with the connection
|
||||||
perspective protocol.Perspective
|
perspective protocol.Perspective
|
||||||
|
|
||||||
maxIncomingBidiStreams uint64
|
maxIncomingBidiStreams uint64
|
||||||
|
@ -64,6 +65,7 @@ type streamsMap struct {
|
||||||
var _ streamManager = &streamsMap{}
|
var _ streamManager = &streamsMap{}
|
||||||
|
|
||||||
func newStreamsMap(
|
func newStreamsMap(
|
||||||
|
ctx context.Context,
|
||||||
sender streamSender,
|
sender streamSender,
|
||||||
newFlowController func(protocol.StreamID) flowcontrol.StreamFlowController,
|
newFlowController func(protocol.StreamID) flowcontrol.StreamFlowController,
|
||||||
maxIncomingBidiStreams uint64,
|
maxIncomingBidiStreams uint64,
|
||||||
|
@ -71,6 +73,7 @@ func newStreamsMap(
|
||||||
perspective protocol.Perspective,
|
perspective protocol.Perspective,
|
||||||
) streamManager {
|
) streamManager {
|
||||||
m := &streamsMap{
|
m := &streamsMap{
|
||||||
|
ctx: ctx,
|
||||||
perspective: perspective,
|
perspective: perspective,
|
||||||
newFlowController: newFlowController,
|
newFlowController: newFlowController,
|
||||||
maxIncomingBidiStreams: maxIncomingBidiStreams,
|
maxIncomingBidiStreams: maxIncomingBidiStreams,
|
||||||
|
@ -86,7 +89,7 @@ func (m *streamsMap) initMaps() {
|
||||||
protocol.StreamTypeBidi,
|
protocol.StreamTypeBidi,
|
||||||
func(num protocol.StreamNum) streamI {
|
func(num protocol.StreamNum) streamI {
|
||||||
id := num.StreamID(protocol.StreamTypeBidi, m.perspective)
|
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,
|
m.sender.queueControlFrame,
|
||||||
)
|
)
|
||||||
|
@ -94,7 +97,7 @@ func (m *streamsMap) initMaps() {
|
||||||
protocol.StreamTypeBidi,
|
protocol.StreamTypeBidi,
|
||||||
func(num protocol.StreamNum) streamI {
|
func(num protocol.StreamNum) streamI {
|
||||||
id := num.StreamID(protocol.StreamTypeBidi, m.perspective.Opposite())
|
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.maxIncomingBidiStreams,
|
||||||
m.sender.queueControlFrame,
|
m.sender.queueControlFrame,
|
||||||
|
@ -103,7 +106,7 @@ func (m *streamsMap) initMaps() {
|
||||||
protocol.StreamTypeUni,
|
protocol.StreamTypeUni,
|
||||||
func(num protocol.StreamNum) sendStreamI {
|
func(num protocol.StreamNum) sendStreamI {
|
||||||
id := num.StreamID(protocol.StreamTypeUni, m.perspective)
|
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,
|
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
|
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
|
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 ecnIPv4DataLen = 1
|
||||||
|
|
||||||
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
|
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 {
|
func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
|
||||||
var serr error
|
var serr error
|
||||||
if err := c.Control(func(fd uintptr) {
|
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
|
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).
|
// 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"))
|
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
|
||||||
if err == nil && disabled {
|
if err == nil && disabled {
|
||||||
return false
|
return false
|
||||||
|
@ -108,3 +117,40 @@ func isPermissionError(err error) bool {
|
||||||
}
|
}
|
||||||
return false
|
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
|
return size, serr
|
||||||
}
|
}
|
||||||
|
|
||||||
func isECNDisabled() bool {
|
func isECNDisabledUsingEnv() bool {
|
||||||
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
|
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
|
||||||
return err == nil && disabled
|
return err == nil && disabled
|
||||||
}
|
}
|
||||||
|
@ -147,8 +147,8 @@ func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) {
|
||||||
readPos: batchSize,
|
readPos: batchSize,
|
||||||
cap: connCapabilities{
|
cap: connCapabilities{
|
||||||
DF: supportsDF,
|
DF: supportsDF,
|
||||||
GSO: isGSOSupported(rawConn),
|
GSO: isGSOEnabled(rawConn),
|
||||||
ECN: !isECNDisabled(),
|
ECN: isECNEnabled(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i := 0; i < batchSize; i++ {
|
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 ecn != protocol.ECNUnsupported {
|
||||||
if !c.capabilities().ECN {
|
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, ok := addr.(*net.UDPAddr); ok {
|
||||||
if remoteUDPAddr.IP.To4() != nil {
|
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
|
# github.com/quic-go/qpack v0.4.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/quic-go/qpack
|
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
|
## explicit; go 1.21
|
||||||
github.com/quic-go/quic-go
|
github.com/quic-go/quic-go
|
||||||
github.com/quic-go/quic-go/http3
|
github.com/quic-go/quic-go/http3
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue