1
0
mirror of https://github.com/fumiama/terasu-cloudflared.git synced 2026-06-10 21:24:52 +08:00

TUN-8006: Update quic-go to latest upstream

This commit is contained in:
Chung-Ting
2023-12-04 09:49:00 +00:00
parent 45236a1f7d
commit 8068cdebb6
219 changed files with 10032 additions and 17038 deletions

View File

@@ -1,4 +1,6 @@
run:
skip-files:
- internal/handshake/cipher_suite.go
linters-settings:
depguard:
type: blacklist

View File

@@ -4,29 +4,210 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go)](https://pkg.go.dev/github.com/quic-go/quic-go)
[![Code Coverage](https://img.shields.io/codecov/c/github/quic-go/quic-go/master.svg?style=flat-square)](https://codecov.io/gh/quic-go/quic-go/)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go)
quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go, including the Unreliable Datagram Extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221)) and Datagram Packetization Layer Path MTU
Discovery (DPLPMTUD, [RFC 8899](https://datatracker.ietf.org/doc/html/rfc8899)). 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)).
In addition to the RFCs listed above, it currently implements the [IETF QUIC draft-29](https://tools.ietf.org/html/draft-ietf-quic-transport-29). Support for draft-29 will eventually be dropped, as it is phased out of the ecosystem.
In addition to these base RFCs, it also implements the following RFCs:
* Unreliable Datagram Extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221))
* Datagram Packetization Layer Path MTU Discovery (DPLPMTUD, [RFC 8899](https://datatracker.ietf.org/doc/html/rfc8899))
* QUIC Version 2 ([RFC 9369](https://datatracker.ietf.org/doc/html/rfc9369))
* QUIC Event Logging using qlog ([draft-ietf-quic-qlog-main-schema](https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-main-schema/) and [draft-ietf-quic-qlog-quic-events](https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-quic-events/))
## Guides
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).
*We currently support Go 1.19.x and Go 1.20.x*
## Using QUIC
Running tests:
### Running a Server
go test ./...
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.
### QUIC without HTTP/3
```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
}
}()
```
Take a look at [this echo example](example/echo/echo.go).
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`).
## Usage
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 is activated by setting a `Tracer` callback on the `Config`. It is called as soon as quic-go decides to starts the QUIC handshake on a new connection.
A useful implementation of this callback could look like this:
```go
quic.Config{
Tracer: func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
role := "server"
if p == logging.PerspectiveClient {
role = "client"
}
filename := fmt.Sprintf("./log_%s_%s.qlog", connID, role)
f, err := os.Create(filename)
// handle the error
return qlog.NewConnectionTracer(f, p, connID)
}
}
```
This implementation of the callback creates a new qlog file in the current directory named `log_<client / server>_<QUIC connection ID>.qlog`.
## Using HTTP/3
### As a server
See the [example server](example/main.go). Starting a QUIC server is very similar to the standard lib http in go:
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)))
@@ -46,12 +227,13 @@ http.Client{
## Projects using quic-go
| Project | Description | Stars |
|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) | Free and open source, powerful network-wide ads & trackers blocking DNS server. | ![GitHub Repo stars](https://img.shields.io/github/stars/AdguardTeam/AdGuardHome?style=flat-square) |
| [algernon](https://github.com/xyproto/algernon) | Small self-contained pure-Go web server with Lua, Markdown, HTTP/2, QUIC, Redis and PostgreSQL support | ![GitHub Repo stars](https://img.shields.io/github/stars/xyproto/algernon?style=flat-square) |
| [caddy](https://github.com/caddyserver/caddy/) | Fast, multi-platform web server with automatic HTTPS | ![GitHub Repo stars](https://img.shields.io/github/stars/caddyserver/caddy?style=flat-square) |
| [cloudflared](https://github.com/cloudflare/cloudflared) | A tunneling daemon that proxies traffic from the Cloudflare network to your origins | ![GitHub Repo stars](https://img.shields.io/github/stars/cloudflare/cloudflared?style=flat-square) |
| [go-libp2p](https://github.com/libp2p/go-libp2p) | libp2p implementation in Go, powering [Kubo](https://github.com/ipfs/kubo) (IPFS) and [Lotus](https://github.com/filecoin-project/lotus) (Filecoin), among others | ![GitHub Repo stars](https://img.shields.io/github/stars/libp2p/go-libp2p?style=flat-square) |
| [Hysteria](https://github.com/apernet/hysteria) | A powerful, lightning fast and censorship resistant proxy | ![GitHub Repo stars](https://img.shields.io/github/stars/apernet/hysteria?style=flat-square) |
| [Mercure](https://github.com/dunglas/mercure) | An open, easy, fast, reliable and battery-efficient solution for real-time communications | ![GitHub Repo stars](https://img.shields.io/github/stars/dunglas/mercure?style=flat-square) |
| [OONI Probe](https://github.com/ooni/probe-cli) | Next generation OONI Probe. Library and CLI tool. | ![GitHub Repo stars](https://img.shields.io/github/stars/ooni/probe-cli?style=flat-square) |
| [syncthing](https://github.com/syncthing/syncthing/) | Open Source Continuous File Synchronization | ![GitHub Repo stars](https://img.shields.io/github/stars/syncthing/syncthing?style=flat-square) |
@@ -59,6 +241,17 @@ http.Client{
| [v2ray-core](https://github.com/v2fly/v2ray-core) | A platform for building proxies to bypass network restrictions | ![GitHub Repo stars](https://img.shields.io/github/stars/v2fly/v2ray-core?style=flat-square) |
| [YoMo](https://github.com/yomorun/yomo) | Streaming Serverless Framework for Geo-distributed System | ![GitHub Repo stars](https://img.shields.io/github/stars/yomorun/yomo?style=flat-square) |
If you'd like to see your project added to this list, please send us a PR.
## Release Policy
quic-go always aims to support the latest two Go releases.
### Dependency on forked crypto/tls
Since the standard library didn't provide any QUIC APIs before the Go 1.21 release, we had to fork crypto/tls to add the required APIs ourselves: [qtls for Go 1.20](https://github.com/quic-go/qtls-go1-20).
This had led to a lot of pain in the Go ecosystem, and we're happy that we can rely on Go 1.21 going forward.
## Contributing
We are always happy to welcome new contributors! We have a number of self-contained issues that are suitable for first-time contributors, they are tagged with [help wanted](https://github.com/quic-go/quic-go/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). If you have any questions, please feel free to reach out by opening an issue or leaving a comment.

View File

@@ -51,18 +51,22 @@ func (b *packetBuffer) Release() {
}
// Len returns the length of Data
func (b *packetBuffer) Len() protocol.ByteCount {
return protocol.ByteCount(len(b.Data))
}
func (b *packetBuffer) Len() protocol.ByteCount { return protocol.ByteCount(len(b.Data)) }
func (b *packetBuffer) Cap() protocol.ByteCount { return protocol.ByteCount(cap(b.Data)) }
func (b *packetBuffer) putBack() {
if cap(b.Data) != int(protocol.MaxPacketBufferSize) {
panic("putPacketBuffer called with packet of wrong size!")
if cap(b.Data) == protocol.MaxPacketBufferSize {
bufferPool.Put(b)
return
}
bufferPool.Put(b)
if cap(b.Data) == protocol.MaxLargePacketBufferSize {
largeBufferPool.Put(b)
return
}
panic("putPacketBuffer called with packet of wrong size!")
}
var bufferPool sync.Pool
var bufferPool, largeBufferPool sync.Pool
func getPacketBuffer() *packetBuffer {
buf := bufferPool.Get().(*packetBuffer)
@@ -71,10 +75,18 @@ func getPacketBuffer() *packetBuffer {
return buf
}
func getLargePacketBuffer() *packetBuffer {
buf := largeBufferPool.Get().(*packetBuffer)
buf.refCount = 1
buf.Data = buf.Data[:0]
return buf
}
func init() {
bufferPool.New = func() interface{} {
return &packetBuffer{
Data: make([]byte, 0, protocol.MaxPacketBufferSize),
}
bufferPool.New = func() any {
return &packetBuffer{Data: make([]byte, 0, protocol.MaxPacketBufferSize)}
}
largeBufferPool.New = func() any {
return &packetBuffer{Data: make([]byte, 0, protocol.MaxLargePacketBufferSize)}
}
}

View File

@@ -34,7 +34,7 @@ type client struct {
conn quicConn
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
tracingID uint64
logger utils.Logger
}
@@ -43,7 +43,9 @@ type client struct {
var generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
// DialAddr establishes a new QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC connection is closed.
// It resolves the address, and then creates a new UDP connection to dial the QUIC server.
// When the QUIC connection is closed, this UDP connection is closed.
// See Dial for more details.
func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (Connection, error) {
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
@@ -53,15 +55,15 @@ func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Confi
if err != nil {
return nil, err
}
dl, err := setupTransport(udpConn, tlsConf, true)
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
return dl.Dial(ctx, udpAddr, tlsConf, conf)
return tr.dial(ctx, udpAddr, addr, tlsConf, conf, false)
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC connection is closed.
// See DialAddr for more details.
func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) {
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
@@ -71,20 +73,20 @@ func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *
if err != nil {
return nil, err
}
dl, err := setupTransport(udpConn, tlsConf, true)
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
conn, err := dl.DialEarly(ctx, udpAddr, tlsConf, conf)
conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, true)
if err != nil {
dl.Close()
tr.Close()
return nil, err
}
return conn, nil
}
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn using the provided context.
// See DialEarly for details.
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
// See Dial for more details.
func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) {
dl, err := setupTransport(c, tlsConf, false)
if err != nil {
@@ -98,12 +100,15 @@ func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tl
return conn, nil
}
// Dial establishes a new QUIC connection to a server using a net.PacketConn. If
// the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn
// does), ECN and packet info support will be enabled. In this case, ReadMsgUDP
// and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write
// packets.
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
// If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does),
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
// will be used instead of ReadFrom and WriteTo to read/write packets.
// The tls.Config must define an application protocol (using NextProtos).
//
// This is a convenience function. More advanced use cases should instantiate a Transport,
// which offers configuration options for a more fine-grained control of the connection establishment,
// including reusing the underlying UDP socket for multiple QUIC connections.
func Dial(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error) {
dl, err := setupTransport(c, tlsConf, false)
if err != nil {
@@ -148,7 +153,7 @@ func dial(
if c.config.Tracer != nil {
c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
}
if c.tracer != nil {
if c.tracer != nil && c.tracer.StartedConnection != nil {
c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
}
if err := c.dial(ctx); err != nil {
@@ -158,12 +163,6 @@ func dial(
}
func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
if tlsConf == nil {
tlsConf = &tls.Config{}
} else {
tlsConf = tlsConf.Clone()
}
srcConnID, err := connIDGenerator.GenerateConnectionID()
if err != nil {
return nil, err
@@ -234,7 +233,7 @@ func (c *client) dial(ctx context.Context) error {
select {
case <-ctx.Done():
c.conn.shutdown()
return ctx.Err()
return context.Cause(ctx)
case err := <-errorChan:
return err
case recreateErr := <-recreateChan:

View File

@@ -16,13 +16,13 @@ type closedLocalConn struct {
perspective protocol.Perspective
logger utils.Logger
sendPacket func(net.Addr, *packetInfo)
sendPacket func(net.Addr, packetInfo)
}
var _ packetHandler = &closedLocalConn{}
// newClosedLocalConn creates a new closedLocalConn and runs it.
func newClosedLocalConn(sendPacket func(net.Addr, *packetInfo), pers protocol.Perspective, logger utils.Logger) packetHandler {
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), pers protocol.Perspective, logger utils.Logger) packetHandler {
return &closedLocalConn{
sendPacket: sendPacket,
perspective: pers,
@@ -30,7 +30,7 @@ func newClosedLocalConn(sendPacket func(net.Addr, *packetInfo), pers protocol.Pe
}
}
func (c *closedLocalConn) handlePacket(p *receivedPacket) {
func (c *closedLocalConn) handlePacket(p receivedPacket) {
c.counter++
// exponential backoff
// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
@@ -58,7 +58,7 @@ func newClosedRemoteConn(pers protocol.Perspective) packetHandler {
return &closedRemoteConn{perspective: pers}
}
func (s *closedRemoteConn) handlePacket(*receivedPacket) {}
func (s *closedRemoteConn) handlePacket(receivedPacket) {}
func (s *closedRemoteConn) shutdown() {}
func (s *closedRemoteConn) destroy(error) {}
func (s *closedRemoteConn) getPerspective() protocol.Perspective { return s.perspective }

View File

@@ -1,18 +1,10 @@
coverage:
round: nearest
ignore:
- streams_map_incoming_bidi.go
- streams_map_incoming_uni.go
- streams_map_outgoing_bidi.go
- streams_map_outgoing_uni.go
- http3/gzip_reader.go
- interop/
- internal/ackhandler/packet_linkedlist.go
- internal/utils/byteinterval_linkedlist.go
- internal/utils/newconnectionid_linkedlist.go
- internal/utils/packetinterval_linkedlist.go
- internal/handshake/cipher_suite.go
- internal/utils/linkedlist/linkedlist.go
- logging/null_tracer.go
- fuzzing/
- metrics/
status:

View File

@@ -1,13 +1,12 @@
package quic
import (
"errors"
"fmt"
"net"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/quicvarint"
)
// Clone clones a Config
@@ -17,18 +16,29 @@ func (c *Config) Clone() *Config {
}
func (c *Config) handshakeTimeout() time.Duration {
return utils.Max(protocol.DefaultHandshakeTimeout, 2*c.HandshakeIdleTimeout)
return 2 * c.HandshakeIdleTimeout
}
func (c *Config) maxRetryTokenAge() time.Duration {
return c.handshakeTimeout()
}
func validateConfig(config *Config) error {
if config == nil {
return nil
}
if config.MaxIncomingStreams > 1<<60 {
return errors.New("invalid value for Config.MaxIncomingStreams")
const maxStreams = 1 << 60
if config.MaxIncomingStreams > maxStreams {
config.MaxIncomingStreams = maxStreams
}
if config.MaxIncomingUniStreams > 1<<60 {
return errors.New("invalid value for Config.MaxIncomingUniStreams")
if config.MaxIncomingUniStreams > maxStreams {
config.MaxIncomingUniStreams = maxStreams
}
if config.MaxStreamReceiveWindow > quicvarint.Max {
config.MaxStreamReceiveWindow = quicvarint.Max
}
if config.MaxConnectionReceiveWindow > quicvarint.Max {
config.MaxConnectionReceiveWindow = quicvarint.Max
}
// check that all QUIC versions are actually supported
for _, v := range config.Versions {
@@ -43,12 +53,6 @@ func validateConfig(config *Config) error {
// it may be called with nil
func populateServerConfig(config *Config) *Config {
config = populateConfig(config)
if config.MaxTokenAge == 0 {
config.MaxTokenAge = protocol.TokenValidity
}
if config.MaxRetryTokenAge == 0 {
config.MaxRetryTokenAge = protocol.RetryTokenValidity
}
if config.RequireAddressValidation == nil {
config.RequireAddressValidation = func(net.Addr) bool { return false }
}
@@ -101,33 +105,25 @@ func populateConfig(config *Config) *Config {
} else if maxIncomingUniStreams < 0 {
maxIncomingUniStreams = 0
}
maxDatagrameFrameSize := config.MaxDatagramFrameSize
if maxDatagrameFrameSize == 0 {
maxDatagrameFrameSize = int64(protocol.DefaultMaxDatagramFrameSize)
}
return &Config{
GetConfigForClient: config.GetConfigForClient,
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
MaxTokenAge: config.MaxTokenAge,
MaxRetryTokenAge: config.MaxRetryTokenAge,
RequireAddressValidation: config.RequireAddressValidation,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
MaxDatagramFrameSize: maxDatagrameFrameSize,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
Allow0RTT: config.Allow0RTT,
Tracer: config.Tracer,
GetConfigForClient: config.GetConfigForClient,
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
RequireAddressValidation: config.RequireAddressValidation,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
Allow0RTT: config.Allow0RTT,
Tracer: config.Tracer,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -71,17 +71,9 @@ func (s *cryptoStreamImpl) HandleCryptoFrame(f *wire.CryptoFrame) error {
// GetCryptoData retrieves data that was received in CRYPTO frames
func (s *cryptoStreamImpl) GetCryptoData() []byte {
if len(s.msgBuf) < 4 {
return nil
}
msgLen := 4 + int(s.msgBuf[1])<<16 + int(s.msgBuf[2])<<8 + int(s.msgBuf[3])
if len(s.msgBuf) < msgLen {
return nil
}
msg := make([]byte, msgLen)
copy(msg, s.msgBuf[:msgLen])
s.msgBuf = s.msgBuf[msgLen:]
return msg
b := s.msgBuf
s.msgBuf = nil
return b
}
func (s *cryptoStreamImpl) Finish() error {

View File

@@ -3,12 +3,14 @@ package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
type cryptoDataHandler interface {
HandleMessage([]byte, protocol.EncryptionLevel) bool
HandleMessage([]byte, protocol.EncryptionLevel) error
NextEvent() handshake.Event
}
type cryptoStreamManager struct {
@@ -33,7 +35,7 @@ func newCryptoStreamManager(
}
}
func (m *cryptoStreamManager) HandleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) (bool /* encryption level changed */, error) {
func (m *cryptoStreamManager) HandleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) error {
var str cryptoStream
//nolint:exhaustive // CRYPTO frames cannot be sent in 0-RTT packets.
switch encLevel {
@@ -44,18 +46,37 @@ func (m *cryptoStreamManager) HandleCryptoFrame(frame *wire.CryptoFrame, encLeve
case protocol.Encryption1RTT:
str = m.oneRTTStream
default:
return false, fmt.Errorf("received CRYPTO frame with unexpected encryption level: %s", encLevel)
return fmt.Errorf("received CRYPTO frame with unexpected encryption level: %s", encLevel)
}
if err := str.HandleCryptoFrame(frame); err != nil {
return false, err
return err
}
for {
data := str.GetCryptoData()
if data == nil {
return false, nil
return nil
}
if encLevelFinished := m.cryptoHandler.HandleMessage(data, encLevel); encLevelFinished {
return true, str.Finish()
if err := m.cryptoHandler.HandleMessage(data, encLevel); err != nil {
return err
}
}
}
func (m *cryptoStreamManager) GetPostHandshakeData(maxSize protocol.ByteCount) *wire.CryptoFrame {
if !m.oneRTTStream.HasData() {
return nil
}
return m.oneRTTStream.PopCryptoFrame(maxSize)
}
func (m *cryptoStreamManager) Drop(encLevel protocol.EncryptionLevel) error {
//nolint:exhaustive // 1-RTT keys should never get dropped.
switch encLevel {
case protocol.EncryptionInitial:
return m.initialStream.Finish()
case protocol.EncryptionHandshake:
return m.handshakeStream.Finish()
default:
panic(fmt.Sprintf("dropped unexpected encryption level: %s", encLevel))
}
}

View File

@@ -1,6 +1,7 @@
package quic
import (
"context"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
@@ -98,7 +99,7 @@ func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
}
// Receive gets a received DATAGRAM frame.
func (h *datagramQueue) Receive() ([]byte, error) {
func (h *datagramQueue) Receive(ctx context.Context) ([]byte, error) {
for {
h.rcvMx.Lock()
if len(h.rcvQueue) > 0 {
@@ -113,6 +114,8 @@ func (h *datagramQueue) Receive() ([]byte, error) {
continue
case <-h.closed:
return nil, h.closeErr
case <-ctx.Done():
return nil, ctx.Err()
}
}
}

View File

@@ -61,3 +61,15 @@ func (e *StreamError) Error() string {
}
return fmt.Sprintf("stream %d canceled by %s with error code %d", e.StreamID, pers, e.ErrorCode)
}
// DatagramTooLargeError is returned from Connection.SendDatagram if the payload is too large to be sent.
type DatagramTooLargeError struct {
PeerMaxDatagramFrameSize int64
}
func (e *DatagramTooLargeError) Is(target error) bool {
_, ok := target.(*DatagramTooLargeError)
return ok
}
func (e *DatagramTooLargeError) Error() string { return "DATAGRAM frame too large" }

View File

@@ -6,6 +6,7 @@ import (
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils/ringbuffer"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/quicvarint"
)
@@ -14,10 +15,10 @@ type framer interface {
HasData() bool
QueueControlFrame(wire.Frame)
AppendControlFrames([]*ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount)
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount)
AddActiveStream(protocol.StreamID)
AppendStreamFrames([]*ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount)
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount)
Handle0RTTRejection() error
}
@@ -28,7 +29,7 @@ type framerI struct {
streamGetter streamGetter
activeStreams map[protocol.StreamID]struct{}
streamQueue []protocol.StreamID
streamQueue ringbuffer.RingBuffer[protocol.StreamID]
controlFrameMutex sync.Mutex
controlFrames []wire.Frame
@@ -45,7 +46,7 @@ func newFramer(streamGetter streamGetter) framer {
func (f *framerI) HasData() bool {
f.mutex.Lock()
hasData := len(f.streamQueue) > 0
hasData := !f.streamQueue.Empty()
f.mutex.Unlock()
if hasData {
return true
@@ -62,7 +63,7 @@ func (f *framerI) QueueControlFrame(frame wire.Frame) {
f.controlFrameMutex.Unlock()
}
func (f *framerI) AppendControlFrames(frames []*ackhandler.Frame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount) {
func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount) {
var length protocol.ByteCount
f.controlFrameMutex.Lock()
for len(f.controlFrames) > 0 {
@@ -71,9 +72,7 @@ func (f *framerI) AppendControlFrames(frames []*ackhandler.Frame, maxLen protoco
if length+frameLen > maxLen {
break
}
af := ackhandler.GetFrame()
af.Frame = frame
frames = append(frames, af)
frames = append(frames, ackhandler.Frame{Frame: frame})
length += frameLen
f.controlFrames = f.controlFrames[:len(f.controlFrames)-1]
}
@@ -84,24 +83,23 @@ func (f *framerI) AppendControlFrames(frames []*ackhandler.Frame, maxLen protoco
func (f *framerI) AddActiveStream(id protocol.StreamID) {
f.mutex.Lock()
if _, ok := f.activeStreams[id]; !ok {
f.streamQueue = append(f.streamQueue, id)
f.streamQueue.PushBack(id)
f.activeStreams[id] = struct{}{}
}
f.mutex.Unlock()
}
func (f *framerI) AppendStreamFrames(frames []*ackhandler.Frame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount) {
func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount) {
startLen := len(frames)
var length protocol.ByteCount
var lastFrame *ackhandler.Frame
f.mutex.Lock()
// pop STREAM frames, until less than MinStreamFrameSize bytes are left in the packet
numActiveStreams := len(f.streamQueue)
numActiveStreams := f.streamQueue.Len()
for i := 0; i < numActiveStreams; i++ {
if protocol.MinStreamFrameSize+length > maxLen {
break
}
id := f.streamQueue[0]
f.streamQueue = f.streamQueue[1:]
id := f.streamQueue.PopFront()
// This should never return an error. Better check it anyway.
// The stream will only be in the streamQueue, if it enqueued itself there.
str, err := f.streamGetter.GetOrOpenSendStream(id)
@@ -115,28 +113,27 @@ func (f *framerI) AppendStreamFrames(frames []*ackhandler.Frame, maxLen protocol
// Therefore, we can pretend to have more bytes available when popping
// the STREAM frame (which will always have the DataLen set).
remainingLen += quicvarint.Len(uint64(remainingLen))
frame, hasMoreData := str.popStreamFrame(remainingLen, v)
frame, ok, hasMoreData := str.popStreamFrame(remainingLen, v)
if hasMoreData { // put the stream back in the queue (at the end)
f.streamQueue = append(f.streamQueue, id)
} else { // no more data to send. Stream is not active any more
f.streamQueue.PushBack(id)
} else { // no more data to send. Stream is not active
delete(f.activeStreams, id)
}
// The frame can be nil
// The frame can be "nil"
// * if the receiveStream was canceled after it said it had data
// * the remaining size doesn't allow us to add another STREAM frame
if frame == nil {
if !ok {
continue
}
frames = append(frames, frame)
length += frame.Length(v)
lastFrame = frame
length += frame.Frame.Length(v)
}
f.mutex.Unlock()
if lastFrame != nil {
lastFrameLen := lastFrame.Length(v)
if len(frames) > startLen {
l := frames[len(frames)-1].Frame.Length(v)
// account for the smaller size of the last STREAM frame
lastFrame.Frame.(*wire.StreamFrame).DataLenPresent = false
length += lastFrame.Length(v) - lastFrameLen
frames[len(frames)-1].Frame.DataLenPresent = false
length += frames[len(frames)-1].Frame.Length(v) - l
}
return frames, length
}
@@ -146,7 +143,7 @@ func (f *framerI) Handle0RTTRejection() error {
defer f.mutex.Unlock()
f.controlFrameMutex.Lock()
f.streamQueue = f.streamQueue[:0]
f.streamQueue.Clear()
for id := range f.activeStreams {
delete(f.activeStreams, id)
}

View File

@@ -2,6 +2,7 @@ package quic
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
@@ -19,10 +20,9 @@ type StreamID = protocol.StreamID
type VersionNumber = protocol.VersionNumber
const (
// VersionDraft29 is IETF QUIC draft-29
VersionDraft29 = protocol.VersionDraft29
// Version1 is RFC 9000
Version1 = protocol.Version1
// Version2 is RFC 9369
Version2 = protocol.Version2
)
@@ -122,6 +122,8 @@ type SendStream interface {
// 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
// cancels the read-side of their stream.
// The cancellation cause is set to the error that caused the stream to
// close, or `context.Canceled` in case the stream is closed without error.
Context() context.Context
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
@@ -178,15 +180,17 @@ type Connection interface {
// The error string will be sent to the peer.
CloseWithError(ApplicationErrorCode, string) error
// Context returns a context that is cancelled when the connection is closed.
// The cancellation cause is set to the error that caused the connection to
// close, or `context.Canceled` in case the listener is closed first.
Context() context.Context
// ConnectionState returns basic details about the QUIC connection.
// Warning: This API should not be considered stable and might change soon.
ConnectionState() ConnectionState
// SendMessage sends a message as a datagram, as specified in RFC 9221.
SendMessage([]byte) error
// ReceiveMessage gets a message received in a datagram, as specified in RFC 9221.
ReceiveMessage() ([]byte, error)
// SendDatagram sends a message as a datagram, as specified in RFC 9221.
SendDatagram([]byte) error
// ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221.
ReceiveDatagram(context.Context) ([]byte, error)
}
// An EarlyConnection is a connection that is handshaking.
@@ -198,7 +202,7 @@ type EarlyConnection interface {
// HandshakeComplete blocks until the handshake completes (or fails).
// For the client, data sent before completion of the handshake is encrypted with 0-RTT keys.
// For the serfer, data sent before completion of the handshake is encrypted with 1-RTT keys,
// For the server, data sent before completion of the handshake is encrypted with 1-RTT keys,
// however the client's identity is only verified once the handshake completes.
HandshakeComplete() <-chan struct{}
@@ -208,6 +212,9 @@ type EarlyConnection interface {
// StatelessResetKey is a key used to derive stateless reset tokens.
type StatelessResetKey [32]byte
// TokenGeneratorKey is a key used to encrypt session resumption tokens.
type TokenGeneratorKey = handshake.TokenProtectorKey
// A ConnectionID is a QUIC Connection ID, as defined in RFC 9000.
// It is not able to handle QUIC Connection IDs longer than 20 bytes,
// as they are allowed by RFC 8999.
@@ -246,7 +253,8 @@ type Config struct {
// If not set, it uses all versions available.
Versions []VersionNumber
// HandshakeIdleTimeout is the idle timeout before completion of the handshake.
// Specifically, if we don't receive any packet from the peer within this time, the connection attempt is aborted.
// If we don't receive any packet from the peer within this time, the connection attempt is aborted.
// Additionally, if the handshake doesn't complete in twice this time, the connection attempt is also aborted.
// If this value is zero, the timeout is set to 5 seconds.
HandshakeIdleTimeout time.Duration
// MaxIdleTimeout is the maximum duration that may pass without any incoming network activity.
@@ -260,13 +268,6 @@ type Config struct {
// See https://datatracker.ietf.org/doc/html/rfc9000#section-8 for details.
// If not set, every client is forced to prove its remote address.
RequireAddressValidation func(net.Addr) bool
// MaxRetryTokenAge is the maximum age of a Retry token.
// If not set, it defaults to 5 seconds. Only valid for a server.
MaxRetryTokenAge time.Duration
// MaxTokenAge is the maximum age of the token presented during the handshake,
// for tokens that were issued on a previous connection.
// If not set, it defaults to 24 hours. Only valid for a server.
MaxTokenAge time.Duration
// The TokenStore stores tokens received from the server.
// Tokens are used to skip address validation on future connection attempts.
// The key used to store tokens is the ServerName from the tls.Config, if set
@@ -276,17 +277,21 @@ type Config struct {
// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
// will increase the window up to MaxStreamReceiveWindow.
// If this value is zero, it will default to 512 KB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
InitialStreamReceiveWindow uint64
// MaxStreamReceiveWindow is the maximum stream-level flow control window for receiving data.
// If this value is zero, it will default to 6 MB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
MaxStreamReceiveWindow uint64
// InitialConnectionReceiveWindow is the initial size of the stream-level flow control window for receiving data.
// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
// will increase the window up to MaxConnectionReceiveWindow.
// If this value is zero, it will default to 512 KB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
InitialConnectionReceiveWindow uint64
// MaxConnectionReceiveWindow is the connection-level flow control window for receiving data.
// If this value is zero, it will default to 15 MB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
MaxConnectionReceiveWindow uint64
// AllowConnectionWindowIncrease is called every time the connection flow controller attempts
// to increase the connection flow control window.
@@ -296,35 +301,30 @@ type Config struct {
// in this callback.
AllowConnectionWindowIncrease func(conn Connection, delta uint64) bool
// MaxIncomingStreams is the maximum number of concurrent bidirectional streams that a peer is allowed to open.
// Values above 2^60 are invalid.
// If not set, it will default to 100.
// If set to a negative value, it doesn't allow any bidirectional streams.
// Values larger than 2^60 will be clipped to that value.
MaxIncomingStreams int64
// MaxIncomingUniStreams is the maximum number of concurrent unidirectional streams that a peer is allowed to open.
// Values above 2^60 are invalid.
// If not set, it will default to 100.
// If set to a negative value, it doesn't allow any unidirectional streams.
// Values larger than 2^60 will be clipped to that value.
MaxIncomingUniStreams int64
// KeepAlivePeriod defines whether this peer will periodically send a packet to keep the connection alive.
// If set to 0, then no keep alive is sent. Otherwise, the keep alive is sent on that period (or at most
// every half of MaxIdleTimeout, whichever is smaller).
KeepAlivePeriod time.Duration
// DisablePathMTUDiscovery disables Path MTU Discovery (RFC 8899).
// Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
// Note that if Path MTU discovery is causing issues on your system, please open a new issue
// This allows the sending of QUIC packets that fully utilize the available MTU of the path.
// Path MTU discovery is only available on systems that allow setting of the Don't Fragment (DF) bit.
// If unavailable or disabled, packets will be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
DisablePathMTUDiscovery bool
// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
// This can be useful if version information is exchanged out-of-band.
// It has no effect for a client.
DisableVersionNegotiationPackets bool
// Allow0RTT allows the application to decide if a 0-RTT connection attempt should be accepted.
// Only valid for the server.
Allow0RTT bool
// Enable QUIC datagram support (RFC 9221).
EnableDatagrams bool
// Maximum size of QUIC datagram frames (RFC 9221).
MaxDatagramFrameSize int64
Tracer func(context.Context, logging.Perspective, ConnectionID) logging.ConnectionTracer
Tracer func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer
}
type ClientHelloInfo struct {
@@ -333,7 +333,17 @@ type ClientHelloInfo struct {
// ConnectionState records basic details about a QUIC connection
type ConnectionState struct {
TLS handshake.ConnectionState
// TLS contains information about the TLS connection state, incl. the tls.ConnectionState.
TLS tls.ConnectionState
// SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated.
// This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams).
// If datagram support was negotiated, datagrams can be sent and received using the
// SendDatagram and ReceiveDatagram methods on the Connection.
SupportsDatagrams bool
Version VersionNumber
// Used0RTT says if 0-RTT resumption was used.
Used0RTT bool
// Version is the QUIC version of the QUIC connection.
Version VersionNumber
// GSO says if generic segmentation offload is used
GSO bool
}

View File

@@ -10,7 +10,7 @@ func IsFrameAckEliciting(f wire.Frame) bool {
}
// HasAckElicitingFrames returns true if at least one frame is ack-eliciting.
func HasAckElicitingFrames(fs []*Frame) bool {
func HasAckElicitingFrames(fs []Frame) bool {
for _, f := range fs {
if IsFrameAckEliciting(f.Frame) {
return true

View File

@@ -14,10 +14,11 @@ func NewAckHandler(
initialMaxDatagramSize protocol.ByteCount,
rttStats *utils.RTTStats,
clientAddressValidated bool,
enableECN bool,
pers protocol.Perspective,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
logger utils.Logger,
) (SentPacketHandler, ReceivedPacketHandler) {
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, clientAddressValidated, pers, tracer, logger)
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, clientAddressValidated, enableECN, pers, tracer, logger)
return sph, newReceivedPacketHandler(sph, rttStats, logger)
}

View File

@@ -0,0 +1,296 @@
package ackhandler
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/logging"
)
type ecnState uint8
const (
ecnStateInitial ecnState = iota
ecnStateTesting
ecnStateUnknown
ecnStateCapable
ecnStateFailed
)
// must fit into an uint8, otherwise numSentTesting and numLostTesting must have a larger type
const numECNTestingPackets = 10
type ecnHandler interface {
SentPacket(protocol.PacketNumber, protocol.ECN)
Mode() protocol.ECN
HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool)
LostPacket(protocol.PacketNumber)
}
// The ecnTracker performs ECN validation of a path.
// Once failed, it doesn't do any re-validation of the path.
// It is designed only work for 1-RTT packets, it doesn't handle multiple packet number spaces.
// In order to avoid revealing any internal state to on-path observers,
// callers should make sure to start using ECN (i.e. calling Mode) for the very first 1-RTT packet sent.
// The validation logic implemented here strictly follows the algorithm described in RFC 9000 section 13.4.2 and A.4.
type ecnTracker struct {
state ecnState
numSentTesting, numLostTesting uint8
firstTestingPacket protocol.PacketNumber
lastTestingPacket protocol.PacketNumber
firstCapablePacket protocol.PacketNumber
numSentECT0, numSentECT1 int64
numAckedECT0, numAckedECT1, numAckedECNCE int64
tracer *logging.ConnectionTracer
logger utils.Logger
}
var _ ecnHandler = &ecnTracker{}
func newECNTracker(logger utils.Logger, tracer *logging.ConnectionTracer) *ecnTracker {
return &ecnTracker{
firstTestingPacket: protocol.InvalidPacketNumber,
lastTestingPacket: protocol.InvalidPacketNumber,
firstCapablePacket: protocol.InvalidPacketNumber,
state: ecnStateInitial,
logger: logger,
tracer: tracer,
}
}
func (e *ecnTracker) SentPacket(pn protocol.PacketNumber, ecn protocol.ECN) {
//nolint:exhaustive // These are the only ones we need to take care of.
switch ecn {
case protocol.ECNNon:
return
case protocol.ECT0:
e.numSentECT0++
case protocol.ECT1:
e.numSentECT1++
case protocol.ECNUnsupported:
if e.state != ecnStateFailed {
panic("didn't expect ECN to be unsupported")
}
default:
panic(fmt.Sprintf("sent packet with unexpected ECN marking: %s", ecn))
}
if e.state == ecnStateCapable && e.firstCapablePacket == protocol.InvalidPacketNumber {
e.firstCapablePacket = pn
}
if e.state != ecnStateTesting {
return
}
e.numSentTesting++
if e.firstTestingPacket == protocol.InvalidPacketNumber {
e.firstTestingPacket = pn
}
if e.numSentECT0+e.numSentECT1 >= numECNTestingPackets {
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
}
e.state = ecnStateUnknown
e.lastTestingPacket = pn
}
}
func (e *ecnTracker) Mode() protocol.ECN {
switch e.state {
case ecnStateInitial:
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
}
e.state = ecnStateTesting
return e.Mode()
case ecnStateTesting, ecnStateCapable:
return protocol.ECT0
case ecnStateUnknown, ecnStateFailed:
return protocol.ECNNon
default:
panic(fmt.Sprintf("unknown ECN state: %d", e.state))
}
}
func (e *ecnTracker) LostPacket(pn protocol.PacketNumber) {
if e.state != ecnStateTesting && e.state != ecnStateUnknown {
return
}
if !e.isTestingPacket(pn) {
return
}
e.numLostTesting++
// Only proceed if we have sent all 10 testing packets.
if e.state != ecnStateUnknown {
return
}
if e.numLostTesting >= e.numSentTesting {
e.logger.Debugf("Disabling ECN. All testing packets were lost.")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets)
}
e.state = ecnStateFailed
return
}
// Path validation also fails if some testing packets are lost, and all other testing packets where CE-marked
e.failIfMangled()
}
// HandleNewlyAcked handles the ECN counts on an ACK frame.
// It must only be called for ACK frames that increase the largest acknowledged packet number,
// see section 13.4.2.1 of RFC 9000.
func (e *ecnTracker) HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool) {
if e.state == ecnStateFailed {
return false
}
// ECN validation can fail if the received total count for either ECT(0) or ECT(1) exceeds
// the total number of packets sent with each corresponding ECT codepoint.
if ect0 > e.numSentECT0 || ect1 > e.numSentECT1 {
e.logger.Debugf("Disabling ECN. Received more ECT(0) / ECT(1) acknowledgements than packets sent.")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent)
}
e.state = ecnStateFailed
return false
}
// Count ECT0 and ECT1 marks that we used when sending the packets that are now being acknowledged.
var ackedECT0, ackedECT1 int64
for _, p := range packets {
//nolint:exhaustive // We only ever send ECT(0) and ECT(1).
switch e.ecnMarking(p.PacketNumber) {
case protocol.ECT0:
ackedECT0++
case protocol.ECT1:
ackedECT1++
}
}
// If an ACK frame newly acknowledges a packet that the endpoint sent with either the ECT(0) or ECT(1)
// codepoint set, ECN validation fails if the corresponding ECN counts are not present in the ACK frame.
// This check detects:
// * paths that bleach all ECN marks, and
// * peers that don't report any ECN counts
if (ackedECT0 > 0 || ackedECT1 > 0) && ect0 == 0 && ect1 == 0 && ecnce == 0 {
e.logger.Debugf("Disabling ECN. ECN-marked packet acknowledged, but no ECN counts on ACK frame.")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedNoECNCounts)
}
e.state = ecnStateFailed
return false
}
// Determine the increase in ECT0, ECT1 and ECNCE marks
newECT0 := ect0 - e.numAckedECT0
newECT1 := ect1 - e.numAckedECT1
newECNCE := ecnce - e.numAckedECNCE
// We're only processing ACKs that increase the Largest Acked.
// Therefore, the ECN counters should only ever increase.
// Any decrease means that the peer's counting logic is broken.
if newECT0 < 0 || newECT1 < 0 || newECNCE < 0 {
e.logger.Debugf("Disabling ECN. ECN counts decreased unexpectedly.")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedDecreasedECNCounts)
}
e.state = ecnStateFailed
return false
}
// ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is less than the number
// of newly acknowledged packets that were originally sent with an ECT(0) marking.
// This could be the result of (partial) bleaching.
if newECT0+newECNCE < ackedECT0 {
e.logger.Debugf("Disabling ECN. Received less ECT(0) + ECN-CE than packets sent with ECT(0).")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts)
}
e.state = ecnStateFailed
return false
}
// Similarly, ECN validation fails if the sum of the increases to ECT(1) and ECN-CE counts is less than
// the number of newly acknowledged packets sent with an ECT(1) marking.
if newECT1+newECNCE < ackedECT1 {
e.logger.Debugf("Disabling ECN. Received less ECT(1) + ECN-CE than packets sent with ECT(1).")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts)
}
e.state = ecnStateFailed
return false
}
// update our counters
e.numAckedECT0 = ect0
e.numAckedECT1 = ect1
e.numAckedECNCE = ecnce
// Detect mangling (a path remarking all ECN-marked testing packets as CE),
// once all 10 testing packets have been sent out.
if e.state == ecnStateUnknown {
e.failIfMangled()
if e.state == ecnStateFailed {
return false
}
}
if e.state == ecnStateTesting || e.state == ecnStateUnknown {
var ackedTestingPacket bool
for _, p := range packets {
if e.isTestingPacket(p.PacketNumber) {
ackedTestingPacket = true
break
}
}
// This check won't succeed if the path is mangling ECN-marks (i.e. rewrites all ECN-marked packets to CE).
if ackedTestingPacket && (newECT0 > 0 || newECT1 > 0) {
e.logger.Debugf("ECN capability confirmed.")
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
}
e.state = ecnStateCapable
}
}
// Don't trust CE marks before having confirmed ECN capability of the path.
// Otherwise, mangling would be misinterpreted as actual congestion.
return e.state == ecnStateCapable && newECNCE > 0
}
// failIfMangled fails ECN validation if all testing packets are lost or CE-marked.
func (e *ecnTracker) failIfMangled() {
numAckedECNCE := e.numAckedECNCE + int64(e.numLostTesting)
if e.numSentECT0+e.numSentECT1 > numAckedECNCE {
return
}
if e.tracer != nil && e.tracer.ECNStateUpdated != nil {
e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
}
e.state = ecnStateFailed
}
func (e *ecnTracker) ecnMarking(pn protocol.PacketNumber) protocol.ECN {
if pn < e.firstTestingPacket || e.firstTestingPacket == protocol.InvalidPacketNumber {
return protocol.ECNNon
}
if pn < e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber {
return protocol.ECT0
}
if pn < e.firstCapablePacket || e.firstCapablePacket == protocol.InvalidPacketNumber {
return protocol.ECNNon
}
// We don't need to deal with the case when ECN validation fails,
// since we're ignoring any ECN counts reported in ACK frames in that case.
return protocol.ECT0
}
func (e *ecnTracker) isTestingPacket(pn protocol.PacketNumber) bool {
if e.firstTestingPacket == protocol.InvalidPacketNumber {
return false
}
return pn >= e.firstTestingPacket && (pn <= e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber)
}

View File

@@ -1,29 +1,21 @@
package ackhandler
import (
"sync"
"github.com/quic-go/quic-go/internal/wire"
)
// FrameHandler handles the acknowledgement and the loss of a frame.
type FrameHandler interface {
OnAcked(wire.Frame)
OnLost(wire.Frame)
}
type Frame struct {
wire.Frame // nil if the frame has already been acknowledged in another packet
OnLost func(wire.Frame)
OnAcked func(wire.Frame)
Frame wire.Frame // nil if the frame has already been acknowledged in another packet
Handler FrameHandler
}
var framePool = sync.Pool{New: func() any { return &Frame{} }}
func GetFrame() *Frame {
f := framePool.Get().(*Frame)
f.OnLost = nil
f.OnAcked = nil
return f
}
func putFrame(f *Frame) {
f.Frame = nil
f.OnLost = nil
f.OnAcked = nil
framePool.Put(f)
type StreamFrame struct {
Frame *wire.StreamFrame
Handler FrameHandler
}

View File

@@ -10,25 +10,26 @@ import (
// SentPacketHandler handles ACKs received for outgoing packets
type SentPacketHandler interface {
// SentPacket may modify the packet
SentPacket(packet *Packet)
ReceivedAck(ackFrame *wire.AckFrame, encLevel protocol.EncryptionLevel, recvTime time.Time) (bool /* 1-RTT packet acked */, error)
SentPacket(t time.Time, pn, largestAcked protocol.PacketNumber, streamFrames []StreamFrame, frames []Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket bool)
// ReceivedAck processes an ACK frame.
// It does not store a copy of the frame.
ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* 1-RTT packet acked */, error)
ReceivedBytes(protocol.ByteCount)
DropPackets(protocol.EncryptionLevel)
ResetForRetry() error
ResetForRetry(rcvTime time.Time) error
SetHandshakeConfirmed()
// The SendMode determines if and what kind of packets can be sent.
SendMode() SendMode
SendMode(now time.Time) SendMode
// TimeUntilSend is the time when the next packet should be sent.
// It is used for pacing packets.
TimeUntilSend() time.Time
// HasPacingBudget says if the pacer allows sending of a (full size) packet at this moment.
HasPacingBudget() bool
SetMaxDatagramSize(count protocol.ByteCount)
// only to be called once the handshake is complete
QueueProbePacket(protocol.EncryptionLevel) bool /* was a packet queued */
ECNMode(isShortHeaderPacket bool) protocol.ECN // isShortHeaderPacket should only be true for non-coalesced 1-RTT packets
PeekPacketNumber(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)
PopPacketNumber(protocol.EncryptionLevel) protocol.PacketNumber
@@ -44,7 +45,7 @@ type sentPacketTracker interface {
// ReceivedPacketHandler handles ACKs needed to send for incoming packets
type ReceivedPacketHandler interface {
IsPotentiallyDuplicate(protocol.PacketNumber, protocol.EncryptionLevel) bool
ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, encLevel protocol.EncryptionLevel, rcvTime time.Time, shouldInstigateAck bool) error
ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, encLevel protocol.EncryptionLevel, rcvTime time.Time, ackEliciting bool) error
DropPackets(protocol.EncryptionLevel)
GetAlarmTimeout() time.Time

View File

@@ -2,5 +2,8 @@
package ackhandler
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker"
type SentPacketTracker = sentPacketTracker
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_ecn_handler_test.go github.com/quic-go/quic-go/internal/ackhandler ECNHandler"
type ECNHandler = ecnHandler

View File

@@ -8,13 +8,14 @@ import (
)
// A Packet is a packet
type Packet struct {
type packet struct {
SendTime time.Time
PacketNumber protocol.PacketNumber
Frames []*Frame
StreamFrames []StreamFrame
Frames []Frame
LargestAcked protocol.PacketNumber // InvalidPacketNumber if the packet doesn't contain an ACK
Length protocol.ByteCount
EncryptionLevel protocol.EncryptionLevel
SendTime time.Time
IsPathMTUProbePacket bool // We don't report the loss of Path MTU probe packets to the congestion controller.
@@ -23,15 +24,16 @@ type Packet struct {
skippedPacket bool
}
func (p *Packet) outstanding() bool {
func (p *packet) outstanding() bool {
return !p.declaredLost && !p.skippedPacket && !p.IsPathMTUProbePacket
}
var packetPool = sync.Pool{New: func() any { return &Packet{} }}
var packetPool = sync.Pool{New: func() any { return &packet{} }}
func GetPacket() *Packet {
p := packetPool.Get().(*Packet)
func getPacket() *packet {
p := packetPool.Get().(*packet)
p.PacketNumber = 0
p.StreamFrames = nil
p.Frames = nil
p.LargestAcked = 0
p.Length = 0
@@ -46,10 +48,8 @@ func GetPacket() *Packet {
// We currently only return Packets back into the pool when they're acknowledged (not when they're lost).
// This simplifies the code, and gives the vast majority of the performance benefit we can gain from using the pool.
func putPacket(p *Packet) {
for _, f := range p.Frames {
putFrame(f)
}
func putPacket(p *packet) {
p.Frames = nil
p.StreamFrames = nil
packetPool.Put(p)
}

View File

@@ -7,7 +7,10 @@ import (
type packetNumberGenerator interface {
Peek() protocol.PacketNumber
Pop() protocol.PacketNumber
// Pop pops the packet number.
// It reports if the packet number (before the one just popped) was skipped.
// It never skips more than one packet number in a row.
Pop() (skipped bool, _ protocol.PacketNumber)
}
type sequentialPacketNumberGenerator struct {
@@ -24,10 +27,10 @@ func (p *sequentialPacketNumberGenerator) Peek() protocol.PacketNumber {
return p.next
}
func (p *sequentialPacketNumberGenerator) Pop() protocol.PacketNumber {
func (p *sequentialPacketNumberGenerator) Pop() (bool, protocol.PacketNumber) {
next := p.next
p.next++
return next
return false, next
}
// The skippingPacketNumberGenerator generates the packet number for the next packet
@@ -56,21 +59,26 @@ func newSkippingPacketNumberGenerator(initial, initialPeriod, maxPeriod protocol
}
func (p *skippingPacketNumberGenerator) Peek() protocol.PacketNumber {
if p.next == p.nextToSkip {
return p.next + 1
}
return p.next
}
func (p *skippingPacketNumberGenerator) Pop() protocol.PacketNumber {
func (p *skippingPacketNumberGenerator) Pop() (bool, protocol.PacketNumber) {
next := p.next
p.next++ // generate a new packet number for the next packet
if p.next == p.nextToSkip {
p.next++
next++
p.next += 2
p.generateNewSkip()
return true, next
}
return next
p.next++ // generate a new packet number for the next packet
return false, next
}
func (p *skippingPacketNumberGenerator) generateNewSkip() {
// make sure that there are never two consecutive packet numbers that are skipped
p.nextToSkip = p.next + 2 + protocol.PacketNumber(p.rng.Int31n(int32(2*p.period)))
p.nextToSkip = p.next + 3 + protocol.PacketNumber(p.rng.Int31n(int32(2*p.period)))
p.period = utils.Min(2*p.period, p.maxPeriod)
}

View File

@@ -40,24 +40,29 @@ func (h *receivedPacketHandler) ReceivedPacket(
ecn protocol.ECN,
encLevel protocol.EncryptionLevel,
rcvTime time.Time,
shouldInstigateAck bool,
ackEliciting bool,
) error {
h.sentPackets.ReceivedPacket(encLevel)
switch encLevel {
case protocol.EncryptionInitial:
return h.initialPackets.ReceivedPacket(pn, ecn, rcvTime, shouldInstigateAck)
return h.initialPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
case protocol.EncryptionHandshake:
return h.handshakePackets.ReceivedPacket(pn, ecn, rcvTime, shouldInstigateAck)
// The Handshake packet number space might already have been dropped as a result
// of processing the CRYPTO frame that was contained in this packet.
if h.handshakePackets == nil {
return nil
}
return h.handshakePackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
case protocol.Encryption0RTT:
if h.lowest1RTTPacket != protocol.InvalidPacketNumber && pn > h.lowest1RTTPacket {
return fmt.Errorf("received packet number %d on a 0-RTT packet after receiving %d on a 1-RTT packet", pn, h.lowest1RTTPacket)
}
return h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, shouldInstigateAck)
return h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
case protocol.Encryption1RTT:
if h.lowest1RTTPacket == protocol.InvalidPacketNumber || pn < h.lowest1RTTPacket {
h.lowest1RTTPacket = pn
}
if err := h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, shouldInstigateAck); err != nil {
if err := h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting); err != nil {
return err
}
h.appDataPackets.IgnoreBelow(h.sentPackets.GetLowestPacketNotConfirmedAcked())

View File

@@ -13,10 +13,10 @@ import (
const packetsBeforeAck = 2
type receivedPacketTracker struct {
largestObserved protocol.PacketNumber
ignoreBelow protocol.PacketNumber
largestObservedReceivedTime time.Time
ect0, ect1, ecnce uint64
largestObserved protocol.PacketNumber
ignoreBelow protocol.PacketNumber
largestObservedRcvdTime time.Time
ect0, ect1, ecnce uint64
packetHistory *receivedPacketHistory
@@ -45,25 +45,23 @@ func newReceivedPacketTracker(
}
}
func (h *receivedPacketTracker) ReceivedPacket(packetNumber protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, shouldInstigateAck bool) error {
if isNew := h.packetHistory.ReceivedPacket(packetNumber); !isNew {
return fmt.Errorf("recevedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", packetNumber)
func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
if isNew := h.packetHistory.ReceivedPacket(pn); !isNew {
return fmt.Errorf("recevedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", pn)
}
isMissing := h.isMissing(packetNumber)
if packetNumber >= h.largestObserved {
h.largestObserved = packetNumber
h.largestObservedReceivedTime = rcvTime
isMissing := h.isMissing(pn)
if pn >= h.largestObserved {
h.largestObserved = pn
h.largestObservedRcvdTime = rcvTime
}
if shouldInstigateAck {
if ackEliciting {
h.hasNewAck = true
h.maybeQueueACK(pn, rcvTime, ecn, isMissing)
}
if shouldInstigateAck {
h.maybeQueueAck(packetNumber, rcvTime, isMissing)
}
//nolint:exhaustive // Only need to count ECT(0), ECT(1) and ECN-CE.
switch ecn {
case protocol.ECNNon:
case protocol.ECT0:
h.ect0++
case protocol.ECT1:
@@ -76,14 +74,14 @@ func (h *receivedPacketTracker) ReceivedPacket(packetNumber protocol.PacketNumbe
// IgnoreBelow sets a lower limit for acknowledging packets.
// Packets with packet numbers smaller than p will not be acked.
func (h *receivedPacketTracker) IgnoreBelow(p protocol.PacketNumber) {
if p <= h.ignoreBelow {
func (h *receivedPacketTracker) IgnoreBelow(pn protocol.PacketNumber) {
if pn <= h.ignoreBelow {
return
}
h.ignoreBelow = p
h.packetHistory.DeleteBelow(p)
h.ignoreBelow = pn
h.packetHistory.DeleteBelow(pn)
if h.logger.Debug() {
h.logger.Debugf("\tIgnoring all packets below %d.", p)
h.logger.Debugf("\tIgnoring all packets below %d.", pn)
}
}
@@ -103,8 +101,8 @@ func (h *receivedPacketTracker) hasNewMissingPackets() bool {
return highestRange.Smallest > h.lastAck.LargestAcked()+1 && highestRange.Len() == 1
}
// maybeQueueAck queues an ACK, if necessary.
func (h *receivedPacketTracker) maybeQueueAck(pn protocol.PacketNumber, rcvTime time.Time, wasMissing bool) {
// maybeQueueACK queues an ACK, if necessary.
func (h *receivedPacketTracker) maybeQueueACK(pn protocol.PacketNumber, rcvTime time.Time, ecn protocol.ECN, wasMissing bool) {
// always acknowledge the first packet
if h.lastAck == nil {
if !h.ackQueued {
@@ -143,12 +141,18 @@ func (h *receivedPacketTracker) maybeQueueAck(pn protocol.PacketNumber, rcvTime
h.ackAlarm = rcvTime.Add(h.maxAckDelay)
}
// Queue an ACK if there are new missing packets to report.
// queue an ACK if there are new missing packets to report
if h.hasNewMissingPackets() {
h.logger.Debugf("\tQueuing ACK because there's a new missing packet to report.")
h.ackQueued = true
}
// queue an ACK if the packet was ECN-CE marked
if ecn == protocol.ECNCE {
h.logger.Debugf("\tQueuing ACK because the packet was ECN-CE marked.")
h.ackQueued = true
}
if h.ackQueued {
// cancel the ack alarm
h.ackAlarm = time.Time{}
@@ -169,16 +173,18 @@ func (h *receivedPacketTracker) GetAckFrame(onlyIfQueued bool) *wire.AckFrame {
}
}
ack := wire.GetAckFrame()
ack.DelayTime = utils.Max(0, now.Sub(h.largestObservedReceivedTime))
// This function always returns the same ACK frame struct, filled with the most recent values.
ack := h.lastAck
if ack == nil {
ack = &wire.AckFrame{}
}
ack.Reset()
ack.DelayTime = utils.Max(0, now.Sub(h.largestObservedRcvdTime))
ack.ECT0 = h.ect0
ack.ECT1 = h.ect1
ack.ECNCE = h.ecnce
ack.AckRanges = h.packetHistory.AppendAckRanges(ack.AckRanges)
if h.lastAck != nil {
wire.PutAckFrame(h.lastAck)
}
h.lastAck = ack
h.ackAlarm = time.Time{}
h.ackQueued = false

View File

@@ -16,6 +16,10 @@ const (
SendPTOHandshake
// SendPTOAppData means that an Application data probe packet should be sent
SendPTOAppData
// SendPacingLimited means that the pacer doesn't allow sending of a packet right now,
// but will do in a little while.
// The timestamp when sending is allowed again can be obtained via the SentPacketHandler.TimeUntilSend.
SendPacingLimited
// SendAny means that any packet should be sent
SendAny
)
@@ -34,6 +38,8 @@ func (s SendMode) String() string {
return "pto (Application Data)"
case SendAny:
return "any"
case SendPacingLimited:
return "pacing limited"
default:
return fmt.Sprintf("invalid send mode: %d", s)
}

View File

@@ -38,7 +38,7 @@ type packetNumberSpace struct {
largestSent protocol.PacketNumber
}
func newPacketNumberSpace(initialPN protocol.PacketNumber, skipPNs bool, rttStats *utils.RTTStats) *packetNumberSpace {
func newPacketNumberSpace(initialPN protocol.PacketNumber, skipPNs bool) *packetNumberSpace {
var pns packetNumberGenerator
if skipPNs {
pns = newSkippingPacketNumberGenerator(initialPN, protocol.SkipPacketInitialPeriod, protocol.SkipPacketMaxPeriod)
@@ -46,7 +46,7 @@ func newPacketNumberSpace(initialPN protocol.PacketNumber, skipPNs bool, rttStat
pns = newSequentialPacketNumberGenerator(initialPN)
}
return &packetNumberSpace{
history: newSentPacketHistory(rttStats),
history: newSentPacketHistory(),
pns: pns,
largestSent: protocol.InvalidPacketNumber,
largestAcked: protocol.InvalidPacketNumber,
@@ -75,7 +75,7 @@ type sentPacketHandler struct {
// Only applies to the application-data packet number space.
lowestNotConfirmedAcked protocol.PacketNumber
ackedPackets []*Packet // to avoid allocations in detectAndRemoveAckedPackets
ackedPackets []*packet // to avoid allocations in detectAndRemoveAckedPackets
bytesInFlight protocol.ByteCount
@@ -92,9 +92,12 @@ type sentPacketHandler struct {
// The alarm timeout
alarm time.Time
enableECN bool
ecnTracker ecnHandler
perspective protocol.Perspective
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
logger utils.Logger
}
@@ -110,8 +113,9 @@ func newSentPacketHandler(
initialMaxDatagramSize protocol.ByteCount,
rttStats *utils.RTTStats,
clientAddressValidated bool,
enableECN bool,
pers protocol.Perspective,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
logger utils.Logger,
) *sentPacketHandler {
congestion := congestion.NewCubicSender(
@@ -122,31 +126,26 @@ func newSentPacketHandler(
tracer,
)
return &sentPacketHandler{
h := &sentPacketHandler{
peerCompletedAddressValidation: pers == protocol.PerspectiveServer,
peerAddressValidated: pers == protocol.PerspectiveClient || clientAddressValidated,
initialPackets: newPacketNumberSpace(initialPN, false, rttStats),
handshakePackets: newPacketNumberSpace(0, false, rttStats),
appDataPackets: newPacketNumberSpace(0, true, rttStats),
initialPackets: newPacketNumberSpace(initialPN, false),
handshakePackets: newPacketNumberSpace(0, false),
appDataPackets: newPacketNumberSpace(0, true),
rttStats: rttStats,
congestion: congestion,
perspective: pers,
tracer: tracer,
logger: logger,
}
}
func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) {
if h.perspective == protocol.PerspectiveClient && encLevel == protocol.EncryptionInitial {
// This function is called when the crypto setup seals a Handshake packet.
// If this Handshake packet is coalesced behind an Initial packet, we would drop the Initial packet number space
// before SentPacket() was called for that Initial packet.
return
if enableECN {
h.enableECN = true
h.ecnTracker = newECNTracker(logger, tracer)
}
h.dropPackets(encLevel)
return h
}
func (h *sentPacketHandler) removeFromBytesInFlight(p *Packet) {
func (h *sentPacketHandler) removeFromBytesInFlight(p *packet) {
if p.includedInBytesInFlight {
if p.Length > h.bytesInFlight {
panic("negative bytes_in_flight")
@@ -156,7 +155,7 @@ func (h *sentPacketHandler) removeFromBytesInFlight(p *Packet) {
}
}
func (h *sentPacketHandler) dropPackets(encLevel protocol.EncryptionLevel) {
func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) {
// The server won't await address validation after the handshake is confirmed.
// This applies even if we didn't receive an ACK for a Handshake packet.
if h.perspective == protocol.PerspectiveClient && encLevel == protocol.EncryptionHandshake {
@@ -165,7 +164,11 @@ func (h *sentPacketHandler) dropPackets(encLevel protocol.EncryptionLevel) {
// remove outstanding packets from bytes_in_flight
if encLevel == protocol.EncryptionInitial || encLevel == protocol.EncryptionHandshake {
pnSpace := h.getPacketNumberSpace(encLevel)
pnSpace.history.Iterate(func(p *Packet) (bool, error) {
// We might already have dropped this packet number space.
if pnSpace == nil {
return
}
pnSpace.history.Iterate(func(p *packet) (bool, error) {
h.removeFromBytesInFlight(p)
return true, nil
})
@@ -182,8 +185,8 @@ func (h *sentPacketHandler) dropPackets(encLevel protocol.EncryptionLevel) {
// and not when the client drops 0-RTT keys when the handshake completes.
// When 0-RTT is rejected, all application data sent so far becomes invalid.
// Delete the packets from the history and remove them from bytes_in_flight.
h.appDataPackets.history.Iterate(func(p *Packet) (bool, error) {
if p.EncryptionLevel != protocol.Encryption0RTT {
h.appDataPackets.history.Iterate(func(p *packet) (bool, error) {
if p.EncryptionLevel != protocol.Encryption0RTT && !p.skippedPacket {
return false, nil
}
h.removeFromBytesInFlight(p)
@@ -193,7 +196,7 @@ func (h *sentPacketHandler) dropPackets(encLevel protocol.EncryptionLevel) {
default:
panic(fmt.Sprintf("Cannot drop keys for encryption level %s", encLevel))
}
if h.tracer != nil && h.ptoCount != 0 {
if h.tracer != nil && h.tracer.UpdatedPTOCount != nil && h.ptoCount != 0 {
h.tracer.UpdatedPTOCount(0)
}
h.ptoCount = 0
@@ -228,26 +231,65 @@ func (h *sentPacketHandler) packetsInFlight() int {
return packetsInFlight
}
func (h *sentPacketHandler) SentPacket(p *Packet) {
h.bytesSent += p.Length
// For the client, drop the Initial packet number space when the first Handshake packet is sent.
if h.perspective == protocol.PerspectiveClient && p.EncryptionLevel == protocol.EncryptionHandshake && h.initialPackets != nil {
h.dropPackets(protocol.EncryptionInitial)
func (h *sentPacketHandler) SentPacket(
t time.Time,
pn, largestAcked protocol.PacketNumber,
streamFrames []StreamFrame,
frames []Frame,
encLevel protocol.EncryptionLevel,
ecn protocol.ECN,
size protocol.ByteCount,
isPathMTUProbePacket bool,
) {
h.bytesSent += size
pnSpace := h.getPacketNumberSpace(encLevel)
if h.logger.Debug() && pnSpace.history.HasOutstandingPackets() {
for p := utils.Max(0, pnSpace.largestSent+1); p < pn; p++ {
h.logger.Debugf("Skipping packet number %d", p)
}
}
isAckEliciting := h.sentPacketImpl(p)
pnSpace.largestSent = pn
isAckEliciting := len(streamFrames) > 0 || len(frames) > 0
if isAckEliciting {
h.getPacketNumberSpace(p.EncryptionLevel).history.SentAckElicitingPacket(p)
} else {
h.getPacketNumberSpace(p.EncryptionLevel).history.SentNonAckElicitingPacket(p.PacketNumber, p.EncryptionLevel, p.SendTime)
putPacket(p)
p = nil //nolint:ineffassign // This is just to be on the safe side.
pnSpace.lastAckElicitingPacketTime = t
h.bytesInFlight += size
if h.numProbesToSend > 0 {
h.numProbesToSend--
}
}
if h.tracer != nil && isAckEliciting {
h.congestion.OnPacketSent(t, h.bytesInFlight, pn, size, isAckEliciting)
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil {
h.ecnTracker.SentPacket(pn, ecn)
}
if !isAckEliciting {
pnSpace.history.SentNonAckElicitingPacket(pn)
if !h.peerCompletedAddressValidation {
h.setLossDetectionTimer()
}
return
}
p := getPacket()
p.SendTime = t
p.PacketNumber = pn
p.EncryptionLevel = encLevel
p.Length = size
p.LargestAcked = largestAcked
p.StreamFrames = streamFrames
p.Frames = frames
p.IsPathMTUProbePacket = isPathMTUProbePacket
p.includedInBytesInFlight = true
pnSpace.history.SentAckElicitingPacket(p)
if h.tracer != nil && h.tracer.UpdatedMetrics != nil {
h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight())
}
if isAckEliciting || !h.peerCompletedAddressValidation {
h.setLossDetectionTimer()
}
h.setLossDetectionTimer()
}
func (h *sentPacketHandler) getPacketNumberSpace(encLevel protocol.EncryptionLevel) *packetNumberSpace {
@@ -263,31 +305,6 @@ func (h *sentPacketHandler) getPacketNumberSpace(encLevel protocol.EncryptionLev
}
}
func (h *sentPacketHandler) sentPacketImpl(packet *Packet) bool /* is ack-eliciting */ {
pnSpace := h.getPacketNumberSpace(packet.EncryptionLevel)
if h.logger.Debug() && pnSpace.history.HasOutstandingPackets() {
for p := utils.Max(0, pnSpace.largestSent+1); p < packet.PacketNumber; p++ {
h.logger.Debugf("Skipping packet number %d", p)
}
}
pnSpace.largestSent = packet.PacketNumber
isAckEliciting := len(packet.Frames) > 0
if isAckEliciting {
pnSpace.lastAckElicitingPacketTime = packet.SendTime
packet.includedInBytesInFlight = true
h.bytesInFlight += packet.Length
if h.numProbesToSend > 0 {
h.numProbesToSend--
}
}
h.congestion.OnPacketSent(packet.SendTime, h.bytesInFlight, packet.PacketNumber, packet.Length, isAckEliciting)
return isAckEliciting
}
func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* contained 1-RTT packet */, error) {
pnSpace := h.getPacketNumberSpace(encLevel)
@@ -299,8 +316,6 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
}
}
pnSpace.largestAcked = utils.Max(pnSpace.largestAcked, largestAcked)
// Servers complete address validation when a protected packet is received.
if h.perspective == protocol.PerspectiveClient && !h.peerCompletedAddressValidation &&
(encLevel == protocol.EncryptionHandshake || encLevel == protocol.Encryption1RTT) {
@@ -330,6 +345,17 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
h.congestion.MaybeExitSlowStart()
}
}
// Only inform the ECN tracker about new 1-RTT ACKs if the ACK increases the largest acked.
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil && largestAcked > pnSpace.largestAcked {
congested := h.ecnTracker.HandleNewlyAcked(ackedPackets, int64(ack.ECT0), int64(ack.ECT1), int64(ack.ECNCE))
if congested {
h.congestion.OnCongestionEvent(largestAcked, 0, priorInFlight)
}
}
pnSpace.largestAcked = utils.Max(pnSpace.largestAcked, largestAcked)
if err := h.detectLostPackets(rcvTime, encLevel); err != nil {
return false, err
}
@@ -350,18 +376,17 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
// Reset the pto_count unless the client is unsure if the server has validated the client's address.
if h.peerCompletedAddressValidation {
if h.tracer != nil && h.ptoCount != 0 {
if h.tracer != nil && h.tracer.UpdatedPTOCount != nil && h.ptoCount != 0 {
h.tracer.UpdatedPTOCount(0)
}
h.ptoCount = 0
}
h.numProbesToSend = 0
if h.tracer != nil {
if h.tracer != nil && h.tracer.UpdatedMetrics != nil {
h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight())
}
pnSpace.history.DeleteOldPackets(rcvTime)
h.setLossDetectionTimer()
return acked1RTTPacket, nil
}
@@ -371,13 +396,13 @@ func (h *sentPacketHandler) GetLowestPacketNotConfirmedAcked() protocol.PacketNu
}
// Packets are returned in ascending packet number order.
func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encLevel protocol.EncryptionLevel) ([]*Packet, error) {
func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encLevel protocol.EncryptionLevel) ([]*packet, error) {
pnSpace := h.getPacketNumberSpace(encLevel)
h.ackedPackets = h.ackedPackets[:0]
ackRangeIndex := 0
lowestAcked := ack.LowestAcked()
largestAcked := ack.LargestAcked()
err := pnSpace.history.Iterate(func(p *Packet) (bool, error) {
err := pnSpace.history.Iterate(func(p *packet) (bool, error) {
// Ignore packets below the lowest acked
if p.PacketNumber < lowestAcked {
return true, nil
@@ -425,14 +450,19 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL
}
for _, f := range p.Frames {
if f.OnAcked != nil {
f.OnAcked(f.Frame)
if f.Handler != nil {
f.Handler.OnAcked(f.Frame)
}
}
for _, f := range p.StreamFrames {
if f.Handler != nil {
f.Handler.OnAcked(f.Frame)
}
}
if err := pnSpace.history.Remove(p.PacketNumber); err != nil {
return nil, err
}
if h.tracer != nil {
if h.tracer != nil && h.tracer.AcknowledgedPacket != nil {
h.tracer.AcknowledgedPacket(encLevel, p.PacketNumber)
}
}
@@ -525,7 +555,7 @@ func (h *sentPacketHandler) setLossDetectionTimer() {
if !lossTime.IsZero() {
// Early retransmit timer or time loss detection.
h.alarm = lossTime
if h.tracer != nil && h.alarm != oldAlarm {
if h.tracer != nil && h.tracer.SetLossTimer != nil && h.alarm != oldAlarm {
h.tracer.SetLossTimer(logging.TimerTypeACK, encLevel, h.alarm)
}
return
@@ -536,7 +566,7 @@ func (h *sentPacketHandler) setLossDetectionTimer() {
h.alarm = time.Time{}
if !oldAlarm.IsZero() {
h.logger.Debugf("Canceling loss detection timer. Amplification limited.")
if h.tracer != nil {
if h.tracer != nil && h.tracer.LossTimerCanceled != nil {
h.tracer.LossTimerCanceled()
}
}
@@ -548,7 +578,7 @@ func (h *sentPacketHandler) setLossDetectionTimer() {
h.alarm = time.Time{}
if !oldAlarm.IsZero() {
h.logger.Debugf("Canceling loss detection timer. No packets in flight.")
if h.tracer != nil {
if h.tracer != nil && h.tracer.LossTimerCanceled != nil {
h.tracer.LossTimerCanceled()
}
}
@@ -561,14 +591,14 @@ func (h *sentPacketHandler) setLossDetectionTimer() {
if !oldAlarm.IsZero() {
h.alarm = time.Time{}
h.logger.Debugf("Canceling loss detection timer. No PTO needed..")
if h.tracer != nil {
if h.tracer != nil && h.tracer.LossTimerCanceled != nil {
h.tracer.LossTimerCanceled()
}
}
return
}
h.alarm = ptoTime
if h.tracer != nil && h.alarm != oldAlarm {
if h.tracer != nil && h.tracer.SetLossTimer != nil && h.alarm != oldAlarm {
h.tracer.SetLossTimer(logging.TimerTypePTO, encLevel, h.alarm)
}
}
@@ -587,30 +617,31 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
lostSendTime := now.Add(-lossDelay)
priorInFlight := h.bytesInFlight
return pnSpace.history.Iterate(func(p *Packet) (bool, error) {
return pnSpace.history.Iterate(func(p *packet) (bool, error) {
if p.PacketNumber > pnSpace.largestAcked {
return false, nil
}
if p.declaredLost || p.skippedPacket {
return true, nil
}
var packetLost bool
if p.SendTime.Before(lostSendTime) {
packetLost = true
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (time threshold)", p.PacketNumber)
}
if h.tracer != nil {
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossTimeThreshold)
if !p.skippedPacket {
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (time threshold)", p.PacketNumber)
}
if h.tracer != nil && h.tracer.LostPacket != nil {
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossTimeThreshold)
}
}
} else if pnSpace.largestAcked >= p.PacketNumber+packetThreshold {
packetLost = true
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (reordering threshold)", p.PacketNumber)
}
if h.tracer != nil {
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossReorderingThreshold)
if !p.skippedPacket {
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (reordering threshold)", p.PacketNumber)
}
if h.tracer != nil && h.tracer.LostPacket != nil {
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossReorderingThreshold)
}
}
} else if pnSpace.lossTime.IsZero() {
// Note: This conditional is only entered once per call
@@ -621,12 +652,17 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
pnSpace.lossTime = lossTime
}
if packetLost {
p = pnSpace.history.DeclareLost(p)
// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p)
if !p.IsPathMTUProbePacket {
h.congestion.OnPacketLost(p.PacketNumber, p.Length, priorInFlight)
pnSpace.history.DeclareLost(p.PacketNumber)
if !p.skippedPacket {
// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p)
if !p.IsPathMTUProbePacket {
h.congestion.OnCongestionEvent(p.PacketNumber, p.Length, priorInFlight)
}
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil {
h.ecnTracker.LostPacket(p.PacketNumber)
}
}
}
return true, nil
@@ -640,7 +676,7 @@ func (h *sentPacketHandler) OnLossDetectionTimeout() error {
if h.logger.Debug() {
h.logger.Debugf("Loss detection alarm fired in loss timer mode. Loss time: %s", earliestLossTime)
}
if h.tracer != nil {
if h.tracer != nil && h.tracer.LossTimerExpired != nil {
h.tracer.LossTimerExpired(logging.TimerTypeACK, encLevel)
}
// Early retransmit or time loss detection
@@ -677,8 +713,12 @@ func (h *sentPacketHandler) OnLossDetectionTimeout() error {
h.logger.Debugf("Loss detection alarm for %s fired in PTO mode. PTO count: %d", encLevel, h.ptoCount)
}
if h.tracer != nil {
h.tracer.LossTimerExpired(logging.TimerTypePTO, encLevel)
h.tracer.UpdatedPTOCount(h.ptoCount)
if h.tracer.LossTimerExpired != nil {
h.tracer.LossTimerExpired(logging.TimerTypePTO, encLevel)
}
if h.tracer.UpdatedPTOCount != nil {
h.tracer.UpdatedPTOCount(h.ptoCount)
}
}
h.numProbesToSend += 2
//nolint:exhaustive // We never arm a PTO timer for 0-RTT packets.
@@ -689,7 +729,8 @@ func (h *sentPacketHandler) OnLossDetectionTimeout() error {
h.ptoMode = SendPTOHandshake
case protocol.Encryption1RTT:
// skip a packet number in order to elicit an immediate ACK
_ = h.PopPacketNumber(protocol.Encryption1RTT)
pn := h.PopPacketNumber(protocol.Encryption1RTT)
h.getPacketNumberSpace(protocol.Encryption1RTT).history.SkippedPacket(pn)
h.ptoMode = SendPTOAppData
default:
return fmt.Errorf("PTO timer in unexpected encryption level: %s", encLevel)
@@ -701,25 +742,37 @@ func (h *sentPacketHandler) GetLossDetectionTimeout() time.Time {
return h.alarm
}
func (h *sentPacketHandler) ECNMode(isShortHeaderPacket bool) protocol.ECN {
if !h.enableECN {
return protocol.ECNUnsupported
}
if !isShortHeaderPacket {
return protocol.ECNNon
}
return h.ecnTracker.Mode()
}
func (h *sentPacketHandler) PeekPacketNumber(encLevel protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen) {
pnSpace := h.getPacketNumberSpace(encLevel)
var lowestUnacked protocol.PacketNumber
if p := pnSpace.history.FirstOutstanding(); p != nil {
lowestUnacked = p.PacketNumber
} else {
lowestUnacked = pnSpace.largestAcked + 1
}
pn := pnSpace.pns.Peek()
return pn, protocol.GetPacketNumberLengthForHeader(pn, lowestUnacked)
// See section 17.1 of RFC 9000.
return pn, protocol.GetPacketNumberLengthForHeader(pn, pnSpace.largestAcked)
}
func (h *sentPacketHandler) PopPacketNumber(encLevel protocol.EncryptionLevel) protocol.PacketNumber {
return h.getPacketNumberSpace(encLevel).pns.Pop()
pnSpace := h.getPacketNumberSpace(encLevel)
skipped, pn := pnSpace.pns.Pop()
if skipped {
skippedPN := pn - 1
pnSpace.history.SkippedPacket(skippedPN)
if h.logger.Debug() {
h.logger.Debugf("Skipping packet number %d", skippedPN)
}
}
return pn
}
func (h *sentPacketHandler) SendMode() SendMode {
func (h *sentPacketHandler) SendMode(now time.Time) SendMode {
numTrackedPackets := h.appDataPackets.history.Len()
if h.initialPackets != nil {
numTrackedPackets += h.initialPackets.history.Len()
@@ -758,6 +811,9 @@ func (h *sentPacketHandler) SendMode() SendMode {
}
return SendAck
}
if !h.congestion.HasPacingBudget(now) {
return SendPacingLimited
}
return SendAny
}
@@ -765,10 +821,6 @@ func (h *sentPacketHandler) TimeUntilSend() time.Time {
return h.congestion.TimeUntilSend(h.bytesInFlight)
}
func (h *sentPacketHandler) HasPacingBudget() bool {
return h.congestion.HasPacingBudget()
}
func (h *sentPacketHandler) SetMaxDatagramSize(s protocol.ByteCount) {
h.congestion.SetMaxDatagramSize(s)
}
@@ -790,24 +842,32 @@ func (h *sentPacketHandler) QueueProbePacket(encLevel protocol.EncryptionLevel)
// TODO: don't declare the packet lost here.
// Keep track of acknowledged frames instead.
h.removeFromBytesInFlight(p)
pnSpace.history.DeclareLost(p)
pnSpace.history.DeclareLost(p.PacketNumber)
return true
}
func (h *sentPacketHandler) queueFramesForRetransmission(p *Packet) {
if len(p.Frames) == 0 {
func (h *sentPacketHandler) queueFramesForRetransmission(p *packet) {
if len(p.Frames) == 0 && len(p.StreamFrames) == 0 {
panic("no frames")
}
for _, f := range p.Frames {
f.OnLost(f.Frame)
if f.Handler != nil {
f.Handler.OnLost(f.Frame)
}
}
for _, f := range p.StreamFrames {
if f.Handler != nil {
f.Handler.OnLost(f.Frame)
}
}
p.StreamFrames = nil
p.Frames = nil
}
func (h *sentPacketHandler) ResetForRetry() error {
func (h *sentPacketHandler) ResetForRetry(now time.Time) error {
h.bytesInFlight = 0
var firstPacketSendTime time.Time
h.initialPackets.history.Iterate(func(p *Packet) (bool, error) {
h.initialPackets.history.Iterate(func(p *packet) (bool, error) {
if firstPacketSendTime.IsZero() {
firstPacketSendTime = p.SendTime
}
@@ -819,7 +879,7 @@ func (h *sentPacketHandler) ResetForRetry() error {
})
// All application data packets sent at this point are 0-RTT packets.
// In the case of a Retry, we can assume that the server dropped all of them.
h.appDataPackets.history.Iterate(func(p *Packet) (bool, error) {
h.appDataPackets.history.Iterate(func(p *packet) (bool, error) {
if !p.declaredLost && !p.skippedPacket {
h.queueFramesForRetransmission(p)
}
@@ -830,22 +890,23 @@ func (h *sentPacketHandler) ResetForRetry() error {
// Otherwise, we don't know which Initial the Retry was sent in response to.
if h.ptoCount == 0 {
// Don't set the RTT to a value lower than 5ms here.
now := time.Now()
h.rttStats.UpdateRTT(utils.Max(minRTTAfterRetry, now.Sub(firstPacketSendTime)), 0, now)
if h.logger.Debug() {
h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation())
}
if h.tracer != nil {
if h.tracer != nil && h.tracer.UpdatedMetrics != nil {
h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight())
}
}
h.initialPackets = newPacketNumberSpace(h.initialPackets.pns.Pop(), false, h.rttStats)
h.appDataPackets = newPacketNumberSpace(h.appDataPackets.pns.Pop(), true, h.rttStats)
h.initialPackets = newPacketNumberSpace(h.initialPackets.pns.Peek(), false)
h.appDataPackets = newPacketNumberSpace(h.appDataPackets.pns.Peek(), true)
oldAlarm := h.alarm
h.alarm = time.Time{}
if h.tracer != nil {
h.tracer.UpdatedPTOCount(0)
if !oldAlarm.IsZero() {
if h.tracer.UpdatedPTOCount != nil {
h.tracer.UpdatedPTOCount(0)
}
if !oldAlarm.IsZero() && h.tracer.LossTimerCanceled != nil {
h.tracer.LossTimerCanceled()
}
}
@@ -854,6 +915,12 @@ func (h *sentPacketHandler) ResetForRetry() error {
}
func (h *sentPacketHandler) SetHandshakeConfirmed() {
if h.initialPackets != nil {
panic("didn't drop initial correctly")
}
if h.handshakePackets != nil {
panic("didn't drop handshake correctly")
}
h.handshakeConfirmed = true
// We don't send PTOs for application data packets before the handshake completes.
// Make sure the timer is armed now, if necessary.

View File

@@ -2,162 +2,176 @@ package ackhandler
import (
"fmt"
"sync"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
list "github.com/quic-go/quic-go/internal/utils/linkedlist"
)
type sentPacketHistory struct {
rttStats *utils.RTTStats
outstandingPacketList *list.List[*Packet]
etcPacketList *list.List[*Packet]
packetMap map[protocol.PacketNumber]*list.Element[*Packet]
highestSent protocol.PacketNumber
packets []*packet
numOutstanding int
highestPacketNumber protocol.PacketNumber
}
var packetElementPool sync.Pool
func init() {
packetElementPool = *list.NewPool[*Packet]()
}
func newSentPacketHistory(rttStats *utils.RTTStats) *sentPacketHistory {
func newSentPacketHistory() *sentPacketHistory {
return &sentPacketHistory{
rttStats: rttStats,
outstandingPacketList: list.NewWithPool[*Packet](&packetElementPool),
etcPacketList: list.NewWithPool[*Packet](&packetElementPool),
packetMap: make(map[protocol.PacketNumber]*list.Element[*Packet]),
highestSent: protocol.InvalidPacketNumber,
packets: make([]*packet, 0, 32),
highestPacketNumber: protocol.InvalidPacketNumber,
}
}
func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel, t time.Time) {
h.registerSentPacket(pn, encLevel, t)
func (h *sentPacketHistory) checkSequentialPacketNumberUse(pn protocol.PacketNumber) {
if h.highestPacketNumber != protocol.InvalidPacketNumber {
if pn != h.highestPacketNumber+1 {
panic("non-sequential packet number use")
}
}
}
func (h *sentPacketHistory) SentAckElicitingPacket(p *Packet) {
h.registerSentPacket(p.PacketNumber, p.EncryptionLevel, p.SendTime)
func (h *sentPacketHistory) SkippedPacket(pn protocol.PacketNumber) {
h.checkSequentialPacketNumberUse(pn)
h.highestPacketNumber = pn
h.packets = append(h.packets, &packet{
PacketNumber: pn,
skippedPacket: true,
})
}
var el *list.Element[*Packet]
func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber) {
h.checkSequentialPacketNumberUse(pn)
h.highestPacketNumber = pn
if len(h.packets) > 0 {
h.packets = append(h.packets, nil)
}
}
func (h *sentPacketHistory) SentAckElicitingPacket(p *packet) {
h.checkSequentialPacketNumberUse(p.PacketNumber)
h.highestPacketNumber = p.PacketNumber
h.packets = append(h.packets, p)
if p.outstanding() {
el = h.outstandingPacketList.PushBack(p)
} else {
el = h.etcPacketList.PushBack(p)
h.numOutstanding++
}
h.packetMap[p.PacketNumber] = el
}
func (h *sentPacketHistory) registerSentPacket(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel, t time.Time) {
if pn <= h.highestSent {
panic("non-sequential packet number use")
}
// Skipped packet numbers.
for p := h.highestSent + 1; p < pn; p++ {
el := h.etcPacketList.PushBack(&Packet{
PacketNumber: p,
EncryptionLevel: encLevel,
SendTime: t,
skippedPacket: true,
})
h.packetMap[p] = el
}
h.highestSent = pn
}
// Iterate iterates through all packets.
func (h *sentPacketHistory) Iterate(cb func(*Packet) (cont bool, err error)) error {
cont := true
outstandingEl := h.outstandingPacketList.Front()
etcEl := h.etcPacketList.Front()
var el *list.Element[*Packet]
// whichever has the next packet number is returned first
for cont {
if outstandingEl == nil || (etcEl != nil && etcEl.Value.PacketNumber < outstandingEl.Value.PacketNumber) {
el = etcEl
} else {
el = outstandingEl
func (h *sentPacketHistory) Iterate(cb func(*packet) (cont bool, err error)) error {
for _, p := range h.packets {
if p == nil {
continue
}
if el == nil {
return nil
}
if el == outstandingEl {
outstandingEl = outstandingEl.Next()
} else {
etcEl = etcEl.Next()
}
var err error
cont, err = cb(el.Value)
cont, err := cb(p)
if err != nil {
return err
}
if !cont {
return nil
}
}
return nil
}
// FirstOutstanding returns the first outstanding packet.
func (h *sentPacketHistory) FirstOutstanding() *Packet {
el := h.outstandingPacketList.Front()
if el == nil {
func (h *sentPacketHistory) FirstOutstanding() *packet {
if !h.HasOutstandingPackets() {
return nil
}
return el.Value
}
func (h *sentPacketHistory) Len() int {
return len(h.packetMap)
}
func (h *sentPacketHistory) Remove(p protocol.PacketNumber) error {
el, ok := h.packetMap[p]
if !ok {
return fmt.Errorf("packet %d not found in sent packet history", p)
for _, p := range h.packets {
if p != nil && p.outstanding() {
return p
}
}
el.List().Remove(el)
delete(h.packetMap, p)
return nil
}
func (h *sentPacketHistory) HasOutstandingPackets() bool {
return h.outstandingPacketList.Len() > 0
func (h *sentPacketHistory) Len() int {
return len(h.packets)
}
func (h *sentPacketHistory) DeleteOldPackets(now time.Time) {
maxAge := 3 * h.rttStats.PTO(false)
var nextEl *list.Element[*Packet]
// we don't iterate outstandingPacketList, as we should not delete outstanding packets.
// being outstanding for more than 3*PTO should only happen in the case of drastic RTT changes.
for el := h.etcPacketList.Front(); el != nil; el = nextEl {
nextEl = el.Next()
p := el.Value
if p.SendTime.After(now.Add(-maxAge)) {
break
}
delete(h.packetMap, p.PacketNumber)
h.etcPacketList.Remove(el)
}
}
func (h *sentPacketHistory) DeclareLost(p *Packet) *Packet {
el, ok := h.packetMap[p.PacketNumber]
func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error {
idx, ok := h.getIndex(pn)
if !ok {
return nil
return fmt.Errorf("packet %d not found in sent packet history", pn)
}
el.List().Remove(el)
p.declaredLost = true
// move it to the correct position in the etc list (based on the packet number)
for el = h.etcPacketList.Back(); el != nil; el = el.Prev() {
if el.Value.PacketNumber < p.PacketNumber {
break
p := h.packets[idx]
if p.outstanding() {
h.numOutstanding--
if h.numOutstanding < 0 {
panic("negative number of outstanding packets")
}
}
if el == nil {
el = h.etcPacketList.PushFront(p)
} else {
el = h.etcPacketList.InsertAfter(p, el)
h.packets[idx] = nil
// clean up all skipped packets directly before this packet number
for idx > 0 {
idx--
p := h.packets[idx]
if p == nil || !p.skippedPacket {
break
}
h.packets[idx] = nil
}
if idx == 0 {
h.cleanupStart()
}
if len(h.packets) > 0 && h.packets[0] == nil {
panic("remove failed")
}
return nil
}
// getIndex gets the index of packet p in the packets slice.
func (h *sentPacketHistory) getIndex(p protocol.PacketNumber) (int, bool) {
if len(h.packets) == 0 {
return 0, false
}
first := h.packets[0].PacketNumber
if p < first {
return 0, false
}
index := int(p - first)
if index > len(h.packets)-1 {
return 0, false
}
return index, true
}
func (h *sentPacketHistory) HasOutstandingPackets() bool {
return h.numOutstanding > 0
}
// delete all nil entries at the beginning of the packets slice
func (h *sentPacketHistory) cleanupStart() {
for i, p := range h.packets {
if p != nil {
h.packets = h.packets[i:]
return
}
}
h.packets = h.packets[:0]
}
func (h *sentPacketHistory) LowestPacketNumber() protocol.PacketNumber {
if len(h.packets) == 0 {
return protocol.InvalidPacketNumber
}
return h.packets[0].PacketNumber
}
func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) {
idx, ok := h.getIndex(pn)
if !ok {
return
}
p := h.packets[idx]
if p.outstanding() {
h.numOutstanding--
if h.numOutstanding < 0 {
panic("negative number of outstanding packets")
}
}
h.packets[idx] = nil
if idx == 0 {
h.cleanupStart()
}
h.packetMap[p.PacketNumber] = el
return el.Value
}

View File

@@ -56,7 +56,7 @@ type cubicSender struct {
maxDatagramSize protocol.ByteCount
lastState logging.CongestionState
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
}
var (
@@ -70,7 +70,7 @@ func NewCubicSender(
rttStats *utils.RTTStats,
initialMaxDatagramSize protocol.ByteCount,
reno bool,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
) *cubicSender {
return newCubicSender(
clock,
@@ -90,7 +90,7 @@ func newCubicSender(
initialMaxDatagramSize,
initialCongestionWindow,
initialMaxCongestionWindow protocol.ByteCount,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
) *cubicSender {
c := &cubicSender{
rttStats: rttStats,
@@ -108,7 +108,7 @@ func newCubicSender(
maxDatagramSize: initialMaxDatagramSize,
}
c.pacer = newPacer(c.BandwidthEstimate)
if c.tracer != nil {
if c.tracer != nil && c.tracer.UpdatedCongestionState != nil {
c.lastState = logging.CongestionStateSlowStart
c.tracer.UpdatedCongestionState(logging.CongestionStateSlowStart)
}
@@ -120,8 +120,8 @@ func (c *cubicSender) TimeUntilSend(_ protocol.ByteCount) time.Time {
return c.pacer.TimeUntilSend()
}
func (c *cubicSender) HasPacingBudget() bool {
return c.pacer.Budget(c.clock.Now()) >= c.maxDatagramSize
func (c *cubicSender) HasPacingBudget(now time.Time) bool {
return c.pacer.Budget(now) >= c.maxDatagramSize
}
func (c *cubicSender) maxCongestionWindow() protocol.ByteCount {
@@ -188,7 +188,7 @@ func (c *cubicSender) OnPacketAcked(
}
}
func (c *cubicSender) OnPacketLost(packetNumber protocol.PacketNumber, lostBytes, priorInFlight protocol.ByteCount) {
func (c *cubicSender) OnCongestionEvent(packetNumber protocol.PacketNumber, lostBytes, priorInFlight protocol.ByteCount) {
// TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
// already sent should be treated as a single loss event, since it's expected.
if packetNumber <= c.largestSentAtLastCutback {
@@ -296,7 +296,7 @@ func (c *cubicSender) OnConnectionMigration() {
}
func (c *cubicSender) maybeTraceStateChange(new logging.CongestionState) {
if c.tracer == nil || new == c.lastState {
if c.tracer == nil || c.tracer.UpdatedCongestionState == nil || new == c.lastState {
return
}
c.tracer.UpdatedCongestionState(new)

View File

@@ -9,12 +9,12 @@ import (
// A SendAlgorithm performs congestion control
type SendAlgorithm interface {
TimeUntilSend(bytesInFlight protocol.ByteCount) time.Time
HasPacingBudget() bool
HasPacingBudget(now time.Time) bool
OnPacketSent(sentTime time.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool)
CanSend(bytesInFlight protocol.ByteCount) bool
MaybeExitSlowStart()
OnPacketAcked(number protocol.PacketNumber, ackedBytes protocol.ByteCount, priorInFlight protocol.ByteCount, eventTime time.Time)
OnPacketLost(number protocol.PacketNumber, lostBytes protocol.ByteCount, priorInFlight protocol.ByteCount)
OnCongestionEvent(number protocol.PacketNumber, lostBytes protocol.ByteCount, priorInFlight protocol.ByteCount)
OnRetransmissionTimeout(packetsRetransmitted bool)
SetMaxDatagramSize(protocol.ByteCount)
}

View File

@@ -1,7 +1,6 @@
package congestion
import (
"math"
"time"
"github.com/quic-go/quic-go/internal/protocol"
@@ -26,7 +25,7 @@ func newPacer(getBandwidth func() Bandwidth) *pacer {
bw := uint64(getBandwidth() / BytesPerSecond)
// Use a slightly higher value than the actual measured bandwidth.
// RTT variations then won't result in under-utilization of the congestion window.
// Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire,
// Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire,
// provided the congestion window is fully utilized and acknowledgments arrive at regular intervals.
return bw * 5 / 4
},
@@ -37,7 +36,7 @@ func newPacer(getBandwidth func() Bandwidth) *pacer {
func (p *pacer) SentPacket(sendTime time.Time, size protocol.ByteCount) {
budget := p.Budget(sendTime)
if size > budget {
if size >= budget {
p.budgetAtLastSent = 0
} else {
p.budgetAtLastSent = budget - size
@@ -69,10 +68,16 @@ func (p *pacer) TimeUntilSend() time.Time {
if p.budgetAtLastSent >= p.maxDatagramSize {
return time.Time{}
}
return p.lastSentTime.Add(utils.Max(
protocol.MinPacingDelay,
time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/float64(p.adjustedBandwidth())))*time.Nanosecond,
))
diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
bw := p.adjustedBandwidth()
// We might need to round up this value.
// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.
d := diff / bw
// this is effectively a math.Ceil, but using only integer math
if diff%bw > 0 {
d++
}
return p.lastSentTime.Add(utils.Max(protocol.MinPacingDelay, time.Duration(d)*time.Nanosecond))
}
func (p *pacer) SetMaxDatagramSize(s protocol.ByteCount) {

View File

@@ -5,11 +5,10 @@ import (
"encoding/binary"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qtls"
"github.com/quic-go/quic-go/internal/utils"
)
func createAEAD(suite *qtls.CipherSuiteTLS13, trafficSecret []byte, v protocol.VersionNumber) cipher.AEAD {
func createAEAD(suite *cipherSuite, trafficSecret []byte, v protocol.VersionNumber) cipher.AEAD {
keyLabel := hkdfLabelKeyV1
ivLabel := hkdfLabelIVV1
if v == protocol.Version2 {
@@ -93,69 +92,3 @@ func (o *longHeaderOpener) Open(dst, src []byte, pn protocol.PacketNumber, ad []
func (o *longHeaderOpener) DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
o.headerProtector.DecryptHeader(sample, firstByte, pnBytes)
}
type handshakeSealer struct {
LongHeaderSealer
dropInitialKeys func()
dropped bool
}
func newHandshakeSealer(
aead cipher.AEAD,
headerProtector headerProtector,
dropInitialKeys func(),
perspective protocol.Perspective,
) LongHeaderSealer {
sealer := newLongHeaderSealer(aead, headerProtector)
// The client drops Initial keys when sending the first Handshake packet.
if perspective == protocol.PerspectiveServer {
return sealer
}
return &handshakeSealer{
LongHeaderSealer: sealer,
dropInitialKeys: dropInitialKeys,
}
}
func (s *handshakeSealer) Seal(dst, src []byte, pn protocol.PacketNumber, ad []byte) []byte {
data := s.LongHeaderSealer.Seal(dst, src, pn, ad)
if !s.dropped {
s.dropInitialKeys()
s.dropped = true
}
return data
}
type handshakeOpener struct {
LongHeaderOpener
dropInitialKeys func()
dropped bool
}
func newHandshakeOpener(
aead cipher.AEAD,
headerProtector headerProtector,
dropInitialKeys func(),
perspective protocol.Perspective,
) LongHeaderOpener {
opener := newLongHeaderOpener(aead, headerProtector)
// The server drops Initial keys when first successfully processing a Handshake packet.
if perspective == protocol.PerspectiveClient {
return opener
}
return &handshakeOpener{
LongHeaderOpener: opener,
dropInitialKeys: dropInitialKeys,
}
}
func (o *handshakeOpener) Open(dst, src []byte, pn protocol.PacketNumber, ad []byte) ([]byte, error) {
dec, err := o.LongHeaderOpener.Open(dst, src, pn, ad)
if err == nil && !o.dropped {
o.dropInitialKeys()
o.dropped = true
}
return dec, err
}

View File

@@ -0,0 +1,104 @@
package handshake
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/tls"
"fmt"
"golang.org/x/crypto/chacha20poly1305"
)
// These cipher suite implementations are copied from the standard library crypto/tls package.
const aeadNonceLength = 12
type cipherSuite struct {
ID uint16
Hash crypto.Hash
KeyLen int
AEAD func(key, nonceMask []byte) cipher.AEAD
}
func (s cipherSuite) IVLen() int { return aeadNonceLength }
func getCipherSuite(id uint16) *cipherSuite {
switch id {
case tls.TLS_AES_128_GCM_SHA256:
return &cipherSuite{ID: tls.TLS_AES_128_GCM_SHA256, Hash: crypto.SHA256, KeyLen: 16, AEAD: aeadAESGCMTLS13}
case tls.TLS_CHACHA20_POLY1305_SHA256:
return &cipherSuite{ID: tls.TLS_CHACHA20_POLY1305_SHA256, Hash: crypto.SHA256, KeyLen: 32, AEAD: aeadChaCha20Poly1305}
case tls.TLS_AES_256_GCM_SHA384:
return &cipherSuite{ID: tls.TLS_AES_256_GCM_SHA384, Hash: crypto.SHA384, KeyLen: 32, AEAD: aeadAESGCMTLS13}
default:
panic(fmt.Sprintf("unknown cypher suite: %d", id))
}
}
func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {
if len(nonceMask) != aeadNonceLength {
panic("tls: internal error: wrong nonce length")
}
aes, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(aes)
if err != nil {
panic(err)
}
ret := &xorNonceAEAD{aead: aead}
copy(ret.nonceMask[:], nonceMask)
return ret
}
func aeadChaCha20Poly1305(key, nonceMask []byte) cipher.AEAD {
if len(nonceMask) != aeadNonceLength {
panic("tls: internal error: wrong nonce length")
}
aead, err := chacha20poly1305.New(key)
if err != nil {
panic(err)
}
ret := &xorNonceAEAD{aead: aead}
copy(ret.nonceMask[:], nonceMask)
return ret
}
// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce
// before each call.
type xorNonceAEAD struct {
nonceMask [aeadNonceLength]byte
aead cipher.AEAD
}
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result
}
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result, err
}

View File

@@ -0,0 +1,21 @@
package handshake
import (
"net"
"time"
)
type conn struct {
localAddr, remoteAddr net.Addr
}
var _ net.Conn = &conn{}
func (c *conn) Read([]byte) (int, error) { return 0, nil }
func (c *conn) Write([]byte) (int, error) { return 0, nil }
func (c *conn) Close() error { return nil }
func (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *conn) LocalAddr() net.Addr { return c.localAddr }
func (c *conn) SetReadDeadline(time.Time) error { return nil }
func (c *conn) SetWriteDeadline(time.Time) error { return nil }
func (c *conn) SetDeadline(time.Time) error { return nil }

View File

@@ -6,10 +6,10 @@ import (
"crypto/tls"
"errors"
"fmt"
"io"
"math"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/protocol"
@@ -25,102 +25,25 @@ type quicVersionContextKey struct{}
var QUICVersionContextKey = &quicVersionContextKey{}
// TLS unexpected_message alert
const alertUnexpectedMessage uint8 = 10
type messageType uint8
// TLS handshake message types.
const (
typeClientHello messageType = 1
typeServerHello messageType = 2
typeNewSessionTicket messageType = 4
typeEncryptedExtensions messageType = 8
typeCertificate messageType = 11
typeCertificateRequest messageType = 13
typeCertificateVerify messageType = 15
typeFinished messageType = 20
)
func (m messageType) String() string {
switch m {
case typeClientHello:
return "ClientHello"
case typeServerHello:
return "ServerHello"
case typeNewSessionTicket:
return "NewSessionTicket"
case typeEncryptedExtensions:
return "EncryptedExtensions"
case typeCertificate:
return "Certificate"
case typeCertificateRequest:
return "CertificateRequest"
case typeCertificateVerify:
return "CertificateVerify"
case typeFinished:
return "Finished"
default:
return fmt.Sprintf("unknown message type: %d", m)
}
}
const clientSessionStateRevision = 3
type conn struct {
localAddr, remoteAddr net.Addr
}
var _ net.Conn = &conn{}
func newConn(local, remote net.Addr) net.Conn {
return &conn{
localAddr: local,
remoteAddr: remote,
}
}
func (c *conn) Read([]byte) (int, error) { return 0, nil }
func (c *conn) Write([]byte) (int, error) { return 0, nil }
func (c *conn) Close() error { return nil }
func (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *conn) LocalAddr() net.Addr { return c.localAddr }
func (c *conn) SetReadDeadline(time.Time) error { return nil }
func (c *conn) SetWriteDeadline(time.Time) error { return nil }
func (c *conn) SetDeadline(time.Time) error { return nil }
type cryptoSetup struct {
tlsConf *tls.Config
extraConf *qtls.ExtraConfig
conn *qtls.Conn
tlsConf *tls.Config
conn *qtls.QUICConn
events []Event
version protocol.VersionNumber
messageChan chan []byte
isReadingHandshakeMessage chan struct{}
readFirstHandshakeMessage bool
ourParams *wire.TransportParameters
peerParams *wire.TransportParameters
paramsChan <-chan []byte
runner handshakeRunner
alertChan chan uint8
// handshakeDone is closed as soon as the go routine running qtls.Handshake() returns
handshakeDone chan struct{}
// is closed when Close() is called
closeChan chan struct{}
zeroRTTParameters *wire.TransportParameters
clientHelloWritten bool
clientHelloWrittenChan chan struct{} // is closed as soon as the ClientHello is written
zeroRTTParametersChan chan<- *wire.TransportParameters
allow0RTT bool
zeroRTTParameters *wire.TransportParameters
allow0RTT bool
rttStats *utils.RTTStats
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
logger utils.Logger
perspective protocol.Perspective
@@ -129,169 +52,152 @@ type cryptoSetup struct {
handshakeCompleteTime time.Time
readEncLevel protocol.EncryptionLevel
writeEncLevel protocol.EncryptionLevel
zeroRTTOpener LongHeaderOpener // only set for the server
zeroRTTSealer LongHeaderSealer // only set for the client
initialStream io.Writer
initialOpener LongHeaderOpener
initialSealer LongHeaderSealer
handshakeStream io.Writer
handshakeOpener LongHeaderOpener
handshakeSealer LongHeaderSealer
used0RTT atomic.Bool
aead *updatableAEAD
has1RTTSealer bool
has1RTTOpener bool
}
var (
_ qtls.RecordLayer = &cryptoSetup{}
_ CryptoSetup = &cryptoSetup{}
)
var _ CryptoSetup = &cryptoSetup{}
// NewCryptoSetupClient creates a new crypto setup for the client
func NewCryptoSetupClient(
initialStream io.Writer,
handshakeStream io.Writer,
connID protocol.ConnectionID,
localAddr net.Addr,
remoteAddr net.Addr,
tp *wire.TransportParameters,
runner handshakeRunner,
tlsConf *tls.Config,
enable0RTT bool,
rttStats *utils.RTTStats,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
logger utils.Logger,
version protocol.VersionNumber,
) (CryptoSetup, <-chan *wire.TransportParameters /* ClientHello written. Receive nil for non-0-RTT */) {
cs, clientHelloWritten := newCryptoSetup(
initialStream,
handshakeStream,
) CryptoSetup {
cs := newCryptoSetup(
connID,
tp,
runner,
tlsConf,
enable0RTT,
rttStats,
tracer,
logger,
protocol.PerspectiveClient,
version,
)
cs.conn = qtls.Client(newConn(localAddr, remoteAddr), cs.tlsConf, cs.extraConf)
return cs, clientHelloWritten
tlsConf = tlsConf.Clone()
tlsConf.MinVersion = tls.VersionTLS13
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
qtls.SetupConfigForClient(quicConf, cs.marshalDataForSessionState, cs.handleDataFromSessionState)
cs.tlsConf = tlsConf
cs.allow0RTT = enable0RTT
cs.conn = qtls.QUICClient(quicConf)
cs.conn.SetTransportParameters(cs.ourParams.Marshal(protocol.PerspectiveClient))
return cs
}
// NewCryptoSetupServer creates a new crypto setup for the server
func NewCryptoSetupServer(
initialStream io.Writer,
handshakeStream io.Writer,
connID protocol.ConnectionID,
localAddr net.Addr,
remoteAddr net.Addr,
localAddr, remoteAddr net.Addr,
tp *wire.TransportParameters,
runner handshakeRunner,
tlsConf *tls.Config,
allow0RTT bool,
rttStats *utils.RTTStats,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
logger utils.Logger,
version protocol.VersionNumber,
) CryptoSetup {
cs, _ := newCryptoSetup(
initialStream,
handshakeStream,
cs := newCryptoSetup(
connID,
tp,
runner,
tlsConf,
allow0RTT,
rttStats,
tracer,
logger,
protocol.PerspectiveServer,
version,
)
cs.conn = qtls.Server(newConn(localAddr, remoteAddr), cs.tlsConf, cs.extraConf)
cs.allow0RTT = allow0RTT
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
qtls.SetupConfigForServer(quicConf, cs.allow0RTT, cs.getDataForSessionTicket, cs.handleSessionTicket)
addConnToClientHelloInfo(quicConf.TLSConfig, localAddr, remoteAddr)
cs.tlsConf = quicConf.TLSConfig
cs.conn = qtls.QUICServer(quicConf)
return cs
}
// The tls.Config contains two callbacks that pass in a tls.ClientHelloInfo.
// Since crypto/tls doesn't do it, we need to make sure to set the Conn field with a fake net.Conn
// that allows the caller to get the local and the remote address.
func addConnToClientHelloInfo(conf *tls.Config, localAddr, remoteAddr net.Addr) {
if conf.GetConfigForClient != nil {
gcfc := conf.GetConfigForClient
conf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
c, err := gcfc(info)
if c != nil {
c = c.Clone()
// This won't be necessary anymore once https://github.com/golang/go/issues/63722 is accepted.
c.MinVersion = tls.VersionTLS13
// We're returning a tls.Config here, so we need to apply this recursively.
addConnToClientHelloInfo(c, localAddr, remoteAddr)
}
return c, err
}
}
if conf.GetCertificate != nil {
gc := conf.GetCertificate
conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
return gc(info)
}
}
}
func newCryptoSetup(
initialStream io.Writer,
handshakeStream io.Writer,
connID protocol.ConnectionID,
tp *wire.TransportParameters,
runner handshakeRunner,
tlsConf *tls.Config,
enable0RTT bool,
rttStats *utils.RTTStats,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
logger utils.Logger,
perspective protocol.Perspective,
version protocol.VersionNumber,
) (*cryptoSetup, <-chan *wire.TransportParameters /* ClientHello written. Receive nil for non-0-RTT */) {
) *cryptoSetup {
initialSealer, initialOpener := NewInitialAEAD(connID, perspective, version)
if tracer != nil {
if tracer != nil && tracer.UpdatedKeyFromTLS != nil {
tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveClient)
tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveServer)
}
extHandler := newExtensionHandler(tp.Marshal(perspective), perspective, version)
zeroRTTParametersChan := make(chan *wire.TransportParameters, 1)
cs := &cryptoSetup{
tlsConf: tlsConf,
initialStream: initialStream,
initialSealer: initialSealer,
initialOpener: initialOpener,
handshakeStream: handshakeStream,
aead: newUpdatableAEAD(rttStats, tracer, logger, version),
readEncLevel: protocol.EncryptionInitial,
writeEncLevel: protocol.EncryptionInitial,
runner: runner,
allow0RTT: enable0RTT,
ourParams: tp,
paramsChan: extHandler.TransportParameters(),
rttStats: rttStats,
tracer: tracer,
logger: logger,
perspective: perspective,
handshakeDone: make(chan struct{}),
alertChan: make(chan uint8),
clientHelloWrittenChan: make(chan struct{}),
zeroRTTParametersChan: zeroRTTParametersChan,
messageChan: make(chan []byte, 1),
isReadingHandshakeMessage: make(chan struct{}),
closeChan: make(chan struct{}),
version: version,
return &cryptoSetup{
initialSealer: initialSealer,
initialOpener: initialOpener,
aead: newUpdatableAEAD(rttStats, tracer, logger, version),
events: make([]Event, 0, 16),
ourParams: tp,
rttStats: rttStats,
tracer: tracer,
logger: logger,
perspective: perspective,
version: version,
}
var maxEarlyData uint32
if enable0RTT {
maxEarlyData = math.MaxUint32
}
cs.extraConf = &qtls.ExtraConfig{
GetExtensions: extHandler.GetExtensions,
ReceivedExtensions: extHandler.ReceivedExtensions,
AlternativeRecordLayer: cs,
EnforceNextProtoSelection: true,
MaxEarlyData: maxEarlyData,
Accept0RTT: cs.accept0RTT,
Rejected0RTT: cs.rejected0RTT,
Enable0RTT: enable0RTT,
GetAppDataForSessionState: cs.marshalDataForSessionState,
SetAppDataFromSessionState: cs.handleDataFromSessionState,
}
return cs, zeroRTTParametersChan
}
func (h *cryptoSetup) ChangeConnectionID(id protocol.ConnectionID) {
initialSealer, initialOpener := NewInitialAEAD(id, h.perspective, h.version)
h.initialSealer = initialSealer
h.initialOpener = initialOpener
if h.tracer != nil {
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
h.tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveClient)
h.tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveServer)
}
@@ -301,142 +207,109 @@ func (h *cryptoSetup) SetLargest1RTTAcked(pn protocol.PacketNumber) error {
return h.aead.SetLargestAcked(pn)
}
func (h *cryptoSetup) RunHandshake() {
// Handle errors that might occur when HandleData() is called.
handshakeComplete := make(chan struct{})
handshakeErrChan := make(chan error, 1)
go func() {
defer close(h.handshakeDone)
if err := h.conn.HandshakeContext(context.WithValue(context.Background(), QUICVersionContextKey, h.version)); err != nil {
handshakeErrChan <- err
return
func (h *cryptoSetup) StartHandshake() error {
err := h.conn.Start(context.WithValue(context.Background(), QUICVersionContextKey, h.version))
if err != nil {
return wrapError(err)
}
for {
ev := h.conn.NextEvent()
done, err := h.handleEvent(ev)
if err != nil {
return wrapError(err)
}
close(handshakeComplete)
}()
if done {
break
}
}
if h.perspective == protocol.PerspectiveClient {
select {
case err := <-handshakeErrChan:
h.onError(0, err.Error())
return
case <-h.clientHelloWrittenChan:
if h.zeroRTTSealer != nil && h.zeroRTTParameters != nil {
h.logger.Debugf("Doing 0-RTT.")
h.events = append(h.events, Event{Kind: EventRestoredTransportParameters, TransportParameters: h.zeroRTTParameters})
} else {
h.logger.Debugf("Not doing 0-RTT. Has sealer: %t, has params: %t", h.zeroRTTSealer != nil, h.zeroRTTParameters != nil)
}
}
select {
case <-handshakeComplete: // return when the handshake is done
h.mutex.Lock()
h.handshakeCompleteTime = time.Now()
h.mutex.Unlock()
h.runner.OnHandshakeComplete()
case <-h.closeChan:
// wait until the Handshake() go routine has returned
<-h.handshakeDone
case alert := <-h.alertChan:
handshakeErr := <-handshakeErrChan
h.onError(alert, handshakeErr.Error())
}
}
func (h *cryptoSetup) onError(alert uint8, message string) {
var err error
if alert == 0 {
err = &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: message}
} else {
err = qerr.NewLocalCryptoError(alert, message)
}
h.runner.OnError(err)
return nil
}
// Close closes the crypto setup.
// It aborts the handshake, if it is still running.
// It must only be called once.
func (h *cryptoSetup) Close() error {
close(h.closeChan)
// wait until qtls.Handshake() actually returned
<-h.handshakeDone
return nil
return h.conn.Close()
}
// handleMessage handles a TLS handshake message.
// HandleMessage handles a TLS handshake message.
// It is called by the crypto streams when a new message is available.
// It returns if it is done with messages on the same encryption level.
func (h *cryptoSetup) HandleMessage(data []byte, encLevel protocol.EncryptionLevel) bool /* stream finished */ {
msgType := messageType(data[0])
h.logger.Debugf("Received %s message (%d bytes, encryption level: %s)", msgType, len(data), encLevel)
if err := h.checkEncryptionLevel(msgType, encLevel); err != nil {
h.onError(alertUnexpectedMessage, err.Error())
return false
}
if encLevel != protocol.Encryption1RTT {
select {
case h.messageChan <- data:
case <-h.handshakeDone: // handshake errored, nobody is going to consume this message
return false
}
}
if encLevel == protocol.Encryption1RTT {
h.messageChan <- data
h.handlePostHandshakeMessage()
return false
}
readLoop:
for {
select {
case data := <-h.paramsChan:
if data == nil {
h.onError(0x6d, "missing quic_transport_parameters extension")
} else {
h.handleTransportParameters(data)
}
case <-h.isReadingHandshakeMessage:
break readLoop
case <-h.handshakeDone:
break readLoop
case <-h.closeChan:
break readLoop
}
}
// We're done with the Initial encryption level after processing a ClientHello / ServerHello,
// but only if a handshake opener and sealer was created.
// Otherwise, a HelloRetryRequest was performed.
// We're done with the Handshake encryption level after processing the Finished message.
return ((msgType == typeClientHello || msgType == typeServerHello) && h.handshakeOpener != nil && h.handshakeSealer != nil) ||
msgType == typeFinished
}
func (h *cryptoSetup) checkEncryptionLevel(msgType messageType, encLevel protocol.EncryptionLevel) error {
var expected protocol.EncryptionLevel
switch msgType {
case typeClientHello, typeServerHello:
expected = protocol.EncryptionInitial
case typeEncryptedExtensions,
typeCertificate,
typeCertificateRequest,
typeCertificateVerify,
typeFinished:
expected = protocol.EncryptionHandshake
case typeNewSessionTicket:
expected = protocol.Encryption1RTT
default:
return fmt.Errorf("unexpected handshake message: %d", msgType)
}
if encLevel != expected {
return fmt.Errorf("expected handshake message %s to have encryption level %s, has %s", msgType, expected, encLevel)
func (h *cryptoSetup) HandleMessage(data []byte, encLevel protocol.EncryptionLevel) error {
if err := h.handleMessage(data, encLevel); err != nil {
return wrapError(err)
}
return nil
}
func (h *cryptoSetup) handleTransportParameters(data []byte) {
func (h *cryptoSetup) handleMessage(data []byte, encLevel protocol.EncryptionLevel) error {
if err := h.conn.HandleData(qtls.ToTLSEncryptionLevel(encLevel), data); err != nil {
return err
}
for {
ev := h.conn.NextEvent()
done, err := h.handleEvent(ev)
if err != nil {
return err
}
if done {
return nil
}
}
}
func (h *cryptoSetup) handleEvent(ev qtls.QUICEvent) (done bool, err error) {
switch ev.Kind {
case qtls.QUICNoEvent:
return true, nil
case qtls.QUICSetReadSecret:
h.SetReadKey(ev.Level, ev.Suite, ev.Data)
return false, nil
case qtls.QUICSetWriteSecret:
h.SetWriteKey(ev.Level, ev.Suite, ev.Data)
return false, nil
case qtls.QUICTransportParameters:
return false, h.handleTransportParameters(ev.Data)
case qtls.QUICTransportParametersRequired:
h.conn.SetTransportParameters(h.ourParams.Marshal(h.perspective))
return false, nil
case qtls.QUICRejectedEarlyData:
h.rejected0RTT()
return false, nil
case qtls.QUICWriteData:
h.WriteRecord(ev.Level, ev.Data)
return false, nil
case qtls.QUICHandshakeDone:
h.handshakeComplete()
return false, nil
default:
return false, fmt.Errorf("unexpected event: %d", ev.Kind)
}
}
func (h *cryptoSetup) NextEvent() Event {
if len(h.events) == 0 {
return Event{Kind: EventNoEvent}
}
ev := h.events[0]
h.events = h.events[1:]
return ev
}
func (h *cryptoSetup) handleTransportParameters(data []byte) error {
var tp wire.TransportParameters
if err := tp.Unmarshal(data, h.perspective.Opposite()); err != nil {
h.runner.OnError(&qerr.TransportError{
ErrorCode: qerr.TransportParameterError,
ErrorMessage: err.Error(),
})
return err
}
h.peerParams = &tp
h.runner.OnReceivedParams(h.peerParams)
h.events = append(h.events, Event{Kind: EventReceivedTransportParameters, TransportParameters: h.peerParams})
return nil
}
// must be called after receiving the transport parameters
@@ -447,13 +320,20 @@ func (h *cryptoSetup) marshalDataForSessionState() []byte {
return h.peerParams.MarshalForSessionTicket(b)
}
func (h *cryptoSetup) handleDataFromSessionState(data []byte) {
func (h *cryptoSetup) handleDataFromSessionState(data []byte) (allowEarlyData bool) {
tp, err := h.handleDataFromSessionStateImpl(data)
if err != nil {
h.logger.Debugf("Restoring of transport parameters from session ticket failed: %s", err.Error())
return
}
h.zeroRTTParameters = tp
// The session ticket might have been saved from a connection that allowed 0-RTT,
// and therefore contain transport parameters.
// Only use them if 0-RTT is actually used on the new connection.
if tp != nil && h.allow0RTT {
h.zeroRTTParameters = tp
return true
}
return false
}
func (h *cryptoSetup) handleDataFromSessionStateImpl(data []byte) (*wire.TransportParameters, error) {
@@ -477,25 +357,54 @@ func (h *cryptoSetup) handleDataFromSessionStateImpl(data []byte) (*wire.Transpo
return &tp, nil
}
// only valid for the server
func (h *cryptoSetup) GetSessionTicket() ([]byte, error) {
var appData []byte
// Save transport parameters to the session ticket if we're allowing 0-RTT.
if h.extraConf.MaxEarlyData > 0 {
appData = (&sessionTicket{
Parameters: h.ourParams,
RTT: h.rttStats.SmoothedRTT(),
}).Marshal()
func (h *cryptoSetup) getDataForSessionTicket() []byte {
ticket := &sessionTicket{
RTT: h.rttStats.SmoothedRTT(),
}
return h.conn.GetSessionTicket(appData)
if h.allow0RTT {
ticket.Parameters = h.ourParams
}
return ticket.Marshal()
}
// accept0RTT is called for the server when receiving the client's session ticket.
// It decides whether to accept 0-RTT.
func (h *cryptoSetup) accept0RTT(sessionTicketData []byte) bool {
// GetSessionTicket generates a new session ticket.
// Due to limitations in crypto/tls, it's only possible to generate a single session ticket per connection.
// It is only valid for the server.
func (h *cryptoSetup) GetSessionTicket() ([]byte, error) {
if err := qtls.SendSessionTicket(h.conn, h.allow0RTT); err != nil {
// Session tickets might be disabled by tls.Config.SessionTicketsDisabled.
// We can't check h.tlsConfig here, since the actual config might have been obtained from
// the GetConfigForClient callback.
// See https://github.com/golang/go/issues/62032.
// Once that issue is resolved, this error assertion can be removed.
if strings.Contains(err.Error(), "session ticket keys unavailable") {
return nil, nil
}
return nil, err
}
ev := h.conn.NextEvent()
if ev.Kind != qtls.QUICWriteData || ev.Level != qtls.QUICEncryptionLevelApplication {
panic("crypto/tls bug: where's my session ticket?")
}
ticket := ev.Data
if ev := h.conn.NextEvent(); ev.Kind != qtls.QUICNoEvent {
panic("crypto/tls bug: why more than one ticket?")
}
return ticket, nil
}
// handleSessionTicket is called for the server when receiving the client's session ticket.
// It reads parameters from the session ticket and checks whether to accept 0-RTT if the session ticket enabled 0-RTT.
// Note that the fact that the session ticket allows 0-RTT doesn't mean that the actual TLS handshake enables 0-RTT:
// A client may use a 0-RTT enabled session to resume a TLS session without using 0-RTT.
func (h *cryptoSetup) handleSessionTicket(sessionTicketData []byte, using0RTT bool) bool {
var t sessionTicket
if err := t.Unmarshal(sessionTicketData); err != nil {
h.logger.Debugf("Unmarshalling transport parameters from session ticket failed: %s", err.Error())
if err := t.Unmarshal(sessionTicketData, using0RTT); err != nil {
h.logger.Debugf("Unmarshalling session ticket failed: %s", err.Error())
return false
}
h.rttStats.SetInitialRTT(t.RTT)
if !using0RTT {
return false
}
valid := h.ourParams.ValidFor0RTT(t.Parameters)
@@ -508,7 +417,6 @@ func (h *cryptoSetup) accept0RTT(sessionTicketData []byte) bool {
return false
}
h.logger.Debugf("Accepting 0-RTT. Restoring RTT from session ticket: %s", t.RTT)
h.rttStats.SetInitialRTT(t.RTT)
return true
}
@@ -522,64 +430,16 @@ func (h *cryptoSetup) rejected0RTT() {
h.mutex.Unlock()
if had0RTTKeys {
h.runner.DropKeys(protocol.Encryption0RTT)
h.events = append(h.events, Event{Kind: EventDiscard0RTTKeys})
}
}
func (h *cryptoSetup) handlePostHandshakeMessage() {
// make sure the handshake has already completed
<-h.handshakeDone
done := make(chan struct{})
defer close(done)
// h.alertChan is an unbuffered channel.
// If an error occurs during conn.HandlePostHandshakeMessage,
// it will be sent on this channel.
// Read it from a go-routine so that HandlePostHandshakeMessage doesn't deadlock.
alertChan := make(chan uint8, 1)
go func() {
<-h.isReadingHandshakeMessage
select {
case alert := <-h.alertChan:
alertChan <- alert
case <-done:
}
}()
if err := h.conn.HandlePostHandshakeMessage(); err != nil {
select {
case <-h.closeChan:
case alert := <-alertChan:
h.onError(alert, err.Error())
}
}
}
// ReadHandshakeMessage is called by TLS.
// It blocks until a new handshake message is available.
func (h *cryptoSetup) ReadHandshakeMessage() ([]byte, error) {
if !h.readFirstHandshakeMessage {
h.readFirstHandshakeMessage = true
} else {
select {
case h.isReadingHandshakeMessage <- struct{}{}:
case <-h.closeChan:
return nil, errors.New("error while handling the handshake message")
}
}
select {
case msg := <-h.messageChan:
return msg, nil
case <-h.closeChan:
return nil, errors.New("error while handling the handshake message")
}
}
func (h *cryptoSetup) SetReadKey(encLevel qtls.EncryptionLevel, suite *qtls.CipherSuiteTLS13, trafficSecret []byte) {
func (h *cryptoSetup) SetReadKey(el qtls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
suite := getCipherSuite(suiteID)
h.mutex.Lock()
switch encLevel {
case qtls.Encryption0RTT:
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
switch el {
case qtls.QUICEncryptionLevelEarly:
if h.perspective == protocol.PerspectiveClient {
panic("Received 0-RTT read key for the client")
}
@@ -587,27 +447,19 @@ func (h *cryptoSetup) SetReadKey(encLevel qtls.EncryptionLevel, suite *qtls.Ciph
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
)
h.mutex.Unlock()
h.used0RTT.Store(true)
if h.logger.Debug() {
h.logger.Debugf("Installed 0-RTT Read keys (using %s)", tls.CipherSuiteName(suite.ID))
}
if h.tracer != nil {
h.tracer.UpdatedKeyFromTLS(protocol.Encryption0RTT, h.perspective.Opposite())
}
return
case qtls.EncryptionHandshake:
h.readEncLevel = protocol.EncryptionHandshake
h.handshakeOpener = newHandshakeOpener(
case qtls.QUICEncryptionLevelHandshake:
h.handshakeOpener = newLongHeaderOpener(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
h.dropInitialKeys,
h.perspective,
)
if h.logger.Debug() {
h.logger.Debugf("Installed Handshake Read keys (using %s)", tls.CipherSuiteName(suite.ID))
}
case qtls.EncryptionApplication:
h.readEncLevel = protocol.Encryption1RTT
case qtls.QUICEncryptionLevelApplication:
h.aead.SetReadKey(suite, trafficSecret)
h.has1RTTOpener = true
if h.logger.Debug() {
@@ -617,15 +469,18 @@ func (h *cryptoSetup) SetReadKey(encLevel qtls.EncryptionLevel, suite *qtls.Ciph
panic("unexpected read encryption level")
}
h.mutex.Unlock()
if h.tracer != nil {
h.tracer.UpdatedKeyFromTLS(h.readEncLevel, h.perspective.Opposite())
h.events = append(h.events, Event{Kind: EventReceivedReadKeys})
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
h.tracer.UpdatedKeyFromTLS(qtls.FromTLSEncryptionLevel(el), h.perspective.Opposite())
}
}
func (h *cryptoSetup) SetWriteKey(encLevel qtls.EncryptionLevel, suite *qtls.CipherSuiteTLS13, trafficSecret []byte) {
func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
suite := getCipherSuite(suiteID)
h.mutex.Lock()
switch encLevel {
case qtls.Encryption0RTT:
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
switch el {
case qtls.QUICEncryptionLevelEarly:
if h.perspective == protocol.PerspectiveServer {
panic("Received 0-RTT write key for the server")
}
@@ -637,32 +492,31 @@ func (h *cryptoSetup) SetWriteKey(encLevel qtls.EncryptionLevel, suite *qtls.Cip
if h.logger.Debug() {
h.logger.Debugf("Installed 0-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
if h.tracer != nil {
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
h.tracer.UpdatedKeyFromTLS(protocol.Encryption0RTT, h.perspective)
}
// don't set used0RTT here. 0-RTT might still get rejected.
return
case qtls.EncryptionHandshake:
h.writeEncLevel = protocol.EncryptionHandshake
h.handshakeSealer = newHandshakeSealer(
case qtls.QUICEncryptionLevelHandshake:
h.handshakeSealer = newLongHeaderSealer(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
h.dropInitialKeys,
h.perspective,
)
if h.logger.Debug() {
h.logger.Debugf("Installed Handshake Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
case qtls.EncryptionApplication:
h.writeEncLevel = protocol.Encryption1RTT
case qtls.QUICEncryptionLevelApplication:
h.aead.SetWriteKey(suite, trafficSecret)
h.has1RTTSealer = true
if h.logger.Debug() {
h.logger.Debugf("Installed 1-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
if h.zeroRTTSealer != nil {
// Once we receive handshake keys, we know that 0-RTT was not rejected.
h.used0RTT.Store(true)
h.zeroRTTSealer = nil
h.logger.Debugf("Dropping 0-RTT keys.")
if h.tracer != nil {
if h.tracer != nil && h.tracer.DroppedEncryptionLevel != nil {
h.tracer.DroppedEncryptionLevel(protocol.Encryption0RTT)
}
}
@@ -670,56 +524,40 @@ func (h *cryptoSetup) SetWriteKey(encLevel qtls.EncryptionLevel, suite *qtls.Cip
panic("unexpected write encryption level")
}
h.mutex.Unlock()
if h.tracer != nil {
h.tracer.UpdatedKeyFromTLS(h.writeEncLevel, h.perspective)
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
h.tracer.UpdatedKeyFromTLS(qtls.FromTLSEncryptionLevel(el), h.perspective)
}
}
// WriteRecord is called when TLS writes data
func (h *cryptoSetup) WriteRecord(p []byte) (int, error) {
h.mutex.Lock()
defer h.mutex.Unlock()
//nolint:exhaustive // LS records can only be written for Initial and Handshake.
switch h.writeEncLevel {
case protocol.EncryptionInitial:
// assume that the first WriteRecord call contains the ClientHello
n, err := h.initialStream.Write(p)
if !h.clientHelloWritten && h.perspective == protocol.PerspectiveClient {
h.clientHelloWritten = true
close(h.clientHelloWrittenChan)
if h.zeroRTTSealer != nil && h.zeroRTTParameters != nil {
h.logger.Debugf("Doing 0-RTT.")
h.zeroRTTParametersChan <- h.zeroRTTParameters
} else {
h.logger.Debugf("Not doing 0-RTT.")
h.zeroRTTParametersChan <- nil
}
}
return n, err
case protocol.EncryptionHandshake:
return h.handshakeStream.Write(p)
func (h *cryptoSetup) WriteRecord(encLevel qtls.QUICEncryptionLevel, p []byte) {
//nolint:exhaustive // handshake records can only be written for Initial and Handshake.
switch encLevel {
case qtls.QUICEncryptionLevelInitial:
h.events = append(h.events, Event{Kind: EventWriteInitialData, Data: p})
case qtls.QUICEncryptionLevelHandshake:
h.events = append(h.events, Event{Kind: EventWriteHandshakeData, Data: p})
case qtls.QUICEncryptionLevelApplication:
panic("unexpected write")
default:
panic(fmt.Sprintf("unexpected write encryption level: %s", h.writeEncLevel))
panic(fmt.Sprintf("unexpected write encryption level: %s", encLevel))
}
}
func (h *cryptoSetup) SendAlert(alert uint8) {
select {
case h.alertChan <- alert:
case <-h.closeChan:
// no need to send an alert when we've already closed
}
}
// used a callback in the handshakeSealer and handshakeOpener
func (h *cryptoSetup) dropInitialKeys() {
func (h *cryptoSetup) DiscardInitialKeys() {
h.mutex.Lock()
dropped := h.initialOpener != nil
h.initialOpener = nil
h.initialSealer = nil
h.mutex.Unlock()
h.runner.DropKeys(protocol.EncryptionInitial)
h.logger.Debugf("Dropping Initial keys.")
if dropped {
h.logger.Debugf("Dropping Initial keys.")
}
}
func (h *cryptoSetup) handshakeComplete() {
h.handshakeCompleteTime = time.Now()
h.events = append(h.events, Event{Kind: EventHandshakeComplete})
}
func (h *cryptoSetup) SetHandshakeConfirmed() {
@@ -734,7 +572,6 @@ func (h *cryptoSetup) SetHandshakeConfirmed() {
}
h.mutex.Unlock()
if dropped {
h.runner.DropKeys(protocol.EncryptionHandshake)
h.logger.Debugf("Dropping Handshake keys.")
}
}
@@ -827,7 +664,7 @@ func (h *cryptoSetup) Get1RTTOpener() (ShortHeaderOpener, error) {
if h.zeroRTTOpener != nil && time.Since(h.handshakeCompleteTime) > 3*h.rttStats.PTO(true) {
h.zeroRTTOpener = nil
h.logger.Debugf("Dropping 0-RTT keys.")
if h.tracer != nil {
if h.tracer != nil && h.tracer.DroppedEncryptionLevel != nil {
h.tracer.DroppedEncryptionLevel(protocol.Encryption0RTT)
}
}
@@ -839,5 +676,16 @@ func (h *cryptoSetup) Get1RTTOpener() (ShortHeaderOpener, error) {
}
func (h *cryptoSetup) ConnectionState() ConnectionState {
return qtls.GetConnectionState(h.conn)
return ConnectionState{
ConnectionState: h.conn.ConnectionState(),
Used0RTT: h.used0RTT.Load(),
}
}
func wrapError(err error) error {
// alert 80 is an internal error
if alertErr := qtls.AlertError(0); errors.As(err, &alertErr) && alertErr != 80 {
return qerr.NewLocalCryptoError(uint8(alertErr), err)
}
return &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: err.Error()}
}

View File

@@ -10,7 +10,6 @@ import (
"golang.org/x/crypto/chacha20"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qtls"
)
type headerProtector interface {
@@ -25,7 +24,7 @@ func hkdfHeaderProtectionLabel(v protocol.VersionNumber) string {
return "quic hp"
}
func newHeaderProtector(suite *qtls.CipherSuiteTLS13, trafficSecret []byte, isLongHeader bool, v protocol.VersionNumber) headerProtector {
func newHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.VersionNumber) headerProtector {
hkdfLabel := hkdfHeaderProtectionLabel(v)
switch suite.ID {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
@@ -45,7 +44,7 @@ type aesHeaderProtector struct {
var _ headerProtector = &aesHeaderProtector{}
func newAESHeaderProtector(suite *qtls.CipherSuiteTLS13, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
func newAESHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
block, err := aes.NewCipher(hpKey)
if err != nil {
@@ -90,7 +89,7 @@ type chachaHeaderProtector struct {
var _ headerProtector = &chachaHeaderProtector{}
func newChaChaHeaderProtector(suite *qtls.CipherSuiteTLS13, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
func newChaChaHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
p := &chachaHeaderProtector{

View File

@@ -7,13 +7,11 @@ import (
"golang.org/x/crypto/hkdf"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qtls"
)
var (
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
quicSaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
quicSaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
quicSaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
quicSaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
)
const (
@@ -27,18 +25,10 @@ func getSalt(v protocol.VersionNumber) []byte {
if v == protocol.Version2 {
return quicSaltV2
}
if v == protocol.Version1 {
return quicSaltV1
}
return quicSaltOld
return quicSaltV1
}
var initialSuite = &qtls.CipherSuiteTLS13{
ID: tls.TLS_AES_128_GCM_SHA256,
KeyLen: 16,
AEAD: qtls.AEADAESGCMTLS13,
Hash: crypto.SHA256,
}
var initialSuite = getCipherSuite(tls.TLS_AES_128_GCM_SHA256)
// NewInitialAEAD creates a new AEAD for Initial encryption / decryption.
func NewInitialAEAD(connID protocol.ConnectionID, pers protocol.Perspective, v protocol.VersionNumber) (LongHeaderSealer, LongHeaderOpener) {
@@ -54,8 +44,8 @@ func NewInitialAEAD(connID protocol.ConnectionID, pers protocol.Perspective, v p
myKey, myIV := computeInitialKeyAndIV(mySecret, v)
otherKey, otherIV := computeInitialKeyAndIV(otherSecret, v)
encrypter := qtls.AEADAESGCMTLS13(myKey, myIV)
decrypter := qtls.AEADAESGCMTLS13(otherKey, otherIV)
encrypter := initialSuite.AEAD(myKey, myIV)
decrypter := initialSuite.AEAD(otherKey, otherIV)
return newLongHeaderSealer(encrypter, newHeaderProtector(initialSuite, mySecret, true, v)),
newLongHeaderOpener(decrypter, newAESHeaderProtector(initialSuite, otherSecret, true, hkdfHeaderProtectionLabel(v)))

View File

@@ -1,12 +1,12 @@
package handshake
import (
"crypto/tls"
"errors"
"io"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qtls"
"github.com/quic-go/quic-go/internal/wire"
)
@@ -22,9 +22,6 @@ var (
ErrDecryptionFailed = errors.New("decryption failed")
)
// ConnectionState contains information about the state of the connection.
type ConnectionState = qtls.ConnectionState
type headerDecryptor interface {
DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte)
}
@@ -56,29 +53,54 @@ type ShortHeaderSealer interface {
KeyPhase() protocol.KeyPhaseBit
}
// A tlsExtensionHandler sends and received the QUIC TLS extension.
type tlsExtensionHandler interface {
GetExtensions(msgType uint8) []qtls.Extension
ReceivedExtensions(msgType uint8, exts []qtls.Extension)
TransportParameters() <-chan []byte
type ConnectionState struct {
tls.ConnectionState
Used0RTT bool
}
type handshakeRunner interface {
OnReceivedParams(*wire.TransportParameters)
OnHandshakeComplete()
OnError(error)
DropKeys(protocol.EncryptionLevel)
// EventKind is the kind of handshake event.
type EventKind uint8
const (
// EventNoEvent signals that there are no new handshake events
EventNoEvent EventKind = iota + 1
// EventWriteInitialData contains new CRYPTO data to send at the Initial encryption level
EventWriteInitialData
// EventWriteHandshakeData contains new CRYPTO data to send at the Handshake encryption level
EventWriteHandshakeData
// EventReceivedReadKeys signals that new decryption keys are available.
// It doesn't say which encryption level those keys are for.
EventReceivedReadKeys
// EventDiscard0RTTKeys signals that the Handshake keys were discarded.
EventDiscard0RTTKeys
// EventReceivedTransportParameters contains the transport parameters sent by the peer.
EventReceivedTransportParameters
// EventRestoredTransportParameters contains the transport parameters restored from the session ticket.
// It is only used for the client.
EventRestoredTransportParameters
// EventHandshakeComplete signals that the TLS handshake was completed.
EventHandshakeComplete
)
// Event is a handshake event.
type Event struct {
Kind EventKind
Data []byte
TransportParameters *wire.TransportParameters
}
// CryptoSetup handles the handshake and protecting / unprotecting packets
type CryptoSetup interface {
RunHandshake()
StartHandshake() error
io.Closer
ChangeConnectionID(protocol.ConnectionID)
GetSessionTicket() ([]byte, error)
HandleMessage([]byte, protocol.EncryptionLevel) bool
HandleMessage([]byte, protocol.EncryptionLevel) error
NextEvent() Event
SetLargest1RTTAcked(protocol.PacketNumber) error
DiscardInitialKeys()
SetHandshakeConfirmed()
ConnectionState() ConnectionState

View File

@@ -1,6 +0,0 @@
//go:build gomock || generate
package handshake
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package handshake -destination mock_handshake_runner_test.go github.com/quic-go/quic-go/internal/handshake HandshakeRunner"
type HandshakeRunner = handshakeRunner

View File

@@ -11,13 +11,11 @@ import (
)
var (
retryAEADdraft29 cipher.AEAD // used for QUIC draft versions up to 34
retryAEADv1 cipher.AEAD // used for QUIC v1 (RFC 9000)
retryAEADv2 cipher.AEAD // used for QUIC v2
retryAEADv1 cipher.AEAD // used for QUIC v1 (RFC 9000)
retryAEADv2 cipher.AEAD // used for QUIC v2 (RFC 9369)
)
func init() {
retryAEADdraft29 = initAEAD([16]byte{0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1})
retryAEADv1 = initAEAD([16]byte{0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e})
retryAEADv2 = initAEAD([16]byte{0x8f, 0xb4, 0xb0, 0x1b, 0x56, 0xac, 0x48, 0xe2, 0x60, 0xfb, 0xcb, 0xce, 0xad, 0x7c, 0xcc, 0x92})
}
@@ -35,11 +33,10 @@ func initAEAD(key [16]byte) cipher.AEAD {
}
var (
retryBuf bytes.Buffer
retryMutex sync.Mutex
retryNonceDraft29 = [12]byte{0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c}
retryNonceV1 = [12]byte{0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}
retryNonceV2 = [12]byte{0xd8, 0x69, 0x69, 0xbc, 0x2d, 0x7c, 0x6d, 0x99, 0x90, 0xef, 0xb0, 0x4a}
retryBuf bytes.Buffer
retryMutex sync.Mutex
retryNonceV1 = [12]byte{0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}
retryNonceV2 = [12]byte{0xd8, 0x69, 0x69, 0xbc, 0x2d, 0x7c, 0x6d, 0x99, 0x90, 0xef, 0xb0, 0x4a}
)
// GetRetryIntegrityTag calculates the integrity tag on a Retry packet
@@ -54,14 +51,10 @@ func GetRetryIntegrityTag(retry []byte, origDestConnID protocol.ConnectionID, ve
var tag [16]byte
var sealed []byte
//nolint:exhaustive // These are all the versions we support
switch version {
case protocol.Version1:
sealed = retryAEADv1.Seal(tag[:0], retryNonceV1[:], nil, retryBuf.Bytes())
case protocol.Version2:
if version == protocol.Version2 {
sealed = retryAEADv2.Seal(tag[:0], retryNonceV2[:], nil, retryBuf.Bytes())
default:
sealed = retryAEADdraft29.Seal(tag[:0], retryNonceDraft29[:], nil, retryBuf.Bytes())
} else {
sealed = retryAEADv1.Seal(tag[:0], retryNonceV1[:], nil, retryBuf.Bytes())
}
if len(sealed) != 16 {
panic(fmt.Sprintf("unexpected Retry integrity tag length: %d", len(sealed)))

View File

@@ -10,7 +10,7 @@ import (
"github.com/quic-go/quic-go/quicvarint"
)
const sessionTicketRevision = 2
const sessionTicketRevision = 4
type sessionTicket struct {
Parameters *wire.TransportParameters
@@ -21,10 +21,13 @@ func (t *sessionTicket) Marshal() []byte {
b := make([]byte, 0, 256)
b = quicvarint.Append(b, sessionTicketRevision)
b = quicvarint.Append(b, uint64(t.RTT.Microseconds()))
if t.Parameters == nil {
return b
}
return t.Parameters.MarshalForSessionTicket(b)
}
func (t *sessionTicket) Unmarshal(b []byte) error {
func (t *sessionTicket) Unmarshal(b []byte, using0RTT bool) error {
r := bytes.NewReader(b)
rev, err := quicvarint.Read(r)
if err != nil {
@@ -37,11 +40,15 @@ func (t *sessionTicket) Unmarshal(b []byte) error {
if err != nil {
return errors.New("failed to read RTT")
}
var tp wire.TransportParameters
if err := tp.UnmarshalFromSessionTicket(r); err != nil {
return fmt.Errorf("unmarshaling transport parameters from session ticket failed: %s", err.Error())
if using0RTT {
var tp wire.TransportParameters
if err := tp.UnmarshalFromSessionTicket(r); err != nil {
return fmt.Errorf("unmarshaling transport parameters from session ticket failed: %s", err.Error())
}
t.Parameters = &tp
} else if r.Len() > 0 {
return fmt.Errorf("the session ticket has more bytes than expected")
}
t.Parameters = &tp
t.RTT = time.Duration(rtt) * time.Microsecond
return nil
}

View File

@@ -1,68 +0,0 @@
package handshake
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qtls"
)
const (
quicTLSExtensionTypeOldDrafts = 0xffa5
quicTLSExtensionType = 0x39
)
type extensionHandler struct {
ourParams []byte
paramsChan chan []byte
extensionType uint16
perspective protocol.Perspective
}
var _ tlsExtensionHandler = &extensionHandler{}
// newExtensionHandler creates a new extension handler
func newExtensionHandler(params []byte, pers protocol.Perspective, v protocol.VersionNumber) tlsExtensionHandler {
et := uint16(quicTLSExtensionType)
if v == protocol.VersionDraft29 {
et = quicTLSExtensionTypeOldDrafts
}
return &extensionHandler{
ourParams: params,
paramsChan: make(chan []byte),
perspective: pers,
extensionType: et,
}
}
func (h *extensionHandler) GetExtensions(msgType uint8) []qtls.Extension {
if (h.perspective == protocol.PerspectiveClient && messageType(msgType) != typeClientHello) ||
(h.perspective == protocol.PerspectiveServer && messageType(msgType) != typeEncryptedExtensions) {
return nil
}
return []qtls.Extension{{
Type: h.extensionType,
Data: h.ourParams,
}}
}
func (h *extensionHandler) ReceivedExtensions(msgType uint8, exts []qtls.Extension) {
if (h.perspective == protocol.PerspectiveClient && messageType(msgType) != typeEncryptedExtensions) ||
(h.perspective == protocol.PerspectiveServer && messageType(msgType) != typeClientHello) {
return
}
var data []byte
for _, ext := range exts {
if ext.Type == h.extensionType {
data = ext.Data
break
}
}
h.paramsChan <- data
}
func (h *extensionHandler) TransportParameters() <-chan []byte {
return h.paramsChan
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/asn1"
"fmt"
"io"
"net"
"time"
@@ -45,15 +44,9 @@ type TokenGenerator struct {
tokenProtector tokenProtector
}
// NewTokenGenerator initializes a new TookenGenerator
func NewTokenGenerator(rand io.Reader) (*TokenGenerator, error) {
tokenProtector, err := newTokenProtector(rand)
if err != nil {
return nil, err
}
return &TokenGenerator{
tokenProtector: tokenProtector,
}, nil
// NewTokenGenerator initializes a new TokenGenerator
func NewTokenGenerator(key TokenProtectorKey) *TokenGenerator {
return &TokenGenerator{tokenProtector: newTokenProtector(key)}
}
// NewRetryToken generates a new token for a Retry for a given source address

View File

@@ -3,6 +3,7 @@ package handshake
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
@@ -10,6 +11,9 @@ import (
"golang.org/x/crypto/hkdf"
)
// TokenProtectorKey is the key used to encrypt both Retry and session resumption tokens.
type TokenProtectorKey [32]byte
// TokenProtector is used to create and verify a token
type tokenProtector interface {
// NewToken creates a new token
@@ -18,40 +22,29 @@ type tokenProtector interface {
DecodeToken([]byte) ([]byte, error)
}
const (
tokenSecretSize = 32
tokenNonceSize = 32
)
const tokenNonceSize = 32
// tokenProtector is used to create and verify a token
type tokenProtectorImpl struct {
rand io.Reader
secret []byte
key TokenProtectorKey
}
// newTokenProtector creates a source for source address tokens
func newTokenProtector(rand io.Reader) (tokenProtector, error) {
secret := make([]byte, tokenSecretSize)
if _, err := rand.Read(secret); err != nil {
return nil, err
}
return &tokenProtectorImpl{
rand: rand,
secret: secret,
}, nil
func newTokenProtector(key TokenProtectorKey) tokenProtector {
return &tokenProtectorImpl{key: key}
}
// NewToken encodes data into a new token.
func (s *tokenProtectorImpl) NewToken(data []byte) ([]byte, error) {
nonce := make([]byte, tokenNonceSize)
if _, err := s.rand.Read(nonce); err != nil {
var nonce [tokenNonceSize]byte
if _, err := rand.Read(nonce[:]); err != nil {
return nil, err
}
aead, aeadNonce, err := s.createAEAD(nonce)
aead, aeadNonce, err := s.createAEAD(nonce[:])
if err != nil {
return nil, err
}
return append(nonce, aead.Seal(nil, aeadNonce, data, nil)...), nil
return append(nonce[:], aead.Seal(nil, aeadNonce, data, nil)...), nil
}
// DecodeToken decodes a token.
@@ -68,7 +61,7 @@ func (s *tokenProtectorImpl) DecodeToken(p []byte) ([]byte, error) {
}
func (s *tokenProtectorImpl) createAEAD(nonce []byte) (cipher.AEAD, []byte, error) {
h := hkdf.New(sha256.New, s.secret, nonce, []byte("quic-go token source"))
h := hkdf.New(sha256.New, s.key[:], nonce, []byte("quic-go token source"))
key := make([]byte, 32) // use a 32 byte key, in order to select AES-256
if _, err := io.ReadFull(h, key); err != nil {
return nil, nil, err

View File

@@ -10,7 +10,6 @@ import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/qtls"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/logging"
)
@@ -24,7 +23,7 @@ var KeyUpdateInterval uint64 = protocol.KeyUpdateInterval
var FirstKeyUpdateInterval uint64 = 100
type updatableAEAD struct {
suite *qtls.CipherSuiteTLS13
suite *cipherSuite
keyPhase protocol.KeyPhase
largestAcked protocol.PacketNumber
@@ -58,7 +57,7 @@ type updatableAEAD struct {
rttStats *utils.RTTStats
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
logger utils.Logger
version protocol.VersionNumber
@@ -71,7 +70,7 @@ var (
_ ShortHeaderSealer = &updatableAEAD{}
)
func newUpdatableAEAD(rttStats *utils.RTTStats, tracer logging.ConnectionTracer, logger utils.Logger, version protocol.VersionNumber) *updatableAEAD {
func newUpdatableAEAD(rttStats *utils.RTTStats, tracer *logging.ConnectionTracer, logger utils.Logger, version protocol.VersionNumber) *updatableAEAD {
return &updatableAEAD{
firstPacketNumber: protocol.InvalidPacketNumber,
largestAcked: protocol.InvalidPacketNumber,
@@ -87,7 +86,7 @@ func newUpdatableAEAD(rttStats *utils.RTTStats, tracer logging.ConnectionTracer,
func (a *updatableAEAD) rollKeys() {
if a.prevRcvAEAD != nil {
a.logger.Debugf("Dropping key phase %d ahead of scheduled time. Drop time was: %s", a.keyPhase-1, a.prevRcvAEADExpiry)
if a.tracer != nil {
if a.tracer != nil && a.tracer.DroppedKey != nil {
a.tracer.DroppedKey(a.keyPhase - 1)
}
a.prevRcvAEADExpiry = time.Time{}
@@ -121,7 +120,7 @@ func (a *updatableAEAD) getNextTrafficSecret(hash crypto.Hash, ts []byte) []byte
// SetReadKey sets the read key.
// For the client, this function is called before SetWriteKey.
// For the server, this function is called after SetWriteKey.
func (a *updatableAEAD) SetReadKey(suite *qtls.CipherSuiteTLS13, trafficSecret []byte) {
func (a *updatableAEAD) SetReadKey(suite *cipherSuite, trafficSecret []byte) {
a.rcvAEAD = createAEAD(suite, trafficSecret, a.version)
a.headerDecrypter = newHeaderProtector(suite, trafficSecret, false, a.version)
if a.suite == nil {
@@ -135,7 +134,7 @@ func (a *updatableAEAD) SetReadKey(suite *qtls.CipherSuiteTLS13, trafficSecret [
// SetWriteKey sets the write key.
// For the client, this function is called after SetReadKey.
// For the server, this function is called before SetWriteKey.
func (a *updatableAEAD) SetWriteKey(suite *qtls.CipherSuiteTLS13, trafficSecret []byte) {
func (a *updatableAEAD) SetWriteKey(suite *cipherSuite, trafficSecret []byte) {
a.sendAEAD = createAEAD(suite, trafficSecret, a.version)
a.headerEncrypter = newHeaderProtector(suite, trafficSecret, false, a.version)
if a.suite == nil {
@@ -146,7 +145,7 @@ func (a *updatableAEAD) SetWriteKey(suite *qtls.CipherSuiteTLS13, trafficSecret
a.nextSendAEAD = createAEAD(suite, a.nextSendTrafficSecret, a.version)
}
func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite *qtls.CipherSuiteTLS13) {
func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite *cipherSuite) {
a.nonceBuf = make([]byte, aead.NonceSize())
a.aeadOverhead = aead.Overhead()
a.suite = suite
@@ -183,7 +182,7 @@ func (a *updatableAEAD) open(dst, src []byte, rcvTime time.Time, pn protocol.Pac
a.prevRcvAEAD = nil
a.logger.Debugf("Dropping key phase %d", a.keyPhase-1)
a.prevRcvAEADExpiry = time.Time{}
if a.tracer != nil {
if a.tracer != nil && a.tracer.DroppedKey != nil {
a.tracer.DroppedKey(a.keyPhase - 1)
}
}
@@ -217,7 +216,7 @@ func (a *updatableAEAD) open(dst, src []byte, rcvTime time.Time, pn protocol.Pac
// The peer initiated this key update. It's safe to drop the keys for the previous generation now.
// Start a timer to drop the previous key generation.
a.startKeyDropTimer(rcvTime)
if a.tracer != nil {
if a.tracer != nil && a.tracer.UpdatedKey != nil {
a.tracer.UpdatedKey(a.keyPhase, true)
}
a.firstRcvdWithCurrentKey = pn
@@ -309,7 +308,7 @@ func (a *updatableAEAD) KeyPhase() protocol.KeyPhaseBit {
if a.shouldInitiateKeyUpdate() {
a.rollKeys()
a.logger.Debugf("Initiating key update to key phase %d", a.keyPhase)
if a.tracer != nil {
if a.tracer != nil && a.tracer.UpdatedKey != nil {
a.tracer.UpdatedKey(a.keyPhase, false)
}
}

View File

@@ -5,6 +5,9 @@ import "time"
// DesiredReceiveBufferSize is the kernel UDP receive buffer size that we'd like to use.
const DesiredReceiveBufferSize = (1 << 20) * 2 // 2 MB
// DesiredSendBufferSize is the kernel UDP send buffer size that we'd like to use.
const DesiredSendBufferSize = (1 << 20) * 2 // 2 MB
// InitialPacketSizeIPv4 is the maximum packet size that we use for sending IPv4 packets.
const InitialPacketSizeIPv4 = 1252
@@ -62,9 +65,6 @@ const MaxAcceptQueueSize = 32
// TokenValidity is the duration that a (non-retry) token is considered valid
const TokenValidity = 24 * time.Hour
// RetryTokenValidity is the duration that a retry token is considered valid
const RetryTokenValidity = 10 * time.Second
// MaxOutstandingSentPackets is maximum number of packets saved for retransmission.
// When reached, it imposes a soft limit on sending new packets:
// Sending ACKs and retransmission is still allowed, but now new regular packets can be sent.
@@ -105,9 +105,6 @@ const DefaultIdleTimeout = 30 * time.Second
// DefaultHandshakeIdleTimeout is the default idle timeout used before handshake completion.
const DefaultHandshakeIdleTimeout = 5 * time.Second
// DefaultHandshakeTimeout is the default timeout for a connection until the crypto handshake succeeds.
const DefaultHandshakeTimeout = 10 * time.Second
// MaxKeepAliveInterval is the maximum time until we send a packet to keep a connection alive.
// It should be shorter than the time that NATs clear their mapping.
const MaxKeepAliveInterval = 20 * time.Second
@@ -132,10 +129,6 @@ const MaxPostHandshakeCryptoFrameSize = 1000
// but must ensure that a maximum size ACK frame fits into one packet.
const MaxAckFrameSize ByteCount = 1000
// DefaultMaxDatagramFrameSize is the maximum size of a DATAGRAM frame (RFC 9221).
// The size is chosen such that a DATAGRAM frame fits into a QUIC packet.
const DefaultMaxDatagramFrameSize ByteCount = 1200
// DatagramRcvQueueLen is the length of the receive queue for DATAGRAM frames (RFC 9221)
const DatagramRcvQueueLen = 128

View File

@@ -37,12 +37,61 @@ func (t PacketType) String() string {
type ECN uint8
const (
ECNNon ECN = iota // 00
ECT1 // 01
ECT0 // 10
ECNCE // 11
ECNUnsupported ECN = iota
ECNNon // 00
ECT1 // 01
ECT0 // 10
ECNCE // 11
)
func ParseECNHeaderBits(bits byte) ECN {
switch bits {
case 0:
return ECNNon
case 0b00000010:
return ECT0
case 0b00000001:
return ECT1
case 0b00000011:
return ECNCE
default:
panic("invalid ECN bits")
}
}
func (e ECN) ToHeaderBits() byte {
//nolint:exhaustive // There are only 4 values.
switch e {
case ECNNon:
return 0
case ECT0:
return 0b00000010
case ECT1:
return 0b00000001
case ECNCE:
return 0b00000011
default:
panic("ECN unsupported")
}
}
func (e ECN) String() string {
switch e {
case ECNUnsupported:
return "ECN unsupported"
case ECNNon:
return "Not-ECT"
case ECT1:
return "ECT(1)"
case ECT0:
return "ECT(0)"
case ECNCE:
return "CE"
default:
return fmt.Sprintf("invalid ECN value: %d", e)
}
}
// A ByteCount in QUIC
type ByteCount int64
@@ -59,7 +108,10 @@ type StatelessResetToken [16]byte
// ethernet's max size, minus the IP and UDP headers. IPv6 has a 40 byte header,
// UDP adds an additional 8 bytes. This is a total overhead of 48 bytes.
// Ethernet's max packet size is 1500 bytes, 1500 - 48 = 1452.
const MaxPacketBufferSize ByteCount = 1452
const MaxPacketBufferSize = 1452
// MaxLargePacketBufferSize is used when using GSO
const MaxLargePacketBufferSize = 20 * 1024
// MinInitialPacketSize is the minimum size an Initial packet is required to have.
const MinInitialPacketSize = 1200
@@ -77,6 +129,9 @@ const MinConnectionIDLenInitial = 8
// DefaultAckDelayExponent is the default ack delay exponent
const DefaultAckDelayExponent = 3
// DefaultActiveConnectionIDLimit is the default active connection ID limit
const DefaultActiveConnectionIDLimit = 2
// MaxAckDelayExponent is the maximum ack delay exponent
const MaxAckDelayExponent = 20

View File

@@ -19,14 +19,14 @@ const (
// The version numbers, making grepping easier
const (
VersionUnknown VersionNumber = math.MaxUint32
VersionDraft29 VersionNumber = 0xff00001d
versionDraft29 VersionNumber = 0xff00001d // draft-29 used to be a widely deployed version
Version1 VersionNumber = 0x1
Version2 VersionNumber = 0x6b3343cf
)
// SupportedVersions lists the versions that the server supports
// must be in sorted descending order
var SupportedVersions = []VersionNumber{Version1, Version2, VersionDraft29}
var SupportedVersions = []VersionNumber{Version1, Version2}
// IsValidVersion says if the version is known to quic-go
func IsValidVersion(v VersionNumber) bool {
@@ -38,7 +38,7 @@ func (vn VersionNumber) String() string {
switch vn {
case VersionUnknown:
return "unknown"
case VersionDraft29:
case versionDraft29:
return "draft-29"
case Version1:
return "v1"

View File

@@ -40,7 +40,7 @@ func (e TransportErrorCode) Message() string {
if !e.IsCryptoError() {
return ""
}
return qtls.Alert(e - 0x100).Error()
return qtls.AlertError(e - 0x100).Error()
}
func (e TransportErrorCode) String() string {

View File

@@ -17,15 +17,16 @@ type TransportError struct {
FrameType uint64
ErrorCode TransportErrorCode
ErrorMessage string
error error // only set for local errors, sometimes
}
var _ error = &TransportError{}
// NewLocalCryptoError create a new TransportError instance for a crypto error
func NewLocalCryptoError(tlsAlert uint8, errorMessage string) *TransportError {
func NewLocalCryptoError(tlsAlert uint8, err error) *TransportError {
return &TransportError{
ErrorCode: 0x100 + TransportErrorCode(tlsAlert),
ErrorMessage: errorMessage,
ErrorCode: 0x100 + TransportErrorCode(tlsAlert),
error: err,
}
}
@@ -35,6 +36,9 @@ func (e *TransportError) Error() string {
str += fmt.Sprintf(" (frame type: %#x)", e.FrameType)
}
msg := e.ErrorMessage
if len(msg) == 0 && e.error != nil {
msg = e.error.Error()
}
if len(msg) == 0 {
msg = e.ErrorCode.Message()
}
@@ -48,6 +52,10 @@ func (e *TransportError) Is(target error) bool {
return target == net.ErrClosed
}
func (e *TransportError) Unwrap() error {
return e.error
}
// An ApplicationErrorCode is an application-defined error code.
type ApplicationErrorCode uint64

View File

@@ -0,0 +1,66 @@
//go:build go1.21
package qtls
import (
"crypto"
"crypto/cipher"
"crypto/tls"
"fmt"
"unsafe"
)
type cipherSuiteTLS13 struct {
ID uint16
KeyLen int
AEAD func(key, fixedNonce []byte) cipher.AEAD
Hash crypto.Hash
}
//go:linkname cipherSuiteTLS13ByID crypto/tls.cipherSuiteTLS13ByID
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13
//go:linkname cipherSuitesTLS13 crypto/tls.cipherSuitesTLS13
var cipherSuitesTLS13 []unsafe.Pointer
//go:linkname defaultCipherSuitesTLS13 crypto/tls.defaultCipherSuitesTLS13
var defaultCipherSuitesTLS13 []uint16
//go:linkname defaultCipherSuitesTLS13NoAES crypto/tls.defaultCipherSuitesTLS13NoAES
var defaultCipherSuitesTLS13NoAES []uint16
var cipherSuitesModified bool
// SetCipherSuite modifies the cipherSuiteTLS13 slice of cipher suites inside qtls
// such that it only contains the cipher suite with the chosen id.
// The reset function returned resets them back to the original value.
func SetCipherSuite(id uint16) (reset func()) {
if cipherSuitesModified {
panic("cipher suites modified multiple times without resetting")
}
cipherSuitesModified = true
origCipherSuitesTLS13 := append([]unsafe.Pointer{}, cipherSuitesTLS13...)
origDefaultCipherSuitesTLS13 := append([]uint16{}, defaultCipherSuitesTLS13...)
origDefaultCipherSuitesTLS13NoAES := append([]uint16{}, defaultCipherSuitesTLS13NoAES...)
// The order is given by the order of the slice elements in cipherSuitesTLS13 in qtls.
switch id {
case tls.TLS_AES_128_GCM_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[:1]
case tls.TLS_CHACHA20_POLY1305_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[1:2]
case tls.TLS_AES_256_GCM_SHA384:
cipherSuitesTLS13 = cipherSuitesTLS13[2:]
default:
panic(fmt.Sprintf("unexpected cipher suite: %d", id))
}
defaultCipherSuitesTLS13 = []uint16{id}
defaultCipherSuitesTLS13NoAES = []uint16{id}
return func() {
cipherSuitesTLS13 = origCipherSuitesTLS13
defaultCipherSuitesTLS13 = origDefaultCipherSuitesTLS13
defaultCipherSuitesTLS13NoAES = origDefaultCipherSuitesTLS13NoAES
cipherSuitesModified = false
}
}

View File

@@ -0,0 +1,63 @@
//go:build go1.21
package qtls
import (
"crypto/tls"
)
type clientSessionCache struct {
getData func() []byte
setData func([]byte) (allowEarlyData bool)
wrapped tls.ClientSessionCache
}
var _ tls.ClientSessionCache = &clientSessionCache{}
func (c clientSessionCache) Put(key string, cs *tls.ClientSessionState) {
if cs == nil {
c.wrapped.Put(key, nil)
return
}
ticket, state, err := cs.ResumptionState()
if err != nil || state == nil {
c.wrapped.Put(key, cs)
return
}
state.Extra = append(state.Extra, addExtraPrefix(c.getData()))
newCS, err := tls.NewResumptionState(ticket, state)
if err != nil {
// It's not clear why this would error. Just save the original state.
c.wrapped.Put(key, cs)
return
}
c.wrapped.Put(key, newCS)
}
func (c clientSessionCache) Get(key string) (*tls.ClientSessionState, bool) {
cs, ok := c.wrapped.Get(key)
if !ok || cs == nil {
return cs, ok
}
ticket, state, err := cs.ResumptionState()
if err != nil {
// It's not clear why this would error.
// Remove the ticket from the session cache, so we don't run into this error over and over again
c.wrapped.Put(key, nil)
return nil, false
}
var earlyData bool
// restore QUIC transport parameters and RTT stored in state.Extra
if extra := findExtraData(state.Extra); extra != nil {
earlyData = c.setData(extra)
}
state.EarlyData = earlyData
session, err := tls.NewResumptionState(ticket, state)
if err != nil {
// It's not clear why this would error.
// Remove the ticket from the session cache, so we don't run into this error over and over again
c.wrapped.Put(key, nil)
return nil, false
}
return session, true
}

View File

@@ -1,145 +0,0 @@
//go:build go1.19 && !go1.20
package qtls
import (
"crypto"
"crypto/cipher"
"crypto/tls"
"fmt"
"net"
"unsafe"
"github.com/quic-go/qtls-go1-19"
)
type (
// Alert is a TLS alert
Alert = qtls.Alert
// A Certificate is qtls.Certificate.
Certificate = qtls.Certificate
// CertificateRequestInfo contains information about a certificate request.
CertificateRequestInfo = qtls.CertificateRequestInfo
// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
CipherSuiteTLS13 = qtls.CipherSuiteTLS13
// ClientHelloInfo contains information about a ClientHello.
ClientHelloInfo = qtls.ClientHelloInfo
// ClientSessionCache is a cache used for session resumption.
ClientSessionCache = qtls.ClientSessionCache
// ClientSessionState is a state needed for session resumption.
ClientSessionState = qtls.ClientSessionState
// A Config is a qtls.Config.
Config = qtls.Config
// A Conn is a qtls.Conn.
Conn = qtls.Conn
// ConnectionState contains information about the state of the connection.
ConnectionState = qtls.ConnectionStateWith0RTT
// EncryptionLevel is the encryption level of a message.
EncryptionLevel = qtls.EncryptionLevel
// Extension is a TLS extension
Extension = qtls.Extension
// ExtraConfig is the qtls.ExtraConfig
ExtraConfig = qtls.ExtraConfig
// RecordLayer is a qtls RecordLayer.
RecordLayer = qtls.RecordLayer
)
const (
// EncryptionHandshake is the Handshake encryption level
EncryptionHandshake = qtls.EncryptionHandshake
// Encryption0RTT is the 0-RTT encryption level
Encryption0RTT = qtls.Encryption0RTT
// EncryptionApplication is the application data encryption level
EncryptionApplication = qtls.EncryptionApplication
)
// AEADAESGCMTLS13 creates a new AES-GCM AEAD for TLS 1.3
func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
return qtls.AEADAESGCMTLS13(key, fixedNonce)
}
// Client returns a new TLS client side connection.
func Client(conn net.Conn, config *Config, extraConfig *ExtraConfig) *Conn {
return qtls.Client(conn, config, extraConfig)
}
// Server returns a new TLS server side connection.
func Server(conn net.Conn, config *Config, extraConfig *ExtraConfig) *Conn {
return qtls.Server(conn, config, extraConfig)
}
func GetConnectionState(conn *Conn) ConnectionState {
return conn.ConnectionStateWith0RTT()
}
// ToTLSConnectionState extracts the tls.ConnectionState
func ToTLSConnectionState(cs ConnectionState) tls.ConnectionState {
return cs.ConnectionState
}
type cipherSuiteTLS13 struct {
ID uint16
KeyLen int
AEAD func(key, fixedNonce []byte) cipher.AEAD
Hash crypto.Hash
}
//go:linkname cipherSuiteTLS13ByID github.com/quic-go/qtls-go1-19.cipherSuiteTLS13ByID
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13
// CipherSuiteTLS13ByID gets a TLS 1.3 cipher suite.
func CipherSuiteTLS13ByID(id uint16) *CipherSuiteTLS13 {
val := cipherSuiteTLS13ByID(id)
cs := (*cipherSuiteTLS13)(unsafe.Pointer(val))
return &qtls.CipherSuiteTLS13{
ID: cs.ID,
KeyLen: cs.KeyLen,
AEAD: cs.AEAD,
Hash: cs.Hash,
}
}
//go:linkname cipherSuitesTLS13 github.com/quic-go/qtls-go1-19.cipherSuitesTLS13
var cipherSuitesTLS13 []unsafe.Pointer
//go:linkname defaultCipherSuitesTLS13 github.com/quic-go/qtls-go1-19.defaultCipherSuitesTLS13
var defaultCipherSuitesTLS13 []uint16
//go:linkname defaultCipherSuitesTLS13NoAES github.com/quic-go/qtls-go1-19.defaultCipherSuitesTLS13NoAES
var defaultCipherSuitesTLS13NoAES []uint16
var cipherSuitesModified bool
// SetCipherSuite modifies the cipherSuiteTLS13 slice of cipher suites inside qtls
// such that it only contains the cipher suite with the chosen id.
// The reset function returned resets them back to the original value.
func SetCipherSuite(id uint16) (reset func()) {
if cipherSuitesModified {
panic("cipher suites modified multiple times without resetting")
}
cipherSuitesModified = true
origCipherSuitesTLS13 := append([]unsafe.Pointer{}, cipherSuitesTLS13...)
origDefaultCipherSuitesTLS13 := append([]uint16{}, defaultCipherSuitesTLS13...)
origDefaultCipherSuitesTLS13NoAES := append([]uint16{}, defaultCipherSuitesTLS13NoAES...)
// The order is given by the order of the slice elements in cipherSuitesTLS13 in qtls.
switch id {
case tls.TLS_AES_128_GCM_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[:1]
case tls.TLS_CHACHA20_POLY1305_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[1:2]
case tls.TLS_AES_256_GCM_SHA384:
cipherSuitesTLS13 = cipherSuitesTLS13[2:]
default:
panic(fmt.Sprintf("unexpected cipher suite: %d", id))
}
defaultCipherSuitesTLS13 = []uint16{id}
defaultCipherSuitesTLS13NoAES = []uint16{id}
return func() {
cipherSuitesTLS13 = origCipherSuitesTLS13
defaultCipherSuitesTLS13 = origDefaultCipherSuitesTLS13
defaultCipherSuitesTLS13NoAES = origDefaultCipherSuitesTLS13NoAES
cipherSuitesModified = false
}
}

View File

@@ -1,101 +1,99 @@
//go:build go1.20
//go:build go1.20 && !go1.21
package qtls
import (
"crypto"
"crypto/cipher"
"crypto/tls"
"fmt"
"net"
"unsafe"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/qtls-go1-20"
)
type (
// Alert is a TLS alert
Alert = qtls.Alert
// A Certificate is qtls.Certificate.
Certificate = qtls.Certificate
// CertificateRequestInfo contains information about a certificate request.
CertificateRequestInfo = qtls.CertificateRequestInfo
// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
CipherSuiteTLS13 = qtls.CipherSuiteTLS13
// ClientHelloInfo contains information about a ClientHello.
ClientHelloInfo = qtls.ClientHelloInfo
// ClientSessionCache is a cache used for session resumption.
ClientSessionCache = qtls.ClientSessionCache
// ClientSessionState is a state needed for session resumption.
ClientSessionState = qtls.ClientSessionState
// A Config is a qtls.Config.
Config = qtls.Config
// A Conn is a qtls.Conn.
Conn = qtls.Conn
// ConnectionState contains information about the state of the connection.
ConnectionState = qtls.ConnectionStateWith0RTT
// EncryptionLevel is the encryption level of a message.
EncryptionLevel = qtls.EncryptionLevel
// Extension is a TLS extension
Extension = qtls.Extension
// ExtraConfig is the qtls.ExtraConfig
ExtraConfig = qtls.ExtraConfig
// RecordLayer is a qtls RecordLayer.
RecordLayer = qtls.RecordLayer
QUICConn = qtls.QUICConn
QUICConfig = qtls.QUICConfig
QUICEvent = qtls.QUICEvent
QUICEventKind = qtls.QUICEventKind
QUICEncryptionLevel = qtls.QUICEncryptionLevel
AlertError = qtls.AlertError
)
const (
// EncryptionHandshake is the Handshake encryption level
EncryptionHandshake = qtls.EncryptionHandshake
// Encryption0RTT is the 0-RTT encryption level
Encryption0RTT = qtls.Encryption0RTT
// EncryptionApplication is the application data encryption level
EncryptionApplication = qtls.EncryptionApplication
QUICEncryptionLevelInitial = qtls.QUICEncryptionLevelInitial
QUICEncryptionLevelEarly = qtls.QUICEncryptionLevelEarly
QUICEncryptionLevelHandshake = qtls.QUICEncryptionLevelHandshake
QUICEncryptionLevelApplication = qtls.QUICEncryptionLevelApplication
)
// AEADAESGCMTLS13 creates a new AES-GCM AEAD for TLS 1.3
func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
return qtls.AEADAESGCMTLS13(key, fixedNonce)
const (
QUICNoEvent = qtls.QUICNoEvent
QUICSetReadSecret = qtls.QUICSetReadSecret
QUICSetWriteSecret = qtls.QUICSetWriteSecret
QUICWriteData = qtls.QUICWriteData
QUICTransportParameters = qtls.QUICTransportParameters
QUICTransportParametersRequired = qtls.QUICTransportParametersRequired
QUICRejectedEarlyData = qtls.QUICRejectedEarlyData
QUICHandshakeDone = qtls.QUICHandshakeDone
)
func SetupConfigForServer(conf *QUICConfig, enable0RTT bool, getDataForSessionTicket func() []byte, handleSessionTicket func([]byte, bool) bool) {
qtls.InitSessionTicketKeys(conf.TLSConfig)
conf.TLSConfig = conf.TLSConfig.Clone()
conf.TLSConfig.MinVersion = tls.VersionTLS13
conf.ExtraConfig = &qtls.ExtraConfig{
Enable0RTT: enable0RTT,
Accept0RTT: func(data []byte) bool {
return handleSessionTicket(data, true)
},
GetAppDataForSessionTicket: getDataForSessionTicket,
}
}
// Client returns a new TLS client side connection.
func Client(conn net.Conn, config *Config, extraConfig *ExtraConfig) *Conn {
return qtls.Client(conn, config, extraConfig)
func SetupConfigForClient(conf *QUICConfig, getDataForSessionState func() []byte, setDataFromSessionState func([]byte) bool) {
conf.ExtraConfig = &qtls.ExtraConfig{
GetAppDataForSessionState: getDataForSessionState,
SetAppDataFromSessionState: setDataFromSessionState,
}
}
// Server returns a new TLS server side connection.
func Server(conn net.Conn, config *Config, extraConfig *ExtraConfig) *Conn {
return qtls.Server(conn, config, extraConfig)
func QUICServer(config *QUICConfig) *QUICConn {
return qtls.QUICServer(config)
}
func GetConnectionState(conn *Conn) ConnectionState {
return conn.ConnectionStateWith0RTT()
func QUICClient(config *QUICConfig) *QUICConn {
return qtls.QUICClient(config)
}
// ToTLSConnectionState extracts the tls.ConnectionState
func ToTLSConnectionState(cs ConnectionState) tls.ConnectionState {
return cs.ConnectionState
func ToTLSEncryptionLevel(e protocol.EncryptionLevel) qtls.QUICEncryptionLevel {
switch e {
case protocol.EncryptionInitial:
return qtls.QUICEncryptionLevelInitial
case protocol.EncryptionHandshake:
return qtls.QUICEncryptionLevelHandshake
case protocol.Encryption1RTT:
return qtls.QUICEncryptionLevelApplication
case protocol.Encryption0RTT:
return qtls.QUICEncryptionLevelEarly
default:
panic(fmt.Sprintf("unexpected encryption level: %s", e))
}
}
type cipherSuiteTLS13 struct {
ID uint16
KeyLen int
AEAD func(key, fixedNonce []byte) cipher.AEAD
Hash crypto.Hash
}
//go:linkname cipherSuiteTLS13ByID github.com/quic-go/qtls-go1-20.cipherSuiteTLS13ByID
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13
// CipherSuiteTLS13ByID gets a TLS 1.3 cipher suite.
func CipherSuiteTLS13ByID(id uint16) *CipherSuiteTLS13 {
val := cipherSuiteTLS13ByID(id)
cs := (*cipherSuiteTLS13)(unsafe.Pointer(val))
return &qtls.CipherSuiteTLS13{
ID: cs.ID,
KeyLen: cs.KeyLen,
AEAD: cs.AEAD,
Hash: cs.Hash,
func FromTLSEncryptionLevel(e qtls.QUICEncryptionLevel) protocol.EncryptionLevel {
switch e {
case qtls.QUICEncryptionLevelInitial:
return protocol.EncryptionInitial
case qtls.QUICEncryptionLevelHandshake:
return protocol.EncryptionHandshake
case qtls.QUICEncryptionLevelApplication:
return protocol.Encryption1RTT
case qtls.QUICEncryptionLevelEarly:
return protocol.Encryption0RTT
default:
panic(fmt.Sprintf("unexpect encryption level: %s", e))
}
}
@@ -143,3 +141,7 @@ func SetCipherSuite(id uint16) (reset func()) {
cipherSuitesModified = false
}
}
func SendSessionTicket(c *QUICConn, allow0RTT bool) error {
return c.SendSessionTicket(allow0RTT)
}

View File

@@ -2,4 +2,158 @@
package qtls
var _ int = "The version of quic-go you're using can't be built on Go 1.21 yet. For more details, please see https://github.com/quic-go/quic-go/wiki/quic-go-and-Go-versions."
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
)
type (
QUICConn = tls.QUICConn
QUICConfig = tls.QUICConfig
QUICEvent = tls.QUICEvent
QUICEventKind = tls.QUICEventKind
QUICEncryptionLevel = tls.QUICEncryptionLevel
QUICSessionTicketOptions = tls.QUICSessionTicketOptions
AlertError = tls.AlertError
)
const (
QUICEncryptionLevelInitial = tls.QUICEncryptionLevelInitial
QUICEncryptionLevelEarly = tls.QUICEncryptionLevelEarly
QUICEncryptionLevelHandshake = tls.QUICEncryptionLevelHandshake
QUICEncryptionLevelApplication = tls.QUICEncryptionLevelApplication
)
const (
QUICNoEvent = tls.QUICNoEvent
QUICSetReadSecret = tls.QUICSetReadSecret
QUICSetWriteSecret = tls.QUICSetWriteSecret
QUICWriteData = tls.QUICWriteData
QUICTransportParameters = tls.QUICTransportParameters
QUICTransportParametersRequired = tls.QUICTransportParametersRequired
QUICRejectedEarlyData = tls.QUICRejectedEarlyData
QUICHandshakeDone = tls.QUICHandshakeDone
)
func QUICServer(config *QUICConfig) *QUICConn { return tls.QUICServer(config) }
func QUICClient(config *QUICConfig) *QUICConn { return tls.QUICClient(config) }
func SetupConfigForServer(qconf *QUICConfig, _ bool, getData func() []byte, handleSessionTicket func([]byte, bool) bool) {
conf := qconf.TLSConfig
// Workaround for https://github.com/golang/go/issues/60506.
// This initializes the session tickets _before_ cloning the config.
_, _ = conf.DecryptTicket(nil, tls.ConnectionState{})
conf = conf.Clone()
conf.MinVersion = tls.VersionTLS13
qconf.TLSConfig = conf
// add callbacks to save transport parameters into the session ticket
origWrapSession := conf.WrapSession
conf.WrapSession = func(cs tls.ConnectionState, state *tls.SessionState) ([]byte, error) {
// Add QUIC session ticket
state.Extra = append(state.Extra, addExtraPrefix(getData()))
if origWrapSession != nil {
return origWrapSession(cs, state)
}
b, err := conf.EncryptTicket(cs, state)
return b, err
}
origUnwrapSession := conf.UnwrapSession
// UnwrapSession might be called multiple times, as the client can use multiple session tickets.
// However, using 0-RTT is only possible with the first session ticket.
// crypto/tls guarantees that this callback is called in the same order as the session ticket in the ClientHello.
var unwrapCount int
conf.UnwrapSession = func(identity []byte, connState tls.ConnectionState) (*tls.SessionState, error) {
unwrapCount++
var state *tls.SessionState
var err error
if origUnwrapSession != nil {
state, err = origUnwrapSession(identity, connState)
} else {
state, err = conf.DecryptTicket(identity, connState)
}
if err != nil || state == nil {
return nil, err
}
extra := findExtraData(state.Extra)
if extra != nil {
state.EarlyData = handleSessionTicket(extra, state.EarlyData && unwrapCount == 1)
} else {
state.EarlyData = false
}
return state, nil
}
}
func SetupConfigForClient(qconf *QUICConfig, getData func() []byte, setData func([]byte) bool) {
conf := qconf.TLSConfig
if conf.ClientSessionCache != nil {
origCache := conf.ClientSessionCache
conf.ClientSessionCache = &clientSessionCache{
wrapped: origCache,
getData: getData,
setData: setData,
}
}
}
func ToTLSEncryptionLevel(e protocol.EncryptionLevel) tls.QUICEncryptionLevel {
switch e {
case protocol.EncryptionInitial:
return tls.QUICEncryptionLevelInitial
case protocol.EncryptionHandshake:
return tls.QUICEncryptionLevelHandshake
case protocol.Encryption1RTT:
return tls.QUICEncryptionLevelApplication
case protocol.Encryption0RTT:
return tls.QUICEncryptionLevelEarly
default:
panic(fmt.Sprintf("unexpected encryption level: %s", e))
}
}
func FromTLSEncryptionLevel(e tls.QUICEncryptionLevel) protocol.EncryptionLevel {
switch e {
case tls.QUICEncryptionLevelInitial:
return protocol.EncryptionInitial
case tls.QUICEncryptionLevelHandshake:
return protocol.EncryptionHandshake
case tls.QUICEncryptionLevelApplication:
return protocol.Encryption1RTT
case tls.QUICEncryptionLevelEarly:
return protocol.Encryption0RTT
default:
panic(fmt.Sprintf("unexpect encryption level: %s", e))
}
}
const extraPrefix = "quic-go1"
func addExtraPrefix(b []byte) []byte {
return append([]byte(extraPrefix), b...)
}
func findExtraData(extras [][]byte) []byte {
prefix := []byte(extraPrefix)
for _, extra := range extras {
if len(extra) < len(prefix) || !bytes.Equal(prefix, extra[:len(prefix)]) {
continue
}
return extra[len(prefix):]
}
return nil
}
func SendSessionTicket(c *QUICConn, allow0RTT bool) error {
return c.SendSessionTicket(tls.QUICSessionTicketOptions{
EarlyData: allow0RTT,
})
}

View File

@@ -1,4 +1,4 @@
//go:build !go1.19
//go:build !go1.20
package qtls

View File

@@ -0,0 +1,86 @@
package ringbuffer
// A RingBuffer is a ring buffer.
// It acts as a heap that doesn't cause any allocations.
type RingBuffer[T any] struct {
ring []T
headPos, tailPos int
full bool
}
// Init preallocs a buffer with a certain size.
func (r *RingBuffer[T]) Init(size int) {
r.ring = make([]T, size)
}
// Len returns the number of elements in the ring buffer.
func (r *RingBuffer[T]) Len() int {
if r.full {
return len(r.ring)
}
if r.tailPos >= r.headPos {
return r.tailPos - r.headPos
}
return r.tailPos - r.headPos + len(r.ring)
}
// Empty says if the ring buffer is empty.
func (r *RingBuffer[T]) Empty() bool {
return !r.full && r.headPos == r.tailPos
}
// PushBack adds a new element.
// If the ring buffer is full, its capacity is increased first.
func (r *RingBuffer[T]) PushBack(t T) {
if r.full || len(r.ring) == 0 {
r.grow()
}
r.ring[r.tailPos] = t
r.tailPos++
if r.tailPos == len(r.ring) {
r.tailPos = 0
}
if r.tailPos == r.headPos {
r.full = true
}
}
// PopFront returns the next element.
// It must not be called when the buffer is empty, that means that
// callers might need to check if there are elements in the buffer first.
func (r *RingBuffer[T]) PopFront() T {
if r.Empty() {
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue")
}
r.full = false
t := r.ring[r.headPos]
r.ring[r.headPos] = *new(T)
r.headPos++
if r.headPos == len(r.ring) {
r.headPos = 0
}
return t
}
// Grow the maximum size of the queue.
// This method assume the queue is full.
func (r *RingBuffer[T]) grow() {
oldRing := r.ring
newSize := len(oldRing) * 2
if newSize == 0 {
newSize = 1
}
r.ring = make([]T, newSize)
headLen := copy(r.ring, oldRing[r.headPos:])
copy(r.ring[headLen:], oldRing[:r.headPos])
r.headPos, r.tailPos, r.full = 0, len(oldRing), false
}
// Clear removes all elements.
func (r *RingBuffer[T]) Clear() {
var zeroValue T
for i := range r.ring {
r.ring[i] = zeroValue
}
r.headPos, r.tailPos, r.full = 0, 0, false
}

View File

@@ -103,8 +103,12 @@ func (r *RTTStats) SetMaxAckDelay(mad time.Duration) {
// SetInitialRTT sets the initial RTT.
// It is used during the 0-RTT handshake when restoring the RTT stats from the session state.
func (r *RTTStats) SetInitialRTT(t time.Duration) {
// On the server side, by the time we get to process the session ticket,
// we might already have obtained an RTT measurement.
// This can happen if we received the ClientHello in multiple pieces, and one of those pieces was lost.
// Discard the restored value. A fresh measurement is always better.
if r.hasMeasurement {
panic("initial RTT set after first measurement")
return
}
r.smoothedRTT = t
r.latestRTT = t

View File

@@ -22,19 +22,17 @@ type AckFrame struct {
}
// parseAckFrame reads an ACK frame
func parseAckFrame(r *bytes.Reader, typ uint64, ackDelayExponent uint8, _ protocol.VersionNumber) (*AckFrame, error) {
func parseAckFrame(frame *AckFrame, r *bytes.Reader, typ uint64, ackDelayExponent uint8, _ protocol.VersionNumber) error {
ecn := typ == ackECNFrameType
frame := GetAckFrame()
la, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
largestAcked := protocol.PacketNumber(la)
delay, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
delayTime := time.Duration(delay*1<<ackDelayExponent) * time.Microsecond
@@ -46,17 +44,17 @@ func parseAckFrame(r *bytes.Reader, typ uint64, ackDelayExponent uint8, _ protoc
numBlocks, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
// read the first ACK range
ab, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
ackBlock := protocol.PacketNumber(ab)
if ackBlock > largestAcked {
return nil, errors.New("invalid first ACK range")
return errors.New("invalid first ACK range")
}
smallest := largestAcked - ackBlock
@@ -65,41 +63,50 @@ func parseAckFrame(r *bytes.Reader, typ uint64, ackDelayExponent uint8, _ protoc
for i := uint64(0); i < numBlocks; i++ {
g, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
gap := protocol.PacketNumber(g)
if smallest < gap+2 {
return nil, errInvalidAckRanges
return errInvalidAckRanges
}
largest := smallest - gap - 2
ab, err := quicvarint.Read(r)
if err != nil {
return nil, err
return err
}
ackBlock := protocol.PacketNumber(ab)
if ackBlock > largest {
return nil, errInvalidAckRanges
return errInvalidAckRanges
}
smallest = largest - ackBlock
frame.AckRanges = append(frame.AckRanges, AckRange{Smallest: smallest, Largest: largest})
}
if !frame.validateAckRanges() {
return nil, errInvalidAckRanges
return errInvalidAckRanges
}
// parse (and skip) the ECN section
if ecn {
for i := 0; i < 3; i++ {
if _, err := quicvarint.Read(r); err != nil {
return nil, err
}
ect0, err := quicvarint.Read(r)
if err != nil {
return err
}
frame.ECT0 = ect0
ect1, err := quicvarint.Read(r)
if err != nil {
return err
}
frame.ECT1 = ect1
ecnce, err := quicvarint.Read(r)
if err != nil {
return err
}
frame.ECNCE = ecnce
}
return frame, nil
return nil
}
// Append appends an ACK frame.
@@ -242,6 +249,18 @@ func (f *AckFrame) AcksPacket(p protocol.PacketNumber) bool {
return p <= f.AckRanges[i].Largest
}
func (f *AckFrame) Reset() {
f.DelayTime = 0
f.ECT0 = 0
f.ECT1 = 0
f.ECNCE = 0
for _, r := range f.AckRanges {
r.Largest = 0
r.Smallest = 0
}
f.AckRanges = f.AckRanges[:0]
}
func encodeAckDelay(delay time.Duration) uint64 {
return uint64(delay.Nanoseconds() / (1000 * (1 << protocol.AckDelayExponent)))
}

View File

@@ -1,24 +0,0 @@
package wire
import "sync"
var ackFramePool = sync.Pool{New: func() any {
return &AckFrame{}
}}
func GetAckFrame() *AckFrame {
f := ackFramePool.Get().(*AckFrame)
f.AckRanges = f.AckRanges[:0]
f.ECNCE = 0
f.ECT0 = 0
f.ECT1 = 0
f.DelayTime = 0
return f
}
func PutAckFrame(f *AckFrame) {
if cap(f.AckRanges) > 4 {
return
}
ackFramePool.Put(f)
}

View File

@@ -8,6 +8,12 @@ import (
"github.com/quic-go/quic-go/quicvarint"
)
// MaxDatagramSize is the maximum size of a DATAGRAM frame (RFC 9221).
// By setting it to a large value, we allow all datagrams that fit into a QUIC packet.
// The value is chosen such that it can still be encoded as a 2 byte varint.
// This is a var and not a const so it can be set in tests.
var MaxDatagramSize protocol.ByteCount = 16383
// A DatagramFrame is a DATAGRAM frame
type DatagramFrame struct {
DataLenPresent bool

View File

@@ -39,9 +39,12 @@ const (
type frameParser struct {
r bytes.Reader // cached bytes.Reader, so we don't have to repeatedly allocate them
ackDelayExponent uint8
ackDelayExponent uint8
supportsDatagrams bool
// To avoid allocating when parsing, keep a single ACK frame struct.
// It is used over and over again.
ackFrame *AckFrame
}
var _ FrameParser = &frameParser{}
@@ -51,6 +54,7 @@ func NewFrameParser(supportsDatagrams bool) *frameParser {
return &frameParser{
r: *bytes.NewReader(nil),
supportsDatagrams: supportsDatagrams,
ackFrame: &AckFrame{},
}
}
@@ -105,7 +109,9 @@ func (p *frameParser) parseFrame(r *bytes.Reader, typ uint64, encLevel protocol.
if encLevel != protocol.Encryption1RTT {
ackDelayExponent = protocol.DefaultAckDelayExponent
}
frame, err = parseAckFrame(r, typ, ackDelayExponent, v)
p.ackFrame.Reset()
err = parseAckFrame(p.ackFrame, r, typ, ackDelayExponent, v)
frame = p.ackFrame
case resetStreamFrameType:
frame, err = parseResetStreamFrame(r, v)
case stopSendingFrameType:

View File

@@ -13,8 +13,6 @@ import (
)
// ParseConnectionID parses the destination connection ID of a packet.
// It uses the data slice for the connection ID.
// That means that the connection ID must not be used after the packet buffer is released.
func ParseConnectionID(data []byte, shortHeaderConnIDLen int) (protocol.ConnectionID, error) {
if len(data) == 0 {
return protocol.ConnectionID{}, io.EOF
@@ -76,6 +74,10 @@ func parseArbitraryLenConnectionIDs(r *bytes.Reader) (dest, src protocol.Arbitra
return destConnID, srcConnID, nil
}
func IsPotentialQUICPacket(firstByte byte) bool {
return firstByte&0x40 > 0
}
// IsLongHeaderPacket says if this is a Long Header packet
func IsLongHeaderPacket(firstByte byte) bool {
return firstByte&0x80 > 0
@@ -110,7 +112,7 @@ func Is0RTTPacket(b []byte) bool {
version := protocol.VersionNumber(binary.BigEndian.Uint32(b[1:5]))
//nolint:exhaustive // We only need to test QUIC versions that we support.
switch version {
case protocol.Version1, protocol.VersionDraft29:
case protocol.Version1:
return b[0]>>4&0b11 == 0b01
case protocol.Version2:
return b[0]>>4&0b11 == 0b10

View File

@@ -2,6 +2,7 @@ package wire
import (
"bytes"
"errors"
"fmt"
"io"
@@ -34,6 +35,9 @@ func parseNewConnectionIDFrame(r *bytes.Reader, _ protocol.VersionNumber) (*NewC
if err != nil {
return nil, err
}
if connIDLen == 0 {
return nil, errors.New("invalid zero-length connection ID")
}
connID, err := protocol.ReadConnectionID(r, int(connIDLen))
if err != nil {
return nil, err

View File

@@ -2,14 +2,13 @@ package wire
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"math/rand"
"net"
"sort"
"sync"
"time"
"github.com/quic-go/quic-go/internal/protocol"
@@ -26,15 +25,6 @@ var AdditionalTransportParametersClient map[uint64][]byte
const transportParameterMarshalingVersion = 1
var (
randomMutex sync.Mutex
random rand.Rand
)
func init() {
random = *rand.New(rand.NewSource(time.Now().UnixNano()))
}
type transportParameterID uint64
const (
@@ -118,6 +108,7 @@ func (p *TransportParameters) unmarshal(r *bytes.Reader, sentBy protocol.Perspec
var (
readOriginalDestinationConnectionID bool
readInitialSourceConnectionID bool
readActiveConnectionIDLimit bool
)
p.AckDelayExponent = protocol.DefaultAckDelayExponent
@@ -139,6 +130,9 @@ func (p *TransportParameters) unmarshal(r *bytes.Reader, sentBy protocol.Perspec
}
parameterIDs = append(parameterIDs, paramID)
switch paramID {
case activeConnectionIDLimitParameterID:
readActiveConnectionIDLimit = true
fallthrough
case maxIdleTimeoutParameterID,
maxUDPPayloadSizeParameterID,
initialMaxDataParameterID,
@@ -148,7 +142,6 @@ func (p *TransportParameters) unmarshal(r *bytes.Reader, sentBy protocol.Perspec
initialMaxStreamsBidiParameterID,
initialMaxStreamsUniParameterID,
maxAckDelayParameterID,
activeConnectionIDLimitParameterID,
maxDatagramFrameSizeParameterID,
ackDelayExponentParameterID:
if err := p.readNumericTransportParameter(r, paramID, int(paramLen)); err != nil {
@@ -196,6 +189,9 @@ func (p *TransportParameters) unmarshal(r *bytes.Reader, sentBy protocol.Perspec
}
}
if !readActiveConnectionIDLimit {
p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
}
if !fromSessionTicket {
if sentBy == protocol.PerspectiveServer && !readOriginalDestinationConnectionID {
return errors.New("missing original_destination_connection_id")
@@ -335,13 +331,12 @@ func (p *TransportParameters) Marshal(pers protocol.Perspective) []byte {
b := make([]byte, 0, 256)
// add a greased value
b = quicvarint.Append(b, uint64(27+31*rand.Intn(100)))
randomMutex.Lock()
length := random.Intn(16)
random := make([]byte, 18)
rand.Read(random)
b = quicvarint.Append(b, 27+31*uint64(random[0]))
length := random[1] % 16
b = quicvarint.Append(b, uint64(length))
b = b[:len(b)+length]
random.Read(b[len(b)-length:])
randomMutex.Unlock()
b = append(b, random[2:2+length]...)
// initial_max_stream_data_bidi_local
b = p.marshalVarintParam(b, initialMaxStreamDataBidiLocalParameterID, uint64(p.InitialMaxStreamDataBidiLocal))
@@ -402,7 +397,9 @@ func (p *TransportParameters) Marshal(pers protocol.Perspective) []byte {
}
}
// active_connection_id_limit
b = p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
if p.ActiveConnectionIDLimit != protocol.DefaultActiveConnectionIDLimit {
b = p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
}
// initial_source_connection_id
b = quicvarint.Append(b, uint64(initialSourceConnectionIDParameterID))
b = quicvarint.Append(b, uint64(p.InitialSourceConnectionID.Len()))
@@ -457,6 +454,10 @@ func (p *TransportParameters) MarshalForSessionTicket(b []byte) []byte {
b = p.marshalVarintParam(b, initialMaxStreamsBidiParameterID, uint64(p.MaxBidiStreamNum))
// initial_max_uni_streams
b = p.marshalVarintParam(b, initialMaxStreamsUniParameterID, uint64(p.MaxUniStreamNum))
// max_datagram_frame_size
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
b = p.marshalVarintParam(b, maxDatagramFrameSizeParameterID, uint64(p.MaxDatagramFrameSize))
}
// active_connection_id_limit
return p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
}
@@ -475,6 +476,9 @@ func (p *TransportParameters) UnmarshalFromSessionTicket(r *bytes.Reader) error
// ValidFor0RTT checks if the transport parameters match those saved in the session ticket.
func (p *TransportParameters) ValidFor0RTT(saved *TransportParameters) bool {
if saved.MaxDatagramFrameSize != protocol.InvalidByteCount && (p.MaxDatagramFrameSize == protocol.InvalidByteCount || p.MaxDatagramFrameSize < saved.MaxDatagramFrameSize) {
return false
}
return p.InitialMaxStreamDataBidiLocal >= saved.InitialMaxStreamDataBidiLocal &&
p.InitialMaxStreamDataBidiRemote >= saved.InitialMaxStreamDataBidiRemote &&
p.InitialMaxStreamDataUni >= saved.InitialMaxStreamDataUni &&
@@ -484,6 +488,21 @@ func (p *TransportParameters) ValidFor0RTT(saved *TransportParameters) bool {
p.ActiveConnectionIDLimit == saved.ActiveConnectionIDLimit
}
// ValidForUpdate checks that the new transport parameters don't reduce limits after resuming a 0-RTT connection.
// It is only used on the client side.
func (p *TransportParameters) ValidForUpdate(saved *TransportParameters) bool {
if saved.MaxDatagramFrameSize != protocol.InvalidByteCount && (p.MaxDatagramFrameSize == protocol.InvalidByteCount || p.MaxDatagramFrameSize < saved.MaxDatagramFrameSize) {
return false
}
return p.ActiveConnectionIDLimit >= saved.ActiveConnectionIDLimit &&
p.InitialMaxData >= saved.InitialMaxData &&
p.InitialMaxStreamDataBidiLocal >= saved.InitialMaxStreamDataBidiLocal &&
p.InitialMaxStreamDataBidiRemote >= saved.InitialMaxStreamDataBidiRemote &&
p.InitialMaxStreamDataUni >= saved.InitialMaxStreamDataUni &&
p.MaxBidiStreamNum >= saved.MaxBidiStreamNum &&
p.MaxUniStreamNum >= saved.MaxUniStreamNum
}
// String returns a string representation, intended for logging.
func (p *TransportParameters) String() string {
logString := "&wire.TransportParameters{OriginalDestinationConnectionID: %s, InitialSourceConnectionID: %s, "

View File

@@ -40,7 +40,10 @@ func ComposeVersionNegotiation(destConnID, srcConnID protocol.ArbitraryLenConnec
buf := bytes.NewBuffer(make([]byte, 0, expectedLen))
r := make([]byte, 1)
_, _ = rand.Read(r) // ignore the error here. It is not critical to have perfect random here.
buf.WriteByte(r[0] | 0x80)
// Setting the "QUIC bit" (0x40) is not required by the RFC,
// but it allows clients to demultiplex QUIC with a long list of other protocols.
// See RFC 9443 and https://mailarchive.ietf.org/arch/msg/quic/oR4kxGKY6mjtPC1CZegY1ED4beg/ for details.
buf.WriteByte(r[0] | 0xc0)
utils.BigEndian.WriteUint32(buf, 0) // version 0
buf.WriteByte(uint8(destConnID.Len()))
buf.Write(destConnID.Bytes())

View File

@@ -0,0 +1,255 @@
package logging
import (
"net"
"time"
)
// A ConnectionTracer records events.
type ConnectionTracer struct {
StartedConnection func(local, remote net.Addr, srcConnID, destConnID ConnectionID)
NegotiatedVersion func(chosen VersionNumber, clientVersions, serverVersions []VersionNumber)
ClosedConnection func(error)
SentTransportParameters func(*TransportParameters)
ReceivedTransportParameters func(*TransportParameters)
RestoredTransportParameters func(parameters *TransportParameters) // for 0-RTT
SentLongHeaderPacket func(*ExtendedHeader, ByteCount, ECN, *AckFrame, []Frame)
SentShortHeaderPacket func(*ShortHeader, ByteCount, ECN, *AckFrame, []Frame)
ReceivedVersionNegotiationPacket func(dest, src ArbitraryLenConnectionID, _ []VersionNumber)
ReceivedRetry func(*Header)
ReceivedLongHeaderPacket func(*ExtendedHeader, ByteCount, ECN, []Frame)
ReceivedShortHeaderPacket func(*ShortHeader, ByteCount, ECN, []Frame)
BufferedPacket func(PacketType, ByteCount)
DroppedPacket func(PacketType, PacketNumber, ByteCount, PacketDropReason)
UpdatedMetrics func(rttStats *RTTStats, cwnd, bytesInFlight ByteCount, packetsInFlight int)
AcknowledgedPacket func(EncryptionLevel, PacketNumber)
LostPacket func(EncryptionLevel, PacketNumber, PacketLossReason)
UpdatedCongestionState func(CongestionState)
UpdatedPTOCount func(value uint32)
UpdatedKeyFromTLS func(EncryptionLevel, Perspective)
UpdatedKey func(generation KeyPhase, remote bool)
DroppedEncryptionLevel func(EncryptionLevel)
DroppedKey func(generation KeyPhase)
SetLossTimer func(TimerType, EncryptionLevel, time.Time)
LossTimerExpired func(TimerType, EncryptionLevel)
LossTimerCanceled func()
ECNStateUpdated func(state ECNState, trigger ECNStateTrigger)
// Close is called when the connection is closed.
Close func()
Debug func(name, msg string)
}
// NewMultiplexedConnectionTracer creates a new connection tracer that multiplexes events to multiple tracers.
func NewMultiplexedConnectionTracer(tracers ...*ConnectionTracer) *ConnectionTracer {
if len(tracers) == 0 {
return nil
}
if len(tracers) == 1 {
return tracers[0]
}
return &ConnectionTracer{
StartedConnection: func(local, remote net.Addr, srcConnID, destConnID ConnectionID) {
for _, t := range tracers {
if t.StartedConnection != nil {
t.StartedConnection(local, remote, srcConnID, destConnID)
}
}
},
NegotiatedVersion: func(chosen VersionNumber, clientVersions, serverVersions []VersionNumber) {
for _, t := range tracers {
if t.NegotiatedVersion != nil {
t.NegotiatedVersion(chosen, clientVersions, serverVersions)
}
}
},
ClosedConnection: func(e error) {
for _, t := range tracers {
if t.ClosedConnection != nil {
t.ClosedConnection(e)
}
}
},
SentTransportParameters: func(tp *TransportParameters) {
for _, t := range tracers {
if t.SentTransportParameters != nil {
t.SentTransportParameters(tp)
}
}
},
ReceivedTransportParameters: func(tp *TransportParameters) {
for _, t := range tracers {
if t.ReceivedTransportParameters != nil {
t.ReceivedTransportParameters(tp)
}
}
},
RestoredTransportParameters: func(tp *TransportParameters) {
for _, t := range tracers {
if t.RestoredTransportParameters != nil {
t.RestoredTransportParameters(tp)
}
}
},
SentLongHeaderPacket: func(hdr *ExtendedHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) {
for _, t := range tracers {
if t.SentLongHeaderPacket != nil {
t.SentLongHeaderPacket(hdr, size, ecn, ack, frames)
}
}
},
SentShortHeaderPacket: func(hdr *ShortHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) {
for _, t := range tracers {
if t.SentShortHeaderPacket != nil {
t.SentShortHeaderPacket(hdr, size, ecn, ack, frames)
}
}
},
ReceivedVersionNegotiationPacket: func(dest, src ArbitraryLenConnectionID, versions []VersionNumber) {
for _, t := range tracers {
if t.ReceivedVersionNegotiationPacket != nil {
t.ReceivedVersionNegotiationPacket(dest, src, versions)
}
}
},
ReceivedRetry: func(hdr *Header) {
for _, t := range tracers {
if t.ReceivedRetry != nil {
t.ReceivedRetry(hdr)
}
}
},
ReceivedLongHeaderPacket: func(hdr *ExtendedHeader, size ByteCount, ecn ECN, frames []Frame) {
for _, t := range tracers {
if t.ReceivedLongHeaderPacket != nil {
t.ReceivedLongHeaderPacket(hdr, size, ecn, frames)
}
}
},
ReceivedShortHeaderPacket: func(hdr *ShortHeader, size ByteCount, ecn ECN, frames []Frame) {
for _, t := range tracers {
if t.ReceivedShortHeaderPacket != nil {
t.ReceivedShortHeaderPacket(hdr, size, ecn, frames)
}
}
},
BufferedPacket: func(typ PacketType, size ByteCount) {
for _, t := range tracers {
if t.BufferedPacket != nil {
t.BufferedPacket(typ, size)
}
}
},
DroppedPacket: func(typ PacketType, pn PacketNumber, size ByteCount, reason PacketDropReason) {
for _, t := range tracers {
if t.DroppedPacket != nil {
t.DroppedPacket(typ, pn, size, reason)
}
}
},
UpdatedMetrics: func(rttStats *RTTStats, cwnd, bytesInFlight ByteCount, packetsInFlight int) {
for _, t := range tracers {
if t.UpdatedMetrics != nil {
t.UpdatedMetrics(rttStats, cwnd, bytesInFlight, packetsInFlight)
}
}
},
AcknowledgedPacket: func(encLevel EncryptionLevel, pn PacketNumber) {
for _, t := range tracers {
if t.AcknowledgedPacket != nil {
t.AcknowledgedPacket(encLevel, pn)
}
}
},
LostPacket: func(encLevel EncryptionLevel, pn PacketNumber, reason PacketLossReason) {
for _, t := range tracers {
if t.LostPacket != nil {
t.LostPacket(encLevel, pn, reason)
}
}
},
UpdatedCongestionState: func(state CongestionState) {
for _, t := range tracers {
if t.UpdatedCongestionState != nil {
t.UpdatedCongestionState(state)
}
}
},
UpdatedPTOCount: func(value uint32) {
for _, t := range tracers {
if t.UpdatedPTOCount != nil {
t.UpdatedPTOCount(value)
}
}
},
UpdatedKeyFromTLS: func(encLevel EncryptionLevel, perspective Perspective) {
for _, t := range tracers {
if t.UpdatedKeyFromTLS != nil {
t.UpdatedKeyFromTLS(encLevel, perspective)
}
}
},
UpdatedKey: func(generation KeyPhase, remote bool) {
for _, t := range tracers {
if t.UpdatedKey != nil {
t.UpdatedKey(generation, remote)
}
}
},
DroppedEncryptionLevel: func(encLevel EncryptionLevel) {
for _, t := range tracers {
if t.DroppedEncryptionLevel != nil {
t.DroppedEncryptionLevel(encLevel)
}
}
},
DroppedKey: func(generation KeyPhase) {
for _, t := range tracers {
if t.DroppedKey != nil {
t.DroppedKey(generation)
}
}
},
SetLossTimer: func(typ TimerType, encLevel EncryptionLevel, exp time.Time) {
for _, t := range tracers {
if t.SetLossTimer != nil {
t.SetLossTimer(typ, encLevel, exp)
}
}
},
LossTimerExpired: func(typ TimerType, encLevel EncryptionLevel) {
for _, t := range tracers {
if t.LossTimerExpired != nil {
t.LossTimerExpired(typ, encLevel)
}
}
},
LossTimerCanceled: func() {
for _, t := range tracers {
if t.LossTimerCanceled != nil {
t.LossTimerCanceled()
}
}
},
ECNStateUpdated: func(state ECNState, trigger ECNStateTrigger) {
for _, t := range tracers {
if t.ECNStateUpdated != nil {
t.ECNStateUpdated(state, trigger)
}
}
},
Close: func() {
for _, t := range tracers {
if t.Close != nil {
t.Close()
}
}
},
Debug: func(name, msg string) {
for _, t := range tracers {
if t.Debug != nil {
t.Debug(name, msg)
}
}
},
}
}

View File

@@ -3,9 +3,6 @@
package logging
import (
"net"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
@@ -15,6 +12,8 @@ import (
type (
// A ByteCount is used to count bytes.
ByteCount = protocol.ByteCount
// ECN is the ECN value
ECN = protocol.ECN
// A ConnectionID is a QUIC Connection ID.
ConnectionID = protocol.ConnectionID
// An ArbitraryLenConnectionID is a QUIC Connection ID that can be up to 255 bytes long.
@@ -58,6 +57,19 @@ type (
RTTStats = utils.RTTStats
)
const (
// ECNUnsupported means that no ECN value was set / received
ECNUnsupported = protocol.ECNUnsupported
// ECTNot is Not-ECT
ECTNot = protocol.ECNNon
// ECT0 is ECT(0)
ECT0 = protocol.ECT0
// ECT1 is ECT(1)
ECT1 = protocol.ECT1
// ECNCE is CE
ECNCE = protocol.ECNCE
)
const (
// KeyPhaseZero is key phase bit 0
KeyPhaseZero KeyPhaseBit = protocol.KeyPhaseZero
@@ -97,43 +109,3 @@ type ShortHeader struct {
PacketNumberLen protocol.PacketNumberLen
KeyPhase KeyPhaseBit
}
// A Tracer traces events.
type Tracer interface {
SentPacket(net.Addr, *Header, ByteCount, []Frame)
SentVersionNegotiationPacket(_ net.Addr, dest, src ArbitraryLenConnectionID, _ []VersionNumber)
DroppedPacket(net.Addr, PacketType, ByteCount, PacketDropReason)
}
// A ConnectionTracer records events.
type ConnectionTracer interface {
StartedConnection(local, remote net.Addr, srcConnID, destConnID ConnectionID)
NegotiatedVersion(chosen VersionNumber, clientVersions, serverVersions []VersionNumber)
ClosedConnection(error)
SentTransportParameters(*TransportParameters)
ReceivedTransportParameters(*TransportParameters)
RestoredTransportParameters(parameters *TransportParameters) // for 0-RTT
SentLongHeaderPacket(hdr *ExtendedHeader, size ByteCount, ack *AckFrame, frames []Frame)
SentShortHeaderPacket(hdr *ShortHeader, size ByteCount, ack *AckFrame, frames []Frame)
ReceivedVersionNegotiationPacket(dest, src ArbitraryLenConnectionID, _ []VersionNumber)
ReceivedRetry(*Header)
ReceivedLongHeaderPacket(hdr *ExtendedHeader, size ByteCount, frames []Frame)
ReceivedShortHeaderPacket(hdr *ShortHeader, size ByteCount, frames []Frame)
BufferedPacket(PacketType, ByteCount)
DroppedPacket(PacketType, ByteCount, PacketDropReason)
UpdatedMetrics(rttStats *RTTStats, cwnd, bytesInFlight ByteCount, packetsInFlight int)
AcknowledgedPacket(EncryptionLevel, PacketNumber)
LostPacket(EncryptionLevel, PacketNumber, PacketLossReason)
UpdatedCongestionState(CongestionState)
UpdatedPTOCount(value uint32)
UpdatedKeyFromTLS(EncryptionLevel, Perspective)
UpdatedKey(generation KeyPhase, remote bool)
DroppedEncryptionLevel(EncryptionLevel)
DroppedKey(generation KeyPhase)
SetLossTimer(TimerType, EncryptionLevel, time.Time)
LossTimerExpired(TimerType, EncryptionLevel)
LossTimerCanceled()
// Close is called when the connection is closed.
Close()
Debug(name, msg string)
}

View File

@@ -1,4 +0,0 @@
package logging
//go:generate sh -c "go run github.com/golang/mock/mockgen -package logging -self_package github.com/quic-go/quic-go/logging -destination mock_connection_tracer_test.go github.com/quic-go/quic-go/logging ConnectionTracer"
//go:generate sh -c "go run github.com/golang/mock/mockgen -package logging -self_package github.com/quic-go/quic-go/logging -destination mock_tracer_test.go github.com/quic-go/quic-go/logging Tracer"

View File

@@ -1,226 +0,0 @@
package logging
import (
"net"
"time"
)
type tracerMultiplexer struct {
tracers []Tracer
}
var _ Tracer = &tracerMultiplexer{}
// NewMultiplexedTracer creates a new tracer that multiplexes events to multiple tracers.
func NewMultiplexedTracer(tracers ...Tracer) Tracer {
if len(tracers) == 0 {
return nil
}
if len(tracers) == 1 {
return tracers[0]
}
return &tracerMultiplexer{tracers}
}
func (m *tracerMultiplexer) SentPacket(remote net.Addr, hdr *Header, size ByteCount, frames []Frame) {
for _, t := range m.tracers {
t.SentPacket(remote, hdr, size, frames)
}
}
func (m *tracerMultiplexer) SentVersionNegotiationPacket(remote net.Addr, dest, src ArbitraryLenConnectionID, versions []VersionNumber) {
for _, t := range m.tracers {
t.SentVersionNegotiationPacket(remote, dest, src, versions)
}
}
func (m *tracerMultiplexer) DroppedPacket(remote net.Addr, typ PacketType, size ByteCount, reason PacketDropReason) {
for _, t := range m.tracers {
t.DroppedPacket(remote, typ, size, reason)
}
}
type connTracerMultiplexer struct {
tracers []ConnectionTracer
}
var _ ConnectionTracer = &connTracerMultiplexer{}
// NewMultiplexedConnectionTracer creates a new connection tracer that multiplexes events to multiple tracers.
func NewMultiplexedConnectionTracer(tracers ...ConnectionTracer) ConnectionTracer {
if len(tracers) == 0 {
return nil
}
if len(tracers) == 1 {
return tracers[0]
}
return &connTracerMultiplexer{tracers: tracers}
}
func (m *connTracerMultiplexer) StartedConnection(local, remote net.Addr, srcConnID, destConnID ConnectionID) {
for _, t := range m.tracers {
t.StartedConnection(local, remote, srcConnID, destConnID)
}
}
func (m *connTracerMultiplexer) NegotiatedVersion(chosen VersionNumber, clientVersions, serverVersions []VersionNumber) {
for _, t := range m.tracers {
t.NegotiatedVersion(chosen, clientVersions, serverVersions)
}
}
func (m *connTracerMultiplexer) ClosedConnection(e error) {
for _, t := range m.tracers {
t.ClosedConnection(e)
}
}
func (m *connTracerMultiplexer) SentTransportParameters(tp *TransportParameters) {
for _, t := range m.tracers {
t.SentTransportParameters(tp)
}
}
func (m *connTracerMultiplexer) ReceivedTransportParameters(tp *TransportParameters) {
for _, t := range m.tracers {
t.ReceivedTransportParameters(tp)
}
}
func (m *connTracerMultiplexer) RestoredTransportParameters(tp *TransportParameters) {
for _, t := range m.tracers {
t.RestoredTransportParameters(tp)
}
}
func (m *connTracerMultiplexer) SentLongHeaderPacket(hdr *ExtendedHeader, size ByteCount, ack *AckFrame, frames []Frame) {
for _, t := range m.tracers {
t.SentLongHeaderPacket(hdr, size, ack, frames)
}
}
func (m *connTracerMultiplexer) SentShortHeaderPacket(hdr *ShortHeader, size ByteCount, ack *AckFrame, frames []Frame) {
for _, t := range m.tracers {
t.SentShortHeaderPacket(hdr, size, ack, frames)
}
}
func (m *connTracerMultiplexer) ReceivedVersionNegotiationPacket(dest, src ArbitraryLenConnectionID, versions []VersionNumber) {
for _, t := range m.tracers {
t.ReceivedVersionNegotiationPacket(dest, src, versions)
}
}
func (m *connTracerMultiplexer) ReceivedRetry(hdr *Header) {
for _, t := range m.tracers {
t.ReceivedRetry(hdr)
}
}
func (m *connTracerMultiplexer) ReceivedLongHeaderPacket(hdr *ExtendedHeader, size ByteCount, frames []Frame) {
for _, t := range m.tracers {
t.ReceivedLongHeaderPacket(hdr, size, frames)
}
}
func (m *connTracerMultiplexer) ReceivedShortHeaderPacket(hdr *ShortHeader, size ByteCount, frames []Frame) {
for _, t := range m.tracers {
t.ReceivedShortHeaderPacket(hdr, size, frames)
}
}
func (m *connTracerMultiplexer) BufferedPacket(typ PacketType, size ByteCount) {
for _, t := range m.tracers {
t.BufferedPacket(typ, size)
}
}
func (m *connTracerMultiplexer) DroppedPacket(typ PacketType, size ByteCount, reason PacketDropReason) {
for _, t := range m.tracers {
t.DroppedPacket(typ, size, reason)
}
}
func (m *connTracerMultiplexer) UpdatedCongestionState(state CongestionState) {
for _, t := range m.tracers {
t.UpdatedCongestionState(state)
}
}
func (m *connTracerMultiplexer) UpdatedMetrics(rttStats *RTTStats, cwnd, bytesInFLight ByteCount, packetsInFlight int) {
for _, t := range m.tracers {
t.UpdatedMetrics(rttStats, cwnd, bytesInFLight, packetsInFlight)
}
}
func (m *connTracerMultiplexer) AcknowledgedPacket(encLevel EncryptionLevel, pn PacketNumber) {
for _, t := range m.tracers {
t.AcknowledgedPacket(encLevel, pn)
}
}
func (m *connTracerMultiplexer) LostPacket(encLevel EncryptionLevel, pn PacketNumber, reason PacketLossReason) {
for _, t := range m.tracers {
t.LostPacket(encLevel, pn, reason)
}
}
func (m *connTracerMultiplexer) UpdatedPTOCount(value uint32) {
for _, t := range m.tracers {
t.UpdatedPTOCount(value)
}
}
func (m *connTracerMultiplexer) UpdatedKeyFromTLS(encLevel EncryptionLevel, perspective Perspective) {
for _, t := range m.tracers {
t.UpdatedKeyFromTLS(encLevel, perspective)
}
}
func (m *connTracerMultiplexer) UpdatedKey(generation KeyPhase, remote bool) {
for _, t := range m.tracers {
t.UpdatedKey(generation, remote)
}
}
func (m *connTracerMultiplexer) DroppedEncryptionLevel(encLevel EncryptionLevel) {
for _, t := range m.tracers {
t.DroppedEncryptionLevel(encLevel)
}
}
func (m *connTracerMultiplexer) DroppedKey(generation KeyPhase) {
for _, t := range m.tracers {
t.DroppedKey(generation)
}
}
func (m *connTracerMultiplexer) SetLossTimer(typ TimerType, encLevel EncryptionLevel, exp time.Time) {
for _, t := range m.tracers {
t.SetLossTimer(typ, encLevel, exp)
}
}
func (m *connTracerMultiplexer) LossTimerExpired(typ TimerType, encLevel EncryptionLevel) {
for _, t := range m.tracers {
t.LossTimerExpired(typ, encLevel)
}
}
func (m *connTracerMultiplexer) LossTimerCanceled() {
for _, t := range m.tracers {
t.LossTimerCanceled()
}
}
func (m *connTracerMultiplexer) Debug(name, msg string) {
for _, t := range m.tracers {
t.Debug(name, msg)
}
}
func (m *connTracerMultiplexer) Close() {
for _, t := range m.tracers {
t.Close()
}
}

View File

@@ -1,58 +0,0 @@
package logging
import (
"net"
"time"
)
// The NullTracer is a Tracer that does nothing.
// It is useful for embedding.
type NullTracer struct{}
var _ Tracer = &NullTracer{}
func (n NullTracer) SentPacket(net.Addr, *Header, ByteCount, []Frame) {}
func (n NullTracer) SentVersionNegotiationPacket(_ net.Addr, dest, src ArbitraryLenConnectionID, _ []VersionNumber) {
}
func (n NullTracer) DroppedPacket(net.Addr, PacketType, ByteCount, PacketDropReason) {}
// The NullConnectionTracer is a ConnectionTracer that does nothing.
// It is useful for embedding.
type NullConnectionTracer struct{}
var _ ConnectionTracer = &NullConnectionTracer{}
func (n NullConnectionTracer) StartedConnection(local, remote net.Addr, srcConnID, destConnID ConnectionID) {
}
func (n NullConnectionTracer) NegotiatedVersion(chosen VersionNumber, clientVersions, serverVersions []VersionNumber) {
}
func (n NullConnectionTracer) ClosedConnection(err error) {}
func (n NullConnectionTracer) SentTransportParameters(*TransportParameters) {}
func (n NullConnectionTracer) ReceivedTransportParameters(*TransportParameters) {}
func (n NullConnectionTracer) RestoredTransportParameters(*TransportParameters) {}
func (n NullConnectionTracer) SentLongHeaderPacket(*ExtendedHeader, ByteCount, *AckFrame, []Frame) {}
func (n NullConnectionTracer) SentShortHeaderPacket(*ShortHeader, ByteCount, *AckFrame, []Frame) {}
func (n NullConnectionTracer) ReceivedVersionNegotiationPacket(dest, src ArbitraryLenConnectionID, _ []VersionNumber) {
}
func (n NullConnectionTracer) ReceivedRetry(*Header) {}
func (n NullConnectionTracer) ReceivedLongHeaderPacket(*ExtendedHeader, ByteCount, []Frame) {}
func (n NullConnectionTracer) ReceivedShortHeaderPacket(*ShortHeader, ByteCount, []Frame) {}
func (n NullConnectionTracer) BufferedPacket(PacketType, ByteCount) {}
func (n NullConnectionTracer) DroppedPacket(PacketType, ByteCount, PacketDropReason) {}
func (n NullConnectionTracer) UpdatedMetrics(rttStats *RTTStats, cwnd, bytesInFlight ByteCount, packetsInFlight int) {
}
func (n NullConnectionTracer) AcknowledgedPacket(EncryptionLevel, PacketNumber) {}
func (n NullConnectionTracer) LostPacket(EncryptionLevel, PacketNumber, PacketLossReason) {}
func (n NullConnectionTracer) UpdatedCongestionState(CongestionState) {}
func (n NullConnectionTracer) UpdatedPTOCount(uint32) {}
func (n NullConnectionTracer) UpdatedKeyFromTLS(EncryptionLevel, Perspective) {}
func (n NullConnectionTracer) UpdatedKey(keyPhase KeyPhase, remote bool) {}
func (n NullConnectionTracer) DroppedEncryptionLevel(EncryptionLevel) {}
func (n NullConnectionTracer) DroppedKey(KeyPhase) {}
func (n NullConnectionTracer) SetLossTimer(TimerType, EncryptionLevel, time.Time) {}
func (n NullConnectionTracer) LossTimerExpired(timerType TimerType, level EncryptionLevel) {}
func (n NullConnectionTracer) LossTimerCanceled() {}
func (n NullConnectionTracer) Close() {}
func (n NullConnectionTracer) Debug(name, msg string) {}

43
vendor/github.com/quic-go/quic-go/logging/tracer.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package logging
import "net"
// A Tracer traces events.
type Tracer struct {
SentPacket func(net.Addr, *Header, ByteCount, []Frame)
SentVersionNegotiationPacket func(_ net.Addr, dest, src ArbitraryLenConnectionID, _ []VersionNumber)
DroppedPacket func(net.Addr, PacketType, ByteCount, PacketDropReason)
}
// NewMultiplexedTracer creates a new tracer that multiplexes events to multiple tracers.
func NewMultiplexedTracer(tracers ...*Tracer) *Tracer {
if len(tracers) == 0 {
return nil
}
if len(tracers) == 1 {
return tracers[0]
}
return &Tracer{
SentPacket: func(remote net.Addr, hdr *Header, size ByteCount, frames []Frame) {
for _, t := range tracers {
if t.SentPacket != nil {
t.SentPacket(remote, hdr, size, frames)
}
}
},
SentVersionNegotiationPacket: func(remote net.Addr, dest, src ArbitraryLenConnectionID, versions []VersionNumber) {
for _, t := range tracers {
if t.SentVersionNegotiationPacket != nil {
t.SentVersionNegotiationPacket(remote, dest, src, versions)
}
}
},
DroppedPacket: func(remote net.Addr, typ PacketType, size ByteCount, reason PacketDropReason) {
for _, t := range tracers {
if t.DroppedPacket != nil {
t.DroppedPacket(remote, typ, size, reason)
}
}
},
}
}

View File

@@ -92,3 +92,37 @@ const (
// CongestionStateApplicationLimited means that the congestion controller is application limited
CongestionStateApplicationLimited
)
// ECNState is the state of the ECN state machine (see Appendix A.4 of RFC 9000)
type ECNState uint8
const (
// ECNStateTesting is the testing state
ECNStateTesting ECNState = 1 + iota
// ECNStateUnknown is the unknown state
ECNStateUnknown
// ECNStateFailed is the failed state
ECNStateFailed
// ECNStateCapable is the capable state
ECNStateCapable
)
// ECNStateTrigger is a trigger for an ECN state transition.
type ECNStateTrigger uint8
const (
ECNTriggerNoTrigger ECNStateTrigger = iota
// ECNFailedNoECNCounts is emitted when an ACK acknowledges ECN-marked packets,
// but doesn't contain any ECN counts
ECNFailedNoECNCounts
// ECNFailedDecreasedECNCounts is emitted when an ACK frame decreases ECN counts
ECNFailedDecreasedECNCounts
// ECNFailedLostAllTestingPackets is emitted when all ECN testing packets are declared lost
ECNFailedLostAllTestingPackets
// ECNFailedMoreECNCountsThanSent is emitted when an ACK contains more ECN counts than ECN-marked packets were sent
ECNFailedMoreECNCountsThanSent
// ECNFailedTooFewECNCounts is emitted when an ACK contains fewer ECN counts than it acknowledges packets
ECNFailedTooFewECNCounts
// ECNFailedManglingDetected is emitted when the path marks all ECN-marked packets as CE
ECNFailedManglingDetected
)

View File

@@ -2,73 +2,75 @@
package quic
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_conn_test.go github.com/quic-go/quic-go SendConn"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_conn_test.go github.com/quic-go/quic-go SendConn"
type SendConn = sendConn
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sender_test.go github.com/quic-go/quic-go Sender"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_raw_conn_test.go github.com/quic-go/quic-go RawConn"
type RawConn = rawConn
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sender_test.go github.com/quic-go/quic-go Sender"
type Sender = sender
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_internal_test.go github.com/quic-go/quic-go StreamI"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_internal_test.go github.com/quic-go/quic-go StreamI"
type StreamI = streamI
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_crypto_stream_test.go github.com/quic-go/quic-go CryptoStream"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_crypto_stream_test.go github.com/quic-go/quic-go CryptoStream"
type CryptoStream = cryptoStream
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_receive_stream_internal_test.go github.com/quic-go/quic-go ReceiveStreamI"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_receive_stream_internal_test.go github.com/quic-go/quic-go ReceiveStreamI"
type ReceiveStreamI = receiveStreamI
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_stream_internal_test.go github.com/quic-go/quic-go SendStreamI"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_stream_internal_test.go github.com/quic-go/quic-go SendStreamI"
type SendStreamI = sendStreamI
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_getter_test.go github.com/quic-go/quic-go StreamGetter"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_getter_test.go github.com/quic-go/quic-go StreamGetter"
type StreamGetter = streamGetter
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_sender_test.go github.com/quic-go/quic-go StreamSender"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_sender_test.go github.com/quic-go/quic-go StreamSender"
type StreamSender = streamSender
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_crypto_data_handler_test.go github.com/quic-go/quic-go CryptoDataHandler"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_crypto_data_handler_test.go github.com/quic-go/quic-go CryptoDataHandler"
type CryptoDataHandler = cryptoDataHandler
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_frame_source_test.go github.com/quic-go/quic-go FrameSource"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_frame_source_test.go github.com/quic-go/quic-go FrameSource"
type FrameSource = frameSource
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_ack_frame_source_test.go github.com/quic-go/quic-go AckFrameSource"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_ack_frame_source_test.go github.com/quic-go/quic-go AckFrameSource"
type AckFrameSource = ackFrameSource
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_manager_test.go github.com/quic-go/quic-go StreamManager"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_manager_test.go github.com/quic-go/quic-go StreamManager"
type StreamManager = streamManager
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sealing_manager_test.go github.com/quic-go/quic-go SealingManager"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sealing_manager_test.go github.com/quic-go/quic-go SealingManager"
type SealingManager = sealingManager
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_unpacker_test.go github.com/quic-go/quic-go Unpacker"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_unpacker_test.go github.com/quic-go/quic-go Unpacker"
type Unpacker = unpacker
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packer_test.go github.com/quic-go/quic-go Packer"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packer_test.go github.com/quic-go/quic-go Packer"
type Packer = packer
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_mtu_discoverer_test.go github.com/quic-go/quic-go MTUDiscoverer"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_mtu_discoverer_test.go github.com/quic-go/quic-go MTUDiscoverer"
type MTUDiscoverer = mtuDiscoverer
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_conn_runner_test.go github.com/quic-go/quic-go ConnRunner"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_conn_runner_test.go github.com/quic-go/quic-go ConnRunner"
type ConnRunner = connRunner
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_quic_conn_test.go github.com/quic-go/quic-go QUICConn"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_quic_conn_test.go github.com/quic-go/quic-go QUICConn"
type QUICConn = quicConn
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_test.go github.com/quic-go/quic-go PacketHandler"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_test.go github.com/quic-go/quic-go PacketHandler"
type PacketHandler = packetHandler
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_unknown_packet_handler_test.go github.com/quic-go/quic-go UnknownPacketHandler"
type UnknownPacketHandler = unknownPacketHandler
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_manager_test.go github.com/quic-go/quic-go PacketHandlerManager"
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_manager_test.go github.com/quic-go/quic-go PacketHandlerManager"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_manager_test.go github.com/quic-go/quic-go PacketHandlerManager"
type PacketHandlerManager = packetHandlerManager
// Need to use source mode for the batchConn, since reflect mode follows type aliases.
// See https://github.com/golang/mock/issues/244 for details.
//
//go:generate sh -c "go run github.com/golang/mock/mockgen -package quic -self_package github.com/quic-go/quic-go -source sys_conn_oob.go -destination mock_batch_conn_test.go -mock_names batchConn=MockBatchConn"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -source sys_conn_oob.go -destination mock_batch_conn_test.go -mock_names batchConn=MockBatchConn"
//go:generate sh -c "go run github.com/golang/mock/mockgen -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_token_store_test.go github.com/quic-go/quic-go TokenStore"
//go:generate sh -c "go run github.com/golang/mock/mockgen -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_packetconn_test.go net PacketConn"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_token_store_test.go github.com/quic-go/quic-go TokenStore"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_packetconn_test.go net PacketConn"

View File

@@ -1,6 +1,7 @@
package quic
import (
"net"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
@@ -10,7 +11,11 @@ import (
)
type mtuDiscoverer interface {
// Start starts the MTU discovery process.
// It's unnecessary to call ShouldSendProbe before that.
Start(maxPacketSize protocol.ByteCount)
ShouldSendProbe(now time.Time) bool
CurrentSize() protocol.ByteCount
GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
}
@@ -22,25 +27,38 @@ const (
mtuProbeDelay = 5
)
func getMaxPacketSize(addr net.Addr) protocol.ByteCount {
maxSize := protocol.ByteCount(protocol.MinInitialPacketSize)
// If this is not a UDP address, we don't know anything about the MTU.
// Use the minimum size of an Initial packet as the max packet size.
if udpAddr, ok := addr.(*net.UDPAddr); ok {
if utils.IsIPv4(udpAddr.IP) {
maxSize = protocol.InitialPacketSizeIPv4
} else {
maxSize = protocol.InitialPacketSizeIPv6
}
}
return maxSize
}
type mtuFinder struct {
lastProbeTime time.Time
probeInFlight bool
mtuIncreased func(protocol.ByteCount)
rttStats *utils.RTTStats
inFlight protocol.ByteCount // the size of the probe packet currently in flight. InvalidByteCount if none is in flight
current protocol.ByteCount
max protocol.ByteCount // the maximum value, as advertised by the peer (or our maximum size buffer)
}
var _ mtuDiscoverer = &mtuFinder{}
func newMTUDiscoverer(rttStats *utils.RTTStats, start, max protocol.ByteCount, mtuIncreased func(protocol.ByteCount)) mtuDiscoverer {
func newMTUDiscoverer(rttStats *utils.RTTStats, start protocol.ByteCount, mtuIncreased func(protocol.ByteCount)) *mtuFinder {
return &mtuFinder{
current: start,
rttStats: rttStats,
lastProbeTime: time.Now(), // to make sure the first probe packet is not sent immediately
mtuIncreased: mtuIncreased,
max: max,
inFlight: protocol.InvalidByteCount,
current: start,
rttStats: rttStats,
mtuIncreased: mtuIncreased,
}
}
@@ -48,8 +66,16 @@ func (f *mtuFinder) done() bool {
return f.max-f.current <= maxMTUDiff+1
}
func (f *mtuFinder) Start(maxPacketSize protocol.ByteCount) {
f.lastProbeTime = time.Now() // makes sure the first probe packet is not sent immediately
f.max = maxPacketSize
}
func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
if f.probeInFlight || f.done() {
if f.max == 0 || f.lastProbeTime.IsZero() {
return false
}
if f.inFlight != protocol.InvalidByteCount || f.done() {
return false
}
return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
@@ -58,17 +84,36 @@ func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) {
size := (f.max + f.current) / 2
f.lastProbeTime = time.Now()
f.probeInFlight = true
f.inFlight = size
return ackhandler.Frame{
Frame: &wire.PingFrame{},
OnLost: func(wire.Frame) {
f.probeInFlight = false
f.max = size
},
OnAcked: func(wire.Frame) {
f.probeInFlight = false
f.current = size
f.mtuIncreased(size)
},
Frame: &wire.PingFrame{},
Handler: (*mtuFinderAckHandler)(f),
}, size
}
func (f *mtuFinder) CurrentSize() protocol.ByteCount {
return f.current
}
type mtuFinderAckHandler mtuFinder
var _ ackhandler.FrameHandler = &mtuFinderAckHandler{}
func (h *mtuFinderAckHandler) OnAcked(wire.Frame) {
size := h.inFlight
if size == protocol.InvalidByteCount {
panic("OnAcked callback called although there's no MTU probe packet in flight")
}
h.inFlight = protocol.InvalidByteCount
h.current = size
h.mtuIncreased(size)
}
func (h *mtuFinderAckHandler) OnLost(wire.Frame) {
size := h.inFlight
if size == protocol.InvalidByteCount {
panic("OnLost callback called although there's no MTU probe packet in flight")
}
h.max = size
h.inFlight = protocol.InvalidByteCount
}

42
vendor/github.com/quic-go/quic-go/oss-fuzz.sh generated vendored Normal file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Install Go manually, since oss-fuzz ships with an outdated Go version.
# See https://github.com/google/oss-fuzz/pull/10643.
export CXX="${CXX} -lresolv" # required by Go 1.20
wget https://go.dev/dl/go1.20.5.linux-amd64.tar.gz \
&& mkdir temp-go \
&& rm -rf /root/.go/* \
&& tar -C temp-go/ -xzf go1.20.5.linux-amd64.tar.gz \
&& mv temp-go/go/* /root/.go/ \
&& rm -rf temp-go go1.20.5.linux-amd64.tar.gz
(
# fuzz qpack
compile_go_fuzzer github.com/quic-go/qpack/fuzzing Fuzz qpack_fuzzer
)
(
# fuzz quic-go
compile_go_fuzzer github.com/quic-go/quic-go/fuzzing/frames Fuzz frame_fuzzer
compile_go_fuzzer github.com/quic-go/quic-go/fuzzing/header Fuzz header_fuzzer
compile_go_fuzzer github.com/quic-go/quic-go/fuzzing/transportparameters Fuzz transportparameter_fuzzer
compile_go_fuzzer github.com/quic-go/quic-go/fuzzing/tokens Fuzz token_fuzzer
compile_go_fuzzer github.com/quic-go/quic-go/fuzzing/handshake Fuzz handshake_fuzzer
if [ $SANITIZER == "coverage" ]; then
# no need for corpora if coverage
exit 0
fi
# generate seed corpora
cd $GOPATH/src/github.com/quic-go/quic-go/
go generate -x ./fuzzing/...
zip --quiet -r $OUT/header_fuzzer_seed_corpus.zip fuzzing/header/corpus
zip --quiet -r $OUT/frame_fuzzer_seed_corpus.zip fuzzing/frames/corpus
zip --quiet -r $OUT/transportparameter_fuzzer_seed_corpus.zip fuzzing/transportparameters/corpus
zip --quiet -r $OUT/handshake_fuzzer_seed_corpus.zip fuzzing/handshake/corpus
)
# for debugging
ls -al $OUT

View File

@@ -4,7 +4,6 @@ import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"errors"
"hash"
"io"
"net"
@@ -15,28 +14,36 @@ import (
"github.com/quic-go/quic-go/internal/utils"
)
type connCapabilities struct {
// This connection has the Don't Fragment (DF) bit set.
// This means it makes to run DPLPMTUD.
DF bool
// GSO (Generic Segmentation Offload) supported
GSO bool
// ECN (Explicit Congestion Notifications) supported
ECN bool
}
// rawConn is a connection that allow reading of a receivedPackeh.
type rawConn interface {
ReadPacket() (*receivedPacket, error)
WritePacket(b []byte, addr net.Addr, oob []byte) (int, error)
ReadPacket() (receivedPacket, error)
// WritePacket writes a packet on the wire.
// gsoSize is the size of a single packet, or 0 to disable GSO.
// It is invalid to set gsoSize if capabilities.GSO is not set.
WritePacket(b []byte, addr net.Addr, packetInfoOOB []byte, gsoSize uint16, ecn protocol.ECN) (int, error)
LocalAddr() net.Addr
SetReadDeadline(time.Time) error
io.Closer
capabilities() connCapabilities
}
type closePacket struct {
payload []byte
addr net.Addr
info *packetInfo
info packetInfo
}
type unknownPacketHandler interface {
handlePacket(*receivedPacket)
setCloseError(error)
}
var errListenerAlreadySet = errors.New("listener already set")
type packetHandlerMap struct {
mutex sync.Mutex
handlers map[protocol.ConnectionID]packetHandler
@@ -165,7 +172,7 @@ func (h *packetHandlerMap) ReplaceWithClosed(ids []protocol.ConnectionID, pers p
var handler packetHandler
if connClosePacket != nil {
handler = newClosedLocalConn(
func(addr net.Addr, info *packetInfo) {
func(addr net.Addr, info packetInfo) {
h.enqueueClosePacket(closePacket{payload: connClosePacket, addr: addr, info: info})
},
pers,
@@ -213,23 +220,6 @@ func (h *packetHandlerMap) GetByResetToken(token protocol.StatelessResetToken) (
return handler, ok
}
func (h *packetHandlerMap) CloseServer() {
h.mutex.Lock()
var wg sync.WaitGroup
for _, handler := range h.handlers {
if handler.getPerspective() == protocol.PerspectiveServer {
wg.Add(1)
go func(handler packetHandler) {
// blocks until the CONNECTION_CLOSE has been sent and the run-loop has stopped
handler.shutdown()
wg.Done()
}(handler)
}
}
h.mutex.Unlock()
wg.Wait()
}
func (h *packetHandlerMap) Close(e error) {
h.mutex.Lock()

View File

@@ -1,32 +1,31 @@
package quic
import (
crand "crypto/rand"
"encoding/binary"
"errors"
"fmt"
"net"
"time"
"golang.org/x/exp/rand"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
var errNothingToPack = errors.New("nothing to pack")
type packer interface {
PackCoalescedPacket(onlyAck bool, v protocol.VersionNumber) (*coalescedPacket, error)
PackPacket(onlyAck bool, now time.Time, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error)
MaybePackProbePacket(protocol.EncryptionLevel, protocol.VersionNumber) (*coalescedPacket, error)
PackConnectionClose(*qerr.TransportError, protocol.VersionNumber) (*coalescedPacket, error)
PackApplicationClose(*qerr.ApplicationError, protocol.VersionNumber) (*coalescedPacket, error)
PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error)
PackAckOnlyPacket(maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error)
AppendPacket(buf *packetBuffer, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, error)
MaybePackProbePacket(protocol.EncryptionLevel, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error)
PackConnectionClose(*qerr.TransportError, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error)
PackApplicationClose(*qerr.ApplicationError, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error)
PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error)
SetMaxPacketSize(protocol.ByteCount)
PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, now time.Time, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error)
HandleTransportParameters(*wire.TransportParameters)
SetToken([]byte)
}
@@ -35,26 +34,31 @@ type sealer interface {
}
type payload struct {
frames []*ackhandler.Frame
ack *wire.AckFrame
length protocol.ByteCount
streamFrames []ackhandler.StreamFrame
frames []ackhandler.Frame
ack *wire.AckFrame
length protocol.ByteCount
}
type longHeaderPacket struct {
header *wire.ExtendedHeader
ack *wire.AckFrame
frames []*ackhandler.Frame
header *wire.ExtendedHeader
ack *wire.AckFrame
frames []ackhandler.Frame
streamFrames []ackhandler.StreamFrame // only used for 0-RTT packets
length protocol.ByteCount
isMTUProbePacket bool
}
type shortHeaderPacket struct {
*ackhandler.Packet
PacketNumber protocol.PacketNumber
Frames []ackhandler.Frame
StreamFrames []ackhandler.StreamFrame
Ack *wire.AckFrame
Length protocol.ByteCount
IsPathMTUProbePacket bool
// used for logging
DestConnID protocol.ConnectionID
Ack *wire.AckFrame
PacketNumberLen protocol.PacketNumberLen
KeyPhase protocol.KeyPhaseBit
}
@@ -67,6 +71,11 @@ type coalescedPacket struct {
shortHdrPacket *shortHeaderPacket
}
// IsOnlyShortHeaderPacket says if this packet only contains a short header packet (and no long header packets).
func (p *coalescedPacket) IsOnlyShortHeaderPacket() bool {
return len(p.longHdrPackets) == 0 && p.shortHdrPacket != nil
}
func (p *longHeaderPacket) EncryptionLevel() protocol.EncryptionLevel {
//nolint:exhaustive // Will never be called for Retry packets (and they don't have encrypted data).
switch p.header.Type {
@@ -83,52 +92,6 @@ func (p *longHeaderPacket) EncryptionLevel() protocol.EncryptionLevel {
func (p *longHeaderPacket) IsAckEliciting() bool { return ackhandler.HasAckElicitingFrames(p.frames) }
func (p *longHeaderPacket) ToAckHandlerPacket(now time.Time, q *retransmissionQueue) *ackhandler.Packet {
largestAcked := protocol.InvalidPacketNumber
if p.ack != nil {
largestAcked = p.ack.LargestAcked()
}
encLevel := p.EncryptionLevel()
for i := range p.frames {
if p.frames[i].OnLost != nil {
continue
}
//nolint:exhaustive // Short header packets are handled separately.
switch encLevel {
case protocol.EncryptionInitial:
p.frames[i].OnLost = q.AddInitial
case protocol.EncryptionHandshake:
p.frames[i].OnLost = q.AddHandshake
case protocol.Encryption0RTT:
p.frames[i].OnLost = q.AddAppData
}
}
ap := ackhandler.GetPacket()
ap.PacketNumber = p.header.PacketNumber
ap.LargestAcked = largestAcked
ap.Frames = p.frames
ap.Length = p.length
ap.EncryptionLevel = encLevel
ap.SendTime = now
ap.IsPathMTUProbePacket = p.isMTUProbePacket
return ap
}
func getMaxPacketSize(addr net.Addr) protocol.ByteCount {
maxSize := protocol.ByteCount(protocol.MinInitialPacketSize)
// If this is not a UDP address, we don't know anything about the MTU.
// Use the minimum size of an Initial packet as the max packet size.
if udpAddr, ok := addr.(*net.UDPAddr); ok {
if utils.IsIPv4(udpAddr.IP) {
maxSize = protocol.InitialPacketSizeIPv4
} else {
maxSize = protocol.InitialPacketSizeIPv6
}
}
return maxSize
}
type packetNumberManager interface {
PeekPacketNumber(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)
PopPacketNumber(protocol.EncryptionLevel) protocol.PacketNumber
@@ -143,8 +106,8 @@ type sealingManager interface {
type frameSource interface {
HasData() bool
AppendStreamFrames([]*ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount)
AppendControlFrames([]*ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]*ackhandler.Frame, protocol.ByteCount)
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount)
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount)
}
type ackFrameSource interface {
@@ -168,14 +131,28 @@ type packetPacker struct {
acks ackFrameSource
datagramQueue *datagramQueue
retransmissionQueue *retransmissionQueue
rand rand.Rand
maxPacketSize protocol.ByteCount
numNonAckElicitingAcks int
}
var _ packer = &packetPacker{}
func newPacketPacker(srcConnID protocol.ConnectionID, getDestConnID func() protocol.ConnectionID, initialStream cryptoStream, handshakeStream cryptoStream, packetNumberManager packetNumberManager, retransmissionQueue *retransmissionQueue, remoteAddr net.Addr, cryptoSetup sealingManager, framer frameSource, acks ackFrameSource, datagramQueue *datagramQueue, perspective protocol.Perspective) *packetPacker {
func newPacketPacker(
srcConnID protocol.ConnectionID,
getDestConnID func() protocol.ConnectionID,
initialStream, handshakeStream cryptoStream,
packetNumberManager packetNumberManager,
retransmissionQueue *retransmissionQueue,
cryptoSetup sealingManager,
framer frameSource,
acks ackFrameSource,
datagramQueue *datagramQueue,
perspective protocol.Perspective,
) *packetPacker {
var b [8]byte
_, _ = crand.Read(b[:])
return &packetPacker{
cryptoSetup: cryptoSetup,
getDestConnID: getDestConnID,
@@ -187,24 +164,24 @@ func newPacketPacker(srcConnID protocol.ConnectionID, getDestConnID func() proto
perspective: perspective,
framer: framer,
acks: acks,
rand: *rand.New(rand.NewSource(binary.BigEndian.Uint64(b[:]))),
pnManager: packetNumberManager,
maxPacketSize: getMaxPacketSize(remoteAddr),
}
}
// PackConnectionClose packs a packet that closes the connection with a transport error.
func (p *packetPacker) PackConnectionClose(e *qerr.TransportError, v protocol.VersionNumber) (*coalescedPacket, error) {
func (p *packetPacker) PackConnectionClose(e *qerr.TransportError, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error) {
var reason string
// don't send details of crypto errors
if !e.ErrorCode.IsCryptoError() {
reason = e.ErrorMessage
}
return p.packConnectionClose(false, uint64(e.ErrorCode), e.FrameType, reason, v)
return p.packConnectionClose(false, uint64(e.ErrorCode), e.FrameType, reason, maxPacketSize, v)
}
// PackApplicationClose packs a packet that closes the connection with an application error.
func (p *packetPacker) PackApplicationClose(e *qerr.ApplicationError, v protocol.VersionNumber) (*coalescedPacket, error) {
return p.packConnectionClose(true, uint64(e.ErrorCode), 0, e.ErrorMessage, v)
func (p *packetPacker) PackApplicationClose(e *qerr.ApplicationError, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error) {
return p.packConnectionClose(true, uint64(e.ErrorCode), 0, e.ErrorMessage, maxPacketSize, v)
}
func (p *packetPacker) packConnectionClose(
@@ -212,6 +189,7 @@ func (p *packetPacker) packConnectionClose(
errorCode uint64,
frameType uint64,
reason string,
maxPacketSize protocol.ByteCount,
v protocol.VersionNumber,
) (*coalescedPacket, error) {
var sealers [4]sealer
@@ -241,7 +219,7 @@ func (p *packetPacker) packConnectionClose(
ccf.ReasonPhrase = ""
}
pl := payload{
frames: []*ackhandler.Frame{{Frame: ccf}},
frames: []ackhandler.Frame{{Frame: ccf}},
length: ccf.Length(v),
}
@@ -293,20 +271,14 @@ func (p *packetPacker) packConnectionClose(
}
var paddingLen protocol.ByteCount
if encLevel == protocol.EncryptionInitial {
paddingLen = p.initialPaddingLen(payloads[i].frames, size)
paddingLen = p.initialPaddingLen(payloads[i].frames, size, maxPacketSize)
}
if encLevel == protocol.Encryption1RTT {
ap, ack, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, keyPhase, payloads[i], paddingLen, sealers[i], false, v)
shp, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, keyPhase, payloads[i], paddingLen, maxPacketSize, sealers[i], false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shortHeaderPacket{
Packet: ap,
DestConnID: connID,
Ack: ack,
PacketNumberLen: oneRTTPacketNumberLen,
KeyPhase: keyPhase,
}
packet.shortHdrPacket = &shp
} else {
longHdrPacket, err := p.appendLongHeaderPacket(buffer, hdrs[i], payloads[i], paddingLen, encLevel, sealers[i], v)
if err != nil {
@@ -342,25 +314,21 @@ func (p *packetPacker) shortHeaderPacketLength(connID protocol.ConnectionID, pnL
}
// size is the expected size of the packet, if no padding was applied.
func (p *packetPacker) initialPaddingLen(frames []*ackhandler.Frame, size protocol.ByteCount) protocol.ByteCount {
func (p *packetPacker) initialPaddingLen(frames []ackhandler.Frame, currentSize, maxPacketSize protocol.ByteCount) protocol.ByteCount {
// For the server, only ack-eliciting Initial packets need to be padded.
if p.perspective == protocol.PerspectiveServer && !ackhandler.HasAckElicitingFrames(frames) {
return 0
}
if size >= p.maxPacketSize {
if currentSize >= maxPacketSize {
return 0
}
return p.maxPacketSize - size
return maxPacketSize - currentSize
}
// PackCoalescedPacket packs a new packet.
// It packs an Initial / Handshake if there is data to send in these packet number spaces.
// It should only be called before the handshake is confirmed.
func (p *packetPacker) PackCoalescedPacket(onlyAck bool, v protocol.VersionNumber) (*coalescedPacket, error) {
maxPacketSize := p.maxPacketSize
if p.perspective == protocol.PerspectiveClient {
maxPacketSize = protocol.MinInitialPacketSize
}
func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error) {
var (
initialHdr, handshakeHdr, zeroRTTHdr *wire.ExtendedHeader
initialPayload, handshakePayload, zeroRTTPayload, oneRTTPayload payload
@@ -417,7 +385,7 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, v protocol.VersionNumbe
if oneRTTPayload.length > 0 {
size += p.shortHeaderPacketLength(connID, oneRTTPacketNumberLen, oneRTTPayload) + protocol.ByteCount(oneRTTSealer.Overhead())
}
} else if p.perspective == protocol.PerspectiveClient { // 0-RTT
} else if p.perspective == protocol.PerspectiveClient && !onlyAck { // 0-RTT packets can't contain ACK frames
var err error
zeroRTTSealer, err = p.cryptoSetup.Get0RTTSealer()
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
@@ -442,7 +410,7 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, v protocol.VersionNumbe
longHdrPackets: make([]*longHeaderPacket, 0, 3),
}
if initialPayload.length > 0 {
padding := p.initialPaddingLen(initialPayload.frames, size)
padding := p.initialPaddingLen(initialPayload.frames, size, maxPacketSize)
cont, err := p.appendLongHeaderPacket(buffer, initialHdr, initialPayload, padding, protocol.EncryptionInitial, initialSealer, v)
if err != nil {
return nil, err
@@ -463,48 +431,44 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, v protocol.VersionNumbe
}
packet.longHdrPackets = append(packet.longHdrPackets, longHdrPacket)
} else if oneRTTPayload.length > 0 {
ap, ack, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, kp, oneRTTPayload, 0, oneRTTSealer, false, v)
shp, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, kp, oneRTTPayload, 0, maxPacketSize, oneRTTSealer, false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shortHeaderPacket{
Packet: ap,
DestConnID: connID,
Ack: ack,
PacketNumberLen: oneRTTPacketNumberLen,
KeyPhase: kp,
}
packet.shortHdrPacket = &shp
}
return packet, nil
}
// PackPacket packs a packet in the application data packet number space.
// PackAckOnlyPacket packs a packet containing only an ACK in the application data packet number space.
// It should be called after the handshake is confirmed.
func (p *packetPacker) PackPacket(onlyAck bool, now time.Time, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error) {
func (p *packetPacker) PackAckOnlyPacket(maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error) {
buf := getPacketBuffer()
packet, err := p.appendPacket(buf, true, maxPacketSize, v)
return packet, buf, err
}
// AppendPacket packs a packet in the application data packet number space.
// It should be called after the handshake is confirmed.
func (p *packetPacker) AppendPacket(buf *packetBuffer, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, error) {
return p.appendPacket(buf, false, maxPacketSize, v)
}
func (p *packetPacker) appendPacket(buf *packetBuffer, onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, error) {
sealer, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
return shortHeaderPacket{}, nil, err
return shortHeaderPacket{}, err
}
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
connID := p.getDestConnID()
hdrLen := wire.ShortHeaderLen(connID, pnLen)
pl := p.maybeGetShortHeaderPacket(sealer, hdrLen, p.maxPacketSize, onlyAck, true, v)
pl := p.maybeGetShortHeaderPacket(sealer, hdrLen, maxPacketSize, onlyAck, true, v)
if pl.length == 0 {
return shortHeaderPacket{}, nil, errNothingToPack
return shortHeaderPacket{}, errNothingToPack
}
kp := sealer.KeyPhase()
buffer := getPacketBuffer()
ap, ack, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, 0, sealer, false, v)
if err != nil {
return shortHeaderPacket{}, nil, err
}
return shortHeaderPacket{
Packet: ap,
DestConnID: connID,
Ack: ack,
PacketNumberLen: pnLen,
KeyPhase: kp,
}, buffer, nil
return p.appendShortHeaderPacket(buf, connID, pn, pnLen, kp, pl, 0, maxPacketSize, sealer, false, v)
}
func (p *packetPacker) maybeGetCryptoPacket(maxPacketSize protocol.ByteCount, encLevel protocol.EncryptionLevel, onlyAck, ackAllowed bool, v protocol.VersionNumber) (*wire.ExtendedHeader, payload) {
@@ -519,14 +483,17 @@ func (p *packetPacker) maybeGetCryptoPacket(maxPacketSize protocol.ByteCount, en
}
var s cryptoStream
var handler ackhandler.FrameHandler
var hasRetransmission bool
//nolint:exhaustive // Initial and Handshake are the only two encryption levels here.
switch encLevel {
case protocol.EncryptionInitial:
s = p.initialStream
handler = p.retransmissionQueue.InitialAckHandler()
hasRetransmission = p.retransmissionQueue.HasInitialData()
case protocol.EncryptionHandshake:
s = p.handshakeStream
handler = p.retransmissionQueue.HandshakeAckHandler()
hasRetransmission = p.retransmissionQueue.HasHandshakeData()
}
@@ -550,27 +517,27 @@ func (p *packetPacker) maybeGetCryptoPacket(maxPacketSize protocol.ByteCount, en
maxPacketSize -= hdr.GetLength(v)
if hasRetransmission {
for {
var f wire.Frame
var f ackhandler.Frame
//nolint:exhaustive // 0-RTT packets can't contain any retransmission.s
switch encLevel {
case protocol.EncryptionInitial:
f = p.retransmissionQueue.GetInitialFrame(maxPacketSize, v)
f.Frame = p.retransmissionQueue.GetInitialFrame(maxPacketSize, v)
f.Handler = p.retransmissionQueue.InitialAckHandler()
case protocol.EncryptionHandshake:
f = p.retransmissionQueue.GetHandshakeFrame(maxPacketSize, v)
f.Frame = p.retransmissionQueue.GetHandshakeFrame(maxPacketSize, v)
f.Handler = p.retransmissionQueue.HandshakeAckHandler()
}
if f == nil {
if f.Frame == nil {
break
}
af := ackhandler.GetFrame()
af.Frame = f
pl.frames = append(pl.frames, af)
frameLen := f.Length(v)
pl.frames = append(pl.frames, f)
frameLen := f.Frame.Length(v)
pl.length += frameLen
maxPacketSize -= frameLen
}
} else if s.HasData() {
cf := s.PopCryptoFrame(maxPacketSize)
pl.frames = []*ackhandler.Frame{{Frame: cf}}
pl.frames = []ackhandler.Frame{{Frame: cf, Handler: handler}}
pl.length += cf.Length(v)
}
return hdr, pl
@@ -595,18 +562,14 @@ func (p *packetPacker) maybeGetAppDataPacket(maxPayloadSize protocol.ByteCount,
pl := p.composeNextPacket(maxPayloadSize, onlyAck, ackAllowed, v)
// check if we have anything to send
if len(pl.frames) == 0 {
if len(pl.frames) == 0 && len(pl.streamFrames) == 0 {
if pl.ack == nil {
return payload{}
}
// the packet only contains an ACK
if p.numNonAckElicitingAcks >= protocol.MaxNonAckElicitingAcks {
ping := &wire.PingFrame{}
// don't retransmit the PING frame when it is lost
af := ackhandler.GetFrame()
af.Frame = ping
af.OnLost = func(wire.Frame) {}
pl.frames = append(pl.frames, af)
pl.frames = append(pl.frames, ackhandler.Frame{Frame: ping})
pl.length += ping.Length(v)
p.numNonAckElicitingAcks = 0
} else {
@@ -621,15 +584,12 @@ func (p *packetPacker) maybeGetAppDataPacket(maxPayloadSize protocol.ByteCount,
func (p *packetPacker) composeNextPacket(maxFrameSize protocol.ByteCount, onlyAck, ackAllowed bool, v protocol.VersionNumber) payload {
if onlyAck {
if ack := p.acks.GetAckFrame(protocol.Encryption1RTT, true); ack != nil {
return payload{
ack: ack,
length: ack.Length(v),
}
return payload{ack: ack, length: ack.Length(v)}
}
return payload{}
}
pl := payload{frames: make([]*ackhandler.Frame, 0, 1)}
pl := payload{streamFrames: make([]ackhandler.StreamFrame, 0, 1)}
hasData := p.framer.HasData()
hasRetransmission := p.retransmissionQueue.HasAppData()
@@ -647,11 +607,7 @@ func (p *packetPacker) composeNextPacket(maxFrameSize protocol.ByteCount, onlyAc
if f := p.datagramQueue.Peek(); f != nil {
size := f.Length(v)
if size <= maxFrameSize-pl.length {
af := ackhandler.GetFrame()
af.Frame = f
// set it to a no-op. Then we won't set the default callback, which would retransmit the frame.
af.OnLost = func(wire.Frame) {}
pl.frames = append(pl.frames, af)
pl.frames = append(pl.frames, ackhandler.Frame{Frame: f})
pl.length += size
p.datagramQueue.Pop()
}
@@ -672,25 +628,28 @@ func (p *packetPacker) composeNextPacket(maxFrameSize protocol.ByteCount, onlyAc
if f == nil {
break
}
af := ackhandler.GetFrame()
af.Frame = f
pl.frames = append(pl.frames, af)
pl.frames = append(pl.frames, ackhandler.Frame{Frame: f, Handler: p.retransmissionQueue.AppDataAckHandler()})
pl.length += f.Length(v)
}
}
if hasData {
var lengthAdded protocol.ByteCount
startLen := len(pl.frames)
pl.frames, lengthAdded = p.framer.AppendControlFrames(pl.frames, maxFrameSize-pl.length, v)
pl.length += lengthAdded
// add handlers for the control frames that were added
for i := startLen; i < len(pl.frames); i++ {
pl.frames[i].Handler = p.retransmissionQueue.AppDataAckHandler()
}
pl.frames, lengthAdded = p.framer.AppendStreamFrames(pl.frames, maxFrameSize-pl.length, v)
pl.streamFrames, lengthAdded = p.framer.AppendStreamFrames(pl.streamFrames, maxFrameSize-pl.length, v)
pl.length += lengthAdded
}
return pl
}
func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, v protocol.VersionNumber) (*coalescedPacket, error) {
func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error) {
if encLevel == protocol.Encryption1RTT {
s, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
@@ -700,23 +659,17 @@ func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, v
connID := p.getDestConnID()
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
hdrLen := wire.ShortHeaderLen(connID, pnLen)
pl := p.maybeGetAppDataPacket(p.maxPacketSize-protocol.ByteCount(s.Overhead())-hdrLen, false, true, v)
pl := p.maybeGetAppDataPacket(maxPacketSize-protocol.ByteCount(s.Overhead())-hdrLen, false, true, v)
if pl.length == 0 {
return nil, nil
}
buffer := getPacketBuffer()
packet := &coalescedPacket{buffer: buffer}
ap, ack, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, 0, s, false, v)
shp, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, 0, maxPacketSize, s, false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shortHeaderPacket{
Packet: ap,
DestConnID: connID,
Ack: ack,
PacketNumberLen: pnLen,
KeyPhase: kp,
}
packet.shortHdrPacket = &shp
return packet, nil
}
@@ -731,14 +684,14 @@ func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, v
if err != nil {
return nil, err
}
hdr, pl = p.maybeGetCryptoPacket(p.maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionInitial, false, true, v)
hdr, pl = p.maybeGetCryptoPacket(maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionInitial, false, true, v)
case protocol.EncryptionHandshake:
var err error
sealer, err = p.cryptoSetup.GetHandshakeSealer()
if err != nil {
return nil, err
}
hdr, pl = p.maybeGetCryptoPacket(p.maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionHandshake, false, true, v)
hdr, pl = p.maybeGetCryptoPacket(maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionHandshake, false, true, v)
default:
panic("unknown encryption level")
}
@@ -751,7 +704,7 @@ func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, v
size := p.longHeaderPacketLength(hdr, pl, v) + protocol.ByteCount(sealer.Overhead())
var padding protocol.ByteCount
if encLevel == protocol.EncryptionInitial {
padding = p.initialPaddingLen(pl.frames, size)
padding = p.initialPaddingLen(pl.frames, size, maxPacketSize)
}
longHdrPacket, err := p.appendLongHeaderPacket(buffer, hdr, pl, padding, encLevel, sealer, v)
@@ -762,10 +715,10 @@ func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, v
return packet, nil
}
func (p *packetPacker) PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, now time.Time, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error) {
func (p *packetPacker) PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, *packetBuffer, error) {
pl := payload{
frames: []*ackhandler.Frame{&ping},
length: ping.Length(v),
frames: []ackhandler.Frame{ping},
length: ping.Frame.Length(v),
}
buffer := getPacketBuffer()
s, err := p.cryptoSetup.Get1RTTSealer()
@@ -776,17 +729,8 @@ func (p *packetPacker) PackMTUProbePacket(ping ackhandler.Frame, size protocol.B
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
padding := size - p.shortHeaderPacketLength(connID, pnLen, pl) - protocol.ByteCount(s.Overhead())
kp := s.KeyPhase()
ap, ack, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, padding, s, true, v)
if err != nil {
return shortHeaderPacket{}, nil, err
}
return shortHeaderPacket{
Packet: ap,
DestConnID: connID,
Ack: ack,
PacketNumberLen: pnLen,
KeyPhase: kp,
}, buffer, nil
packet, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, padding, size, s, true, v)
return packet, buffer, err
}
func (p *packetPacker) getLongHeader(encLevel protocol.EncryptionLevel, v protocol.VersionNumber) *wire.ExtendedHeader {
@@ -829,23 +773,22 @@ func (p *packetPacker) appendLongHeaderPacket(buffer *packetBuffer, header *wire
}
payloadOffset := protocol.ByteCount(len(raw))
pn := p.pnManager.PopPacketNumber(encLevel)
if pn != header.PacketNumber {
return nil, errors.New("packetPacker BUG: Peeked and Popped packet numbers do not match")
}
raw, err = p.appendPacketPayload(raw, pl, paddingLen, v)
if err != nil {
return nil, err
}
raw = p.encryptPacket(raw, sealer, pn, payloadOffset, pnLen)
raw = p.encryptPacket(raw, sealer, header.PacketNumber, payloadOffset, pnLen)
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
if pn := p.pnManager.PopPacketNumber(encLevel); pn != header.PacketNumber {
return nil, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, header.PacketNumber)
}
return &longHeaderPacket{
header: header,
ack: pl.ack,
frames: pl.frames,
length: protocol.ByteCount(len(raw)),
header: header,
ack: pl.ack,
frames: pl.frames,
streamFrames: pl.streamFrames,
length: protocol.ByteCount(len(raw)),
}, nil
}
@@ -856,11 +799,11 @@ func (p *packetPacker) appendShortHeaderPacket(
pnLen protocol.PacketNumberLen,
kp protocol.KeyPhaseBit,
pl payload,
padding protocol.ByteCount,
padding, maxPacketSize protocol.ByteCount,
sealer sealer,
isMTUProbePacket bool,
v protocol.VersionNumber,
) (*ackhandler.Packet, *wire.AckFrame, error) {
) (shortHeaderPacket, error) {
var paddingLen protocol.ByteCount
if pl.length < 4-protocol.ByteCount(pnLen) {
paddingLen = 4 - protocol.ByteCount(pnLen) - pl.length
@@ -871,50 +814,40 @@ func (p *packetPacker) appendShortHeaderPacket(
raw := buffer.Data[startLen:]
raw, err := wire.AppendShortHeader(raw, connID, pn, pnLen, kp)
if err != nil {
return nil, nil, err
return shortHeaderPacket{}, err
}
payloadOffset := protocol.ByteCount(len(raw))
if pn != p.pnManager.PopPacketNumber(protocol.Encryption1RTT) {
return nil, nil, errors.New("packetPacker BUG: Peeked and Popped packet numbers do not match")
}
raw, err = p.appendPacketPayload(raw, pl, paddingLen, v)
if err != nil {
return nil, nil, err
return shortHeaderPacket{}, err
}
if !isMTUProbePacket {
if size := protocol.ByteCount(len(raw) + sealer.Overhead()); size > p.maxPacketSize {
return nil, nil, fmt.Errorf("PacketPacker BUG: packet too large (%d bytes, allowed %d bytes)", size, p.maxPacketSize)
if size := protocol.ByteCount(len(raw) + sealer.Overhead()); size > maxPacketSize {
return shortHeaderPacket{}, fmt.Errorf("PacketPacker BUG: packet too large (%d bytes, allowed %d bytes)", size, maxPacketSize)
}
}
raw = p.encryptPacket(raw, sealer, pn, payloadOffset, protocol.ByteCount(pnLen))
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
// create the ackhandler.Packet
largestAcked := protocol.InvalidPacketNumber
if pl.ack != nil {
largestAcked = pl.ack.LargestAcked()
if newPN := p.pnManager.PopPacketNumber(protocol.Encryption1RTT); newPN != pn {
return shortHeaderPacket{}, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, newPN)
}
for i := range pl.frames {
if pl.frames[i].OnLost != nil {
continue
}
pl.frames[i].OnLost = p.retransmissionQueue.AddAppData
}
ap := ackhandler.GetPacket()
ap.PacketNumber = pn
ap.LargestAcked = largestAcked
ap.Frames = pl.frames
ap.Length = protocol.ByteCount(len(raw))
ap.EncryptionLevel = protocol.Encryption1RTT
ap.SendTime = time.Now()
ap.IsPathMTUProbePacket = isMTUProbePacket
return ap, pl.ack, nil
return shortHeaderPacket{
PacketNumber: pn,
PacketNumberLen: pnLen,
KeyPhase: kp,
StreamFrames: pl.streamFrames,
Frames: pl.frames,
Ack: pl.ack,
Length: protocol.ByteCount(len(raw)),
DestConnID: connID,
IsPathMTUProbePacket: isMTUProbePacket,
}, nil
}
// appendPacketPayload serializes the payload of a packet into the raw byte slice.
// It modifies the order of payload.frames.
func (p *packetPacker) appendPacketPayload(raw []byte, pl payload, paddingLen protocol.ByteCount, v protocol.VersionNumber) ([]byte, error) {
payloadOffset := len(raw)
if pl.ack != nil {
@@ -927,9 +860,21 @@ func (p *packetPacker) appendPacketPayload(raw []byte, pl payload, paddingLen pr
if paddingLen > 0 {
raw = append(raw, make([]byte, paddingLen)...)
}
for _, frame := range pl.frames {
// Randomize the order of the control frames.
// This makes sure that the receiver doesn't rely on the order in which frames are packed.
if len(pl.frames) > 1 {
p.rand.Shuffle(len(pl.frames), func(i, j int) { pl.frames[i], pl.frames[j] = pl.frames[j], pl.frames[i] })
}
for _, f := range pl.frames {
var err error
raw, err = frame.Append(raw, v)
raw, err = f.Frame.Append(raw, v)
if err != nil {
return nil, err
}
}
for _, f := range pl.streamFrames {
var err error
raw, err = f.Frame.Append(raw, v)
if err != nil {
return nil, err
}
@@ -953,16 +898,3 @@ func (p *packetPacker) encryptPacket(raw []byte, sealer sealer, pn protocol.Pack
func (p *packetPacker) SetToken(token []byte) {
p.token = token
}
// When a higher MTU is discovered, use it.
func (p *packetPacker) SetMaxPacketSize(s protocol.ByteCount) {
p.maxPacketSize = s
}
// If the peer sets a max_packet_size that's smaller than the size we're currently using,
// we need to reduce the size of packets we send.
func (p *packetPacker) HandleTransportParameters(params *wire.TransportParameters) {
if params.MaxUDPPayloadSize != 0 {
p.maxPacketSize = utils.Min(p.maxPacketSize, params.MaxUDPPayloadSize)
}
}

View File

@@ -70,25 +70,6 @@ func Read(r io.ByteReader) (uint64, error) {
return uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, nil
}
// Write writes i in the QUIC varint format to w.
// Deprecated: use Append instead.
func Write(w Writer, i uint64) {
if i <= maxVarInt1 {
w.WriteByte(uint8(i))
} else if i <= maxVarInt2 {
w.Write([]byte{uint8(i>>8) | 0x40, uint8(i)})
} else if i <= maxVarInt4 {
w.Write([]byte{uint8(i>>24) | 0x80, uint8(i >> 16), uint8(i >> 8), uint8(i)})
} else if i <= maxVarInt8 {
w.Write([]byte{
uint8(i>>56) | 0xc0, uint8(i >> 48), uint8(i >> 40), uint8(i >> 32),
uint8(i >> 24), uint8(i >> 16), uint8(i >> 8), uint8(i),
})
} else {
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
}
}
// Append appends i in the QUIC varint format.
func Append(b []byte, i uint64) []byte {
if i <= maxVarInt1 {

View File

@@ -179,6 +179,10 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err
if s.readPosInFrame >= len(s.currentFrame) && s.currentFrameIsLast {
s.finRead = true
s.currentFrame = nil
if s.currentFrameDone != nil {
s.currentFrameDone()
}
return true, bytesRead, io.EOF
}
}

View File

@@ -3,6 +3,8 @@ package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
@@ -21,7 +23,23 @@ func newRetransmissionQueue() *retransmissionQueue {
return &retransmissionQueue{}
}
func (q *retransmissionQueue) AddInitial(f wire.Frame) {
// AddPing queues a ping.
// It is used when a probe packet needs to be sent
func (q *retransmissionQueue) AddPing(encLevel protocol.EncryptionLevel) {
//nolint:exhaustive // Cannot send probe packets for 0-RTT.
switch encLevel {
case protocol.EncryptionInitial:
q.addInitial(&wire.PingFrame{})
case protocol.EncryptionHandshake:
q.addHandshake(&wire.PingFrame{})
case protocol.Encryption1RTT:
q.addAppData(&wire.PingFrame{})
default:
panic("unexpected encryption level")
}
}
func (q *retransmissionQueue) addInitial(f wire.Frame) {
if cf, ok := f.(*wire.CryptoFrame); ok {
q.initialCryptoData = append(q.initialCryptoData, cf)
return
@@ -29,7 +47,7 @@ func (q *retransmissionQueue) AddInitial(f wire.Frame) {
q.initial = append(q.initial, f)
}
func (q *retransmissionQueue) AddHandshake(f wire.Frame) {
func (q *retransmissionQueue) addHandshake(f wire.Frame) {
if cf, ok := f.(*wire.CryptoFrame); ok {
q.handshakeCryptoData = append(q.handshakeCryptoData, cf)
return
@@ -49,7 +67,7 @@ func (q *retransmissionQueue) HasAppData() bool {
return len(q.appData) > 0
}
func (q *retransmissionQueue) AddAppData(f wire.Frame) {
func (q *retransmissionQueue) addAppData(f wire.Frame) {
if _, ok := f.(*wire.StreamFrame); ok {
panic("STREAM frames are handled with their respective streams.")
}
@@ -127,3 +145,36 @@ func (q *retransmissionQueue) DropPackets(encLevel protocol.EncryptionLevel) {
panic(fmt.Sprintf("unexpected encryption level: %s", encLevel))
}
}
func (q *retransmissionQueue) InitialAckHandler() ackhandler.FrameHandler {
return (*retransmissionQueueInitialAckHandler)(q)
}
func (q *retransmissionQueue) HandshakeAckHandler() ackhandler.FrameHandler {
return (*retransmissionQueueHandshakeAckHandler)(q)
}
func (q *retransmissionQueue) AppDataAckHandler() ackhandler.FrameHandler {
return (*retransmissionQueueAppDataAckHandler)(q)
}
type retransmissionQueueInitialAckHandler retransmissionQueue
func (q *retransmissionQueueInitialAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueInitialAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addInitial(f)
}
type retransmissionQueueHandshakeAckHandler retransmissionQueue
func (q *retransmissionQueueHandshakeAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueHandshakeAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addHandshake(f)
}
type retransmissionQueueAppDataAckHandler retransmissionQueue
func (q *retransmissionQueueAppDataAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueAppDataAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addAppData(f)
}

View File

@@ -2,52 +2,102 @@ package quic
import (
"net"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
// A sendConn allows sending using a simple Write() on a non-connected packet conn.
type sendConn interface {
Write([]byte) error
Write(b []byte, gsoSize uint16, ecn protocol.ECN) error
Close() error
LocalAddr() net.Addr
RemoteAddr() net.Addr
capabilities() connCapabilities
}
type sconn struct {
rawConn
localAddr net.Addr
remoteAddr net.Addr
info *packetInfo
oob []byte
logger utils.Logger
packetInfoOOB []byte
// If GSO enabled, and we receive a GSO error for this remote address, GSO is disabled.
gotGSOError bool
// Used to catch the error sometimes returned by the first sendmsg call on Linux,
// see https://github.com/golang/go/issues/63322.
wroteFirstPacket bool
}
var _ sendConn = &sconn{}
func newSendConn(c rawConn, remote net.Addr, info *packetInfo) *sconn {
func newSendConn(c rawConn, remote net.Addr, info packetInfo, logger utils.Logger) *sconn {
localAddr := c.LocalAddr()
if info.addr.IsValid() {
if udpAddr, ok := localAddr.(*net.UDPAddr); ok {
addrCopy := *udpAddr
addrCopy.IP = info.addr.AsSlice()
localAddr = &addrCopy
}
}
oob := info.OOB()
// increase oob slice capacity, so we can add the UDP_SEGMENT and ECN control messages without allocating
l := len(oob)
oob = append(oob, make([]byte, 64)...)[:l]
return &sconn{
rawConn: c,
remoteAddr: remote,
info: info,
oob: info.OOB(),
rawConn: c,
localAddr: localAddr,
remoteAddr: remote,
packetInfoOOB: oob,
logger: logger,
}
}
func (c *sconn) Write(p []byte) error {
_, err := c.WritePacket(p, c.remoteAddr, c.oob)
func (c *sconn) Write(p []byte, gsoSize uint16, ecn protocol.ECN) error {
err := c.writePacket(p, c.remoteAddr, c.packetInfoOOB, gsoSize, ecn)
if err != nil && isGSOError(err) {
// disable GSO for future calls
c.gotGSOError = true
if c.logger.Debug() {
c.logger.Debugf("GSO failed when sending to %s", c.remoteAddr)
}
// send out the packets one by one
for len(p) > 0 {
l := len(p)
if l > int(gsoSize) {
l = int(gsoSize)
}
if err := c.writePacket(p[:l], c.remoteAddr, c.packetInfoOOB, 0, ecn); err != nil {
return err
}
p = p[l:]
}
return nil
}
return err
}
func (c *sconn) RemoteAddr() net.Addr {
return c.remoteAddr
func (c *sconn) writePacket(p []byte, addr net.Addr, oob []byte, gsoSize uint16, ecn protocol.ECN) error {
_, err := c.WritePacket(p, addr, oob, gsoSize, ecn)
if err != nil && !c.wroteFirstPacket && isPermissionError(err) {
_, err = c.WritePacket(p, addr, oob, gsoSize, ecn)
}
c.wroteFirstPacket = true
return err
}
func (c *sconn) LocalAddr() net.Addr {
addr := c.rawConn.LocalAddr()
if c.info != nil {
if udpAddr, ok := addr.(*net.UDPAddr); ok {
addrCopy := *udpAddr
addrCopy.IP = c.info.addr
addr = &addrCopy
}
func (c *sconn) capabilities() connCapabilities {
capabilities := c.rawConn.capabilities()
if capabilities.GSO {
capabilities.GSO = !c.gotGSOError
}
return addr
return capabilities
}
func (c *sconn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *sconn) LocalAddr() net.Addr { return c.localAddr }

View File

@@ -1,15 +1,23 @@
package quic
import "github.com/quic-go/quic-go/internal/protocol"
type sender interface {
Send(p *packetBuffer)
Send(p *packetBuffer, gsoSize uint16, ecn protocol.ECN)
Run() error
WouldBlock() bool
Available() <-chan struct{}
Close()
}
type queueEntry struct {
buf *packetBuffer
gsoSize uint16
ecn protocol.ECN
}
type sendQueue struct {
queue chan *packetBuffer
queue chan queueEntry
closeCalled chan struct{} // runStopped when Close() is called
runStopped chan struct{} // runStopped when the run loop returns
available chan struct{}
@@ -26,16 +34,16 @@ func newSendQueue(conn sendConn) sender {
runStopped: make(chan struct{}),
closeCalled: make(chan struct{}),
available: make(chan struct{}, 1),
queue: make(chan *packetBuffer, sendQueueCapacity),
queue: make(chan queueEntry, sendQueueCapacity),
}
}
// Send sends out a packet. It's guaranteed to not block.
// Callers need to make sure that there's actually space in the send queue by calling WouldBlock.
// Otherwise Send will panic.
func (h *sendQueue) Send(p *packetBuffer) {
func (h *sendQueue) Send(p *packetBuffer, gsoSize uint16, ecn protocol.ECN) {
select {
case h.queue <- p:
case h.queue <- queueEntry{buf: p, gsoSize: gsoSize, ecn: ecn}:
// clear available channel if we've reached capacity
if len(h.queue) == sendQueueCapacity {
select {
@@ -69,17 +77,17 @@ func (h *sendQueue) Run() error {
h.closeCalled = nil // prevent this case from being selected again
// make sure that all queued packets are actually sent out
shouldClose = true
case p := <-h.queue:
if err := h.conn.Write(p.Data); err != nil {
case e := <-h.queue:
if err := h.conn.Write(e.buf.Data, e.gsoSize, e.ecn); err != nil {
// This additional check enables:
// 1. Checking for "datagram too large" message from the kernel, as such,
// 2. Path MTU discovery,and
// 3. Eventual detection of loss PingFrame.
if !isMsgSizeErr(err) {
if !isSendMsgSizeErr(err) {
return err
}
}
p.Release()
e.buf.Release()
select {
case h.available <- struct{}{}:
default:

View File

@@ -18,7 +18,7 @@ type sendStreamI interface {
SendStream
handleStopSendingFrame(*wire.StopSendingFrame)
hasData() bool
popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (*ackhandler.Frame, bool)
popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (frame ackhandler.StreamFrame, ok, hasMore bool)
closeForShutdown(error)
updateSendWindow(protocol.ByteCount)
}
@@ -30,7 +30,7 @@ type sendStream struct {
retransmissionQueue []*wire.StreamFrame
ctx context.Context
ctxCancel context.CancelFunc
ctxCancel context.CancelCauseFunc
streamID protocol.StreamID
sender streamSender
@@ -71,7 +71,7 @@ func newSendStream(
writeChan: make(chan struct{}, 1),
writeOnce: make(chan struct{}, 1), // cap: 1, to protect against concurrent use of Write
}
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
s.ctx, s.ctxCancel = context.WithCancelCause(context.Background())
return s
}
@@ -198,7 +198,7 @@ func (s *sendStream) canBufferStreamFrame() bool {
// popStreamFrame returns the next STREAM frame that is supposed to be sent on this stream
// maxBytes is the maximum length this frame (including frame header) will have.
func (s *sendStream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (*ackhandler.Frame, bool /* has more data to send */) {
func (s *sendStream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (af ackhandler.StreamFrame, ok, hasMore bool) {
s.mutex.Lock()
f, hasMoreData := s.popNewOrRetransmittedStreamFrame(maxBytes, v)
if f != nil {
@@ -207,13 +207,12 @@ func (s *sendStream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.Vers
s.mutex.Unlock()
if f == nil {
return nil, hasMoreData
return ackhandler.StreamFrame{}, false, hasMoreData
}
af := ackhandler.GetFrame()
af.Frame = f
af.OnLost = s.queueRetransmission
af.OnAcked = s.frameAcked
return af, hasMoreData
return ackhandler.StreamFrame{
Frame: f,
Handler: (*sendStreamAckHandler)(s),
}, true, hasMoreData
}
func (s *sendStream) popNewOrRetransmittedStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (*wire.StreamFrame, bool /* has more data to send */) {
@@ -348,26 +347,6 @@ func (s *sendStream) getDataForWriting(f *wire.StreamFrame, maxBytes protocol.By
}
}
func (s *sendStream) frameAcked(f wire.Frame) {
f.(*wire.StreamFrame).PutBack()
s.mutex.Lock()
if s.cancelWriteErr != nil {
s.mutex.Unlock()
return
}
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
newlyCompleted := s.isNewlyCompleted()
s.mutex.Unlock()
if newlyCompleted {
s.sender.onStreamCompleted(s.streamID)
}
}
func (s *sendStream) isNewlyCompleted() bool {
completed := (s.finSent || s.cancelWriteErr != nil) && s.numOutstandingFrames == 0 && len(s.retransmissionQueue) == 0
if completed && !s.completed {
@@ -377,24 +356,6 @@ func (s *sendStream) isNewlyCompleted() bool {
return false
}
func (s *sendStream) queueRetransmission(f wire.Frame) {
sf := f.(*wire.StreamFrame)
sf.DataLenPresent = true
s.mutex.Lock()
if s.cancelWriteErr != nil {
s.mutex.Unlock()
return
}
s.retransmissionQueue = append(s.retransmissionQueue, sf)
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
s.mutex.Unlock()
s.sender.onHasStreamData(s.streamID)
}
func (s *sendStream) Close() error {
s.mutex.Lock()
if s.closeForShutdownErr != nil {
@@ -405,7 +366,7 @@ func (s *sendStream) Close() error {
s.mutex.Unlock()
return fmt.Errorf("close called for canceled stream %d", s.streamID)
}
s.ctxCancel()
s.ctxCancel(nil)
s.finishedWriting = true
s.mutex.Unlock()
@@ -424,8 +385,8 @@ func (s *sendStream) cancelWriteImpl(errorCode qerr.StreamErrorCode, remote bool
s.mutex.Unlock()
return
}
s.ctxCancel()
s.cancelWriteErr = &StreamError{StreamID: s.streamID, ErrorCode: errorCode, Remote: remote}
s.ctxCancel(s.cancelWriteErr)
s.numOutstandingFrames = 0
s.retransmissionQueue = nil
newlyCompleted := s.isNewlyCompleted()
@@ -474,7 +435,7 @@ func (s *sendStream) SetWriteDeadline(t time.Time) error {
// The peer will NOT be informed about this: the stream is closed without sending a FIN or RST.
func (s *sendStream) closeForShutdown(err error) {
s.mutex.Lock()
s.ctxCancel()
s.ctxCancel(err)
s.closeForShutdownErr = err
s.mutex.Unlock()
s.signalWrite()
@@ -487,3 +448,45 @@ func (s *sendStream) signalWrite() {
default:
}
}
type sendStreamAckHandler sendStream
var _ ackhandler.FrameHandler = &sendStreamAckHandler{}
func (s *sendStreamAckHandler) OnAcked(f wire.Frame) {
sf := f.(*wire.StreamFrame)
sf.PutBack()
s.mutex.Lock()
if s.cancelWriteErr != nil {
s.mutex.Unlock()
return
}
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
newlyCompleted := (*sendStream)(s).isNewlyCompleted()
s.mutex.Unlock()
if newlyCompleted {
s.sender.onStreamCompleted(s.streamID)
}
}
func (s *sendStreamAckHandler) OnLost(f wire.Frame) {
sf := f.(*wire.StreamFrame)
s.mutex.Lock()
if s.cancelWriteErr != nil {
s.mutex.Unlock()
return
}
sf.DataLenPresent = true
s.retransmissionQueue = append(s.retransmissionQueue, sf)
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
s.mutex.Unlock()
s.sender.onHasStreamData(s.streamID)
}

View File

@@ -2,7 +2,6 @@ package quic
import (
"context"
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
@@ -24,7 +23,7 @@ var ErrServerClosed = errors.New("quic: server closed")
// packetHandler handles packets
type packetHandler interface {
handlePacket(*receivedPacket)
handlePacket(receivedPacket)
shutdown()
destroy(error)
getPerspective() protocol.Perspective
@@ -35,14 +34,13 @@ type packetHandlerManager interface {
GetByResetToken(protocol.StatelessResetToken) (packetHandler, bool)
AddWithConnID(protocol.ConnectionID, protocol.ConnectionID, func() (packetHandler, bool)) bool
Close(error)
CloseServer()
connRunner
}
type quicConn interface {
EarlyConnection
earlyConnReady() <-chan struct{}
handlePacket(*receivedPacket)
handlePacket(receivedPacket)
GetVersion() protocol.VersionNumber
getPerspective() protocol.Perspective
run() error
@@ -51,15 +49,19 @@ type quicConn interface {
}
type zeroRTTQueue struct {
packets []*receivedPacket
packets []receivedPacket
expiration time.Time
}
type rejectedPacket struct {
receivedPacket
hdr *wire.Header
}
// A Listener of QUIC
type baseServer struct {
mutex sync.Mutex
acceptEarlyConns bool
disableVersionNegotiation bool
acceptEarlyConns bool
tlsConf *tls.Config
config *Config
@@ -67,12 +69,13 @@ type baseServer struct {
conn rawConn
tokenGenerator *handshake.TokenGenerator
maxTokenAge time.Duration
connIDGenerator ConnectionIDGenerator
connHandler packetHandlerManager
onClose func()
receivedPackets chan *receivedPacket
receivedPackets chan receivedPacket
nextZeroRTTCleanup time.Time
zeroRTTQueues map[protocol.ConnectionID]*zeroRTTQueue // only initialized if acceptEarlyConns == true
@@ -92,21 +95,26 @@ type baseServer struct {
*tls.Config,
*handshake.TokenGenerator,
bool, /* client address validated by an address validation token */
logging.ConnectionTracer,
*logging.ConnectionTracer,
uint64,
utils.Logger,
protocol.VersionNumber,
) quicConn
serverError error
errorChan chan struct{}
closed bool
running chan struct{} // closed as soon as run() returns
closeOnce sync.Once
errorChan chan struct{} // is closed when the server is closed
closeErr error
running chan struct{} // closed as soon as run() returns
versionNegotiationQueue chan receivedPacket
invalidTokenQueue chan rejectedPacket
connectionRefusedQueue chan rejectedPacket
retryQueue chan rejectedPacket
connQueue chan quicConn
connQueueLen int32 // to be used as an atomic
tracer logging.Tracer
tracer *logging.Tracer
logger utils.Logger
}
@@ -122,7 +130,12 @@ func (l *Listener) Accept(ctx context.Context) (Connection, error) {
return l.baseServer.Accept(ctx)
}
// Close the server. All active connections will be closed.
// Close closes the listener.
// Accept will return ErrServerClosed as soon as all connections in the accept queue have been accepted.
// QUIC handshakes that are still in flight will be rejected with a CONNECTION_REFUSED error.
// The effect of closing the listener depends on how it was created:
// * if it was created using Transport.Listen, already established connections will be unaffected
// * if it was created using the Listen convenience method, all established connection will be closed immediately
func (l *Listener) Close() error {
return l.baseServer.Close()
}
@@ -158,8 +171,7 @@ func (l *EarlyListener) Addr() net.Addr {
}
// ListenAddr creates a QUIC server listening on a given address.
// The tls.Config must not be nil and must contain a certificate configuration.
// The quic.Config may be nil, in that case the default values will be used.
// See Listen for more details.
func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (*Listener, error) {
conn, err := listenUDP(addr)
if err != nil {
@@ -193,16 +205,20 @@ func listenUDP(addr string) (*net.UDPConn, error) {
return net.ListenUDP("udp", udpAddr)
}
// Listen listens for QUIC connections on a given net.PacketConn. If the
// PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn
// does), ECN and packet info support will be enabled. In this case, ReadMsgUDP
// and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write
// packets. A single net.PacketConn only be used for a single call to Listen.
// The PacketConn can be used for simultaneous calls to Dial. QUIC connection
// IDs are used for demultiplexing the different connections.
// Listen listens for QUIC connections on a given net.PacketConn.
// If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does),
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
// will be used instead of ReadFrom and WriteTo to read/write packets.
// A single net.PacketConn can only be used for a single call to Listen.
//
// The tls.Config must not be nil and must contain a certificate configuration.
// Furthermore, it must define an application control (using NextProtos).
// The quic.Config may be nil, in that case the default values will be used.
//
// This is a convenience function. More advanced use cases should instantiate a Transport,
// which offers configuration options for a more fine-grained control of the connection establishment,
// including reusing the underlying UDP socket for outgoing QUIC connections.
// When closing a listener created with Listen, all established QUIC connections will be closed immediately.
func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*Listener, error) {
tr := &Transport{Conn: conn, isSingleUse: true}
return tr.Listen(tlsConf, config)
@@ -220,37 +236,43 @@ func newServer(
connIDGenerator ConnectionIDGenerator,
tlsConf *tls.Config,
config *Config,
tracer logging.Tracer,
tracer *logging.Tracer,
onClose func(),
tokenGeneratorKey TokenGeneratorKey,
maxTokenAge time.Duration,
disableVersionNegotiation bool,
acceptEarly bool,
) (*baseServer, error) {
tokenGenerator, err := handshake.NewTokenGenerator(rand.Reader)
if err != nil {
return nil, err
}
) *baseServer {
s := &baseServer{
conn: conn,
tlsConf: tlsConf,
config: config,
tokenGenerator: tokenGenerator,
connIDGenerator: connIDGenerator,
connHandler: connHandler,
connQueue: make(chan quicConn),
errorChan: make(chan struct{}),
running: make(chan struct{}),
receivedPackets: make(chan *receivedPacket, protocol.MaxServerUnprocessedPackets),
newConn: newConnection,
tracer: tracer,
logger: utils.DefaultLogger.WithPrefix("server"),
acceptEarlyConns: acceptEarly,
onClose: onClose,
conn: conn,
tlsConf: tlsConf,
config: config,
tokenGenerator: handshake.NewTokenGenerator(tokenGeneratorKey),
maxTokenAge: maxTokenAge,
connIDGenerator: connIDGenerator,
connHandler: connHandler,
connQueue: make(chan quicConn),
errorChan: make(chan struct{}),
running: make(chan struct{}),
receivedPackets: make(chan receivedPacket, protocol.MaxServerUnprocessedPackets),
versionNegotiationQueue: make(chan receivedPacket, 4),
invalidTokenQueue: make(chan rejectedPacket, 4),
connectionRefusedQueue: make(chan rejectedPacket, 4),
retryQueue: make(chan rejectedPacket, 8),
newConn: newConnection,
tracer: tracer,
logger: utils.DefaultLogger.WithPrefix("server"),
acceptEarlyConns: acceptEarly,
disableVersionNegotiation: disableVersionNegotiation,
onClose: onClose,
}
if acceptEarly {
s.zeroRTTQueues = map[protocol.ConnectionID]*zeroRTTQueue{}
}
go s.run()
go s.runSendQueue()
s.logger.Debugf("Listening for %s connections on %s", conn.LocalAddr().Network(), conn.LocalAddr().String())
return s, nil
return s
}
func (s *baseServer) run() {
@@ -272,6 +294,23 @@ func (s *baseServer) run() {
}
}
func (s *baseServer) runSendQueue() {
for {
select {
case <-s.running:
return
case p := <-s.versionNegotiationQueue:
s.maybeSendVersionNegotiationPacket(p)
case p := <-s.invalidTokenQueue:
s.maybeSendInvalidToken(p)
case p := <-s.connectionRefusedQueue:
s.sendConnectionRefused(p)
case p := <-s.retryQueue:
s.sendRetry(p)
}
}
}
// Accept returns connections that already completed the handshake.
// It is only valid if acceptEarlyConns is false.
func (s *baseServer) Accept(ctx context.Context) (Connection, error) {
@@ -286,38 +325,25 @@ func (s *baseServer) accept(ctx context.Context) (quicConn, error) {
atomic.AddInt32(&s.connQueueLen, -1)
return conn, nil
case <-s.errorChan:
return nil, s.serverError
return nil, s.closeErr
}
}
// Close the server
func (s *baseServer) Close() error {
s.mutex.Lock()
if s.closed {
s.mutex.Unlock()
return nil
}
if s.serverError == nil {
s.serverError = ErrServerClosed
}
s.closed = true
close(s.errorChan)
s.mutex.Unlock()
<-s.running
s.onClose()
s.close(ErrServerClosed, true)
return nil
}
func (s *baseServer) setCloseError(e error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.closed {
return
}
s.closed = true
s.serverError = e
close(s.errorChan)
func (s *baseServer) close(e error, notifyOnClose bool) {
s.closeOnce.Do(func() {
s.closeErr = e
close(s.errorChan)
<-s.running
if notifyOnClose {
s.onClose()
}
})
}
// Addr returns the server's network address
@@ -325,25 +351,25 @@ func (s *baseServer) Addr() net.Addr {
return s.conn.LocalAddr()
}
func (s *baseServer) handlePacket(p *receivedPacket) {
func (s *baseServer) handlePacket(p receivedPacket) {
select {
case s.receivedPackets <- p:
default:
s.logger.Debugf("Dropping packet from %s (%d bytes). Server receive queue full.", p.remoteAddr, p.Size())
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention)
}
}
}
func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer still in use? */ {
func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer still in use? */ {
if !s.nextZeroRTTCleanup.IsZero() && p.rcvTime.After(s.nextZeroRTTCleanup) {
defer s.cleanupZeroRTTQueues(p.rcvTime)
}
if wire.IsVersionNegotiationPacket(p.data) {
s.logger.Debugf("Dropping Version Negotiation packet.")
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
@@ -353,32 +379,33 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
panic(fmt.Sprintf("misrouted packet: %#v", p.data))
}
v, err := wire.ParseVersion(p.data)
// send a Version Negotiation Packet if the client is speaking a different protocol version
if err != nil || !protocol.IsSupportedVersion(s.config.Versions, v) {
if err != nil || p.Size() < protocol.MinUnknownVersionPacketSize {
s.logger.Debugf("Dropping a packet with an unknown version that is too small (%d bytes)", p.Size())
if s.tracer != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
}
_, src, dest, err := wire.ParseArbitraryLenConnectionIDs(p.data)
if err != nil { // should never happen
s.logger.Debugf("Dropping a packet with an unknown version for which we failed to parse connection IDs")
if s.tracer != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
}
if !s.config.DisableVersionNegotiationPackets {
go s.sendVersionNegotiationPacket(p.remoteAddr, src, dest, p.info.OOB(), v)
// drop the packet if we failed to parse the protocol version
if err != nil {
s.logger.Debugf("Dropping a packet with an unknown version")
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
}
// send a Version Negotiation Packet if the client is speaking a different protocol version
if !protocol.IsSupportedVersion(s.config.Versions, v) {
if s.disableVersionNegotiation {
return false
}
if p.Size() < protocol.MinUnknownVersionPacketSize {
s.logger.Debugf("Dropping a packet with an unsupported version number %d that is too small (%d bytes)", v, p.Size())
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
}
return s.enqueueVersionNegotiationPacket(p)
}
if wire.Is0RTTPacket(p.data) {
if !s.acceptEarlyConns {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
@@ -390,7 +417,7 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
// The header will then be parsed again.
hdr, _, _, err := wire.ParsePacket(p.data)
if err != nil {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError)
}
s.logger.Debugf("Error parsing packet: %s", err)
@@ -398,7 +425,7 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
}
if hdr.Type == protocol.PacketTypeInitial && p.Size() < protocol.MinInitialPacketSize {
s.logger.Debugf("Dropping a packet that is too small to be a valid Initial (%d bytes)", p.Size())
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
@@ -409,7 +436,7 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
// There's little point in sending a Stateless Reset, since the client
// might not have received the token yet.
s.logger.Debugf("Dropping long header packet of type %s (%d bytes)", hdr.Type, len(p.data))
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeFromHeader(hdr), p.Size(), logging.PacketDropUnexpectedPacket)
}
return false
@@ -425,10 +452,10 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
return true
}
func (s *baseServer) handle0RTTPacket(p *receivedPacket) bool {
func (s *baseServer) handle0RTTPacket(p receivedPacket) bool {
connID, err := wire.ParseConnectionID(p.data, 0)
if err != nil {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropHeaderParseError)
}
return false
@@ -442,7 +469,7 @@ func (s *baseServer) handle0RTTPacket(p *receivedPacket) bool {
if q, ok := s.zeroRTTQueues[connID]; ok {
if len(q.packets) >= protocol.Max0RTTQueueLen {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
}
return false
@@ -452,12 +479,12 @@ func (s *baseServer) handle0RTTPacket(p *receivedPacket) bool {
}
if len(s.zeroRTTQueues) >= protocol.Max0RTTQueues {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
}
return false
}
queue := &zeroRTTQueue{packets: make([]*receivedPacket, 1, 8)}
queue := &zeroRTTQueue{packets: make([]receivedPacket, 1, 8)}
queue.packets[0] = p
expiration := p.rcvTime.Add(protocol.Max0RTTQueueingDuration)
queue.expiration = expiration
@@ -480,7 +507,7 @@ func (s *baseServer) cleanupZeroRTTQueues(now time.Time) {
continue
}
for _, p := range q.packets {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
}
p.buffer.Release()
@@ -504,19 +531,19 @@ func (s *baseServer) validateToken(token *handshake.Token, addr net.Addr) bool {
if !token.ValidateRemoteAddr(addr) {
return false
}
if !token.IsRetryToken && time.Since(token.SentTime) > s.config.MaxTokenAge {
if !token.IsRetryToken && time.Since(token.SentTime) > s.maxTokenAge {
return false
}
if token.IsRetryToken && time.Since(token.SentTime) > s.config.MaxRetryTokenAge {
if token.IsRetryToken && time.Since(token.SentTime) > s.config.maxRetryTokenAge() {
return false
}
return true
}
func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) error {
func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error {
if len(hdr.Token) == 0 && hdr.DestConnectionID.Len() < protocol.MinConnectionIDLenInitial {
p.buffer.Release()
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket)
}
return errors.New("too short connection ID")
@@ -557,35 +584,35 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
// For Retry tokens, we send an INVALID_ERROR if
// * the token is too old, or
// * the token is invalid, in case of a retry token.
go func() {
defer p.buffer.Release()
if err := s.maybeSendInvalidToken(p, hdr); err != nil {
s.logger.Debugf("Error sending INVALID_TOKEN error: %s", err)
}
}()
select {
case s.invalidTokenQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out the INVALID_TOKEN packets fast enough
p.buffer.Release()
}
return nil
}
}
if token == nil && s.config.RequireAddressValidation(p.remoteAddr) {
// Retry invalidates all 0-RTT packets sent.
delete(s.zeroRTTQueues, hdr.DestConnectionID)
go func() {
defer p.buffer.Release()
if err := s.sendRetry(p.remoteAddr, hdr, p.info); err != nil {
s.logger.Debugf("Error sending Retry: %s", err)
}
}()
select {
case s.retryQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out Retry packets fast enough
p.buffer.Release()
}
return nil
}
if queueLen := atomic.LoadInt32(&s.connQueueLen); queueLen >= protocol.MaxAcceptQueueSize {
s.logger.Debugf("Rejecting new connection. Server currently busy. Accept queue length: %d (max %d)", queueLen, protocol.MaxAcceptQueueSize)
go func() {
defer p.buffer.Release()
if err := s.sendConnectionRefused(p.remoteAddr, hdr, p.info); err != nil {
s.logger.Debugf("Error rejecting connection: %s", err)
}
}()
select {
case s.connectionRefusedQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out the CONNECTION_REFUSED fast enough
p.buffer.Release()
}
return nil
}
@@ -606,7 +633,7 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
}
config = populateConfig(conf)
}
var tracer logging.ConnectionTracer
var tracer *logging.ConnectionTracer
if config.Tracer != nil {
// Use the same connection ID that is passed to the client's GetLogWriter callback.
connID := hdr.DestConnectionID
@@ -616,7 +643,7 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
tracer = config.Tracer(context.WithValue(context.Background(), ConnectionTracingKey, tracingID), protocol.PerspectiveServer, connID)
}
conn = s.newConn(
newSendConn(s.conn, p.remoteAddr, p.info),
newSendConn(s.conn, p.remoteAddr, p.info, s.logger),
s.connHandler,
origDestConnID,
retrySrcConnID,
@@ -645,12 +672,12 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
return conn, true
}); !added {
go func() {
defer p.buffer.Release()
if err := s.sendConnectionRefused(p.remoteAddr, hdr, p.info); err != nil {
s.logger.Debugf("Error rejecting connection: %s", err)
}
}()
select {
case s.connectionRefusedQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out the CONNECTION_REFUSED fast enough
p.buffer.Release()
}
return nil
}
go conn.run()
@@ -665,8 +692,11 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
func (s *baseServer) handleNewConn(conn quicConn) {
connCtx := conn.Context()
if s.acceptEarlyConns {
// wait until the early connection is ready (or the handshake fails)
// wait until the early connection is ready, the handshake fails, or the server is closed
select {
case <-s.errorChan:
conn.destroy(&qerr.TransportError{ErrorCode: ConnectionRefused})
return
case <-conn.earlyConnReady():
case <-connCtx.Done():
return
@@ -674,6 +704,9 @@ func (s *baseServer) handleNewConn(conn quicConn) {
} else {
// wait until the handshake is complete (or fails)
select {
case <-s.errorChan:
conn.destroy(&qerr.TransportError{ErrorCode: ConnectionRefused})
return
case <-conn.HandshakeComplete():
case <-connCtx.Done():
return
@@ -690,7 +723,14 @@ func (s *baseServer) handleNewConn(conn quicConn) {
}
}
func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header, info *packetInfo) error {
func (s *baseServer) sendRetry(p rejectedPacket) {
if err := s.sendRetryPacket(p); err != nil {
s.logger.Debugf("Error sending Retry packet: %s", err)
}
}
func (s *baseServer) sendRetryPacket(p rejectedPacket) error {
hdr := p.hdr
// Log the Initial packet now.
// If no Retry is sent, the packet will be logged by the connection.
(&wire.ExtendedHeader{Header: *hdr}).Log(s.logger)
@@ -698,7 +738,7 @@ func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header, info *pack
if err != nil {
return err
}
token, err := s.tokenGenerator.NewRetryToken(remoteAddr, hdr.DestConnectionID, srcConnID)
token, err := s.tokenGenerator.NewRetryToken(p.remoteAddr, hdr.DestConnectionID, srcConnID)
if err != nil {
return err
}
@@ -723,47 +763,55 @@ func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header, info *pack
// append the Retry integrity tag
tag := handshake.GetRetryIntegrityTag(buf.Data, hdr.DestConnectionID, hdr.Version)
buf.Data = append(buf.Data, tag[:]...)
if s.tracer != nil {
s.tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(buf.Data)), nil)
if s.tracer != nil && s.tracer.SentPacket != nil {
s.tracer.SentPacket(p.remoteAddr, &replyHdr.Header, protocol.ByteCount(len(buf.Data)), nil)
}
_, err = s.conn.WritePacket(buf.Data, remoteAddr, info.OOB())
_, err = s.conn.WritePacket(buf.Data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported)
return err
}
func (s *baseServer) maybeSendInvalidToken(p *receivedPacket, hdr *wire.Header) error {
func (s *baseServer) maybeSendInvalidToken(p rejectedPacket) {
defer p.buffer.Release()
// Only send INVALID_TOKEN if we can unprotect the packet.
// This makes sure that we won't send it for packets that were corrupted.
hdr := p.hdr
sealer, opener := handshake.NewInitialAEAD(hdr.DestConnectionID, protocol.PerspectiveServer, hdr.Version)
data := p.data[:hdr.ParsedLen()+hdr.Length]
extHdr, err := unpackLongHeader(opener, hdr, data, hdr.Version)
// Only send INVALID_TOKEN if we can unprotect the packet.
// This makes sure that we won't send it for packets that were corrupted.
if err != nil {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropHeaderParseError)
}
// don't return the error here. Just drop the packet.
return nil
return
}
hdrLen := extHdr.ParsedLen()
if _, err := opener.Open(data[hdrLen:hdrLen], data[hdrLen:], extHdr.PacketNumber, data[:hdrLen]); err != nil {
// don't return the error here. Just drop the packet.
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropPayloadDecryptError)
}
return nil
return
}
if s.logger.Debug() {
s.logger.Debugf("Client sent an invalid retry token. Sending INVALID_TOKEN to %s.", p.remoteAddr)
}
return s.sendError(p.remoteAddr, hdr, sealer, qerr.InvalidToken, p.info)
if err := s.sendError(p.remoteAddr, hdr, sealer, qerr.InvalidToken, p.info); err != nil {
s.logger.Debugf("Error sending INVALID_TOKEN error: %s", err)
}
}
func (s *baseServer) sendConnectionRefused(remoteAddr net.Addr, hdr *wire.Header, info *packetInfo) error {
sealer, _ := handshake.NewInitialAEAD(hdr.DestConnectionID, protocol.PerspectiveServer, hdr.Version)
return s.sendError(remoteAddr, hdr, sealer, qerr.ConnectionRefused, info)
func (s *baseServer) sendConnectionRefused(p rejectedPacket) {
defer p.buffer.Release()
sealer, _ := handshake.NewInitialAEAD(p.hdr.DestConnectionID, protocol.PerspectiveServer, p.hdr.Version)
if err := s.sendError(p.remoteAddr, p.hdr, sealer, qerr.ConnectionRefused, p.info); err != nil {
s.logger.Debugf("Error sending CONNECTION_REFUSED error: %s", err)
}
}
// sendError sends the error as a response to the packet received with header hdr
func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer handshake.LongHeaderSealer, errorCode qerr.TransportErrorCode, info *packetInfo) error {
func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer handshake.LongHeaderSealer, errorCode qerr.TransportErrorCode, info packetInfo) error {
b := getPacketBuffer()
defer b.Release()
@@ -800,21 +848,48 @@ func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer han
replyHdr.Log(s.logger)
wire.LogFrame(s.logger, ccf, true)
if s.tracer != nil {
if s.tracer != nil && s.tracer.SentPacket != nil {
s.tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(b.Data)), []logging.Frame{ccf})
}
_, err = s.conn.WritePacket(b.Data, remoteAddr, info.OOB())
_, err = s.conn.WritePacket(b.Data, remoteAddr, info.OOB(), 0, protocol.ECNUnsupported)
return err
}
func (s *baseServer) sendVersionNegotiationPacket(remote net.Addr, src, dest protocol.ArbitraryLenConnectionID, oob []byte, v protocol.VersionNumber) {
func (s *baseServer) enqueueVersionNegotiationPacket(p receivedPacket) (bufferInUse bool) {
select {
case s.versionNegotiationQueue <- p:
return true
default:
// it's fine to not send version negotiation packets when we are busy
}
return false
}
func (s *baseServer) maybeSendVersionNegotiationPacket(p receivedPacket) {
defer p.buffer.Release()
v, err := wire.ParseVersion(p.data)
if err != nil {
s.logger.Debugf("failed to parse version for sending version negotiation packet: %s", err)
return
}
_, src, dest, err := wire.ParseArbitraryLenConnectionIDs(p.data)
if err != nil { // should never happen
s.logger.Debugf("Dropping a packet with an unknown version for which we failed to parse connection IDs")
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
}
return
}
s.logger.Debugf("Client offered version %s, sending Version Negotiation", v)
data := wire.ComposeVersionNegotiation(dest, src, s.config.Versions)
if s.tracer != nil {
s.tracer.SentVersionNegotiationPacket(remote, src, dest, s.config.Versions)
if s.tracer != nil && s.tracer.SentVersionNegotiationPacket != nil {
s.tracer.SentVersionNegotiationPacket(p.remoteAddr, src, dest, s.config.Versions)
}
if _, err := s.conn.WritePacket(data, remote, oob); err != nil {
if _, err := s.conn.WritePacket(data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported); err != nil {
s.logger.Debugf("Error sending Version Negotiation: %s", err)
}
}

View File

@@ -60,7 +60,7 @@ type streamI interface {
// for sending
hasData() bool
handleStopSendingFrame(*wire.StopSendingFrame)
popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (*ackhandler.Frame, bool)
popStreamFrame(maxBytes protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool)
updateSendWindow(protocol.ByteCount)
}

View File

@@ -1,7 +1,11 @@
package quic
import (
"log"
"net"
"os"
"strconv"
"strings"
"syscall"
"time"
@@ -15,6 +19,7 @@ import (
type OOBCapablePacketConn interface {
net.PacketConn
SyscallConn() (syscall.RawConn, error)
SetReadBuffer(int) error
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}
@@ -22,9 +27,31 @@ type OOBCapablePacketConn interface {
var _ OOBCapablePacketConn = &net.UDPConn{}
func wrapConn(pc net.PacketConn) (rawConn, error) {
if err := setReceiveBuffer(pc); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
setBufferWarningOnce.Do(func() {
if disable, _ := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING")); disable {
return
}
log.Printf("%s. See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.", err)
})
}
}
if err := setSendBuffer(pc); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
setBufferWarningOnce.Do(func() {
if disable, _ := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING")); disable {
return
}
log.Printf("%s. See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.", err)
})
}
}
conn, ok := pc.(interface {
SyscallConn() (syscall.RawConn, error)
})
var supportsDF bool
if ok {
rawConn, err := conn.SyscallConn()
if err != nil {
@@ -33,7 +60,8 @@ func wrapConn(pc net.PacketConn) (rawConn, error) {
if _, ok := pc.LocalAddr().(*net.UDPAddr); ok {
// Only set DF on sockets that we expect to be able to handle that configuration.
err = setDF(rawConn)
var err error
supportsDF, err = setDF(rawConn)
if err != nil {
return nil, err
}
@@ -42,32 +70,33 @@ func wrapConn(pc net.PacketConn) (rawConn, error) {
c, ok := pc.(OOBCapablePacketConn)
if !ok {
utils.DefaultLogger.Infof("PacketConn is not a net.UDPConn. Disabling optimizations possible on UDP connections.")
return &basicConn{PacketConn: pc}, nil
return &basicConn{PacketConn: pc, supportsDF: supportsDF}, nil
}
return newConn(c)
return newConn(c, supportsDF)
}
// The basicConn is the most trivial implementation of a connection.
// The basicConn is the most trivial implementation of a rawConn.
// It reads a single packet from the underlying net.PacketConn.
// It is used when
// * the net.PacketConn is not a OOBCapablePacketConn, and
// * when the OS doesn't support OOB.
type basicConn struct {
net.PacketConn
supportsDF bool
}
var _ rawConn = &basicConn{}
func (c *basicConn) ReadPacket() (*receivedPacket, error) {
func (c *basicConn) ReadPacket() (receivedPacket, error) {
buffer := getPacketBuffer()
// The packet size should not exceed protocol.MaxPacketBufferSize bytes
// If it does, we only read a truncated packet, which will then end up undecryptable
buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize]
n, addr, err := c.PacketConn.ReadFrom(buffer.Data)
if err != nil {
return nil, err
return receivedPacket{}, err
}
return &receivedPacket{
return receivedPacket{
remoteAddr: addr,
rcvTime: time.Now(),
data: buffer.Data[:n],
@@ -75,6 +104,14 @@ func (c *basicConn) ReadPacket() (*receivedPacket, error) {
}, nil
}
func (c *basicConn) WritePacket(b []byte, addr net.Addr, _ []byte) (n int, err error) {
func (c *basicConn) WritePacket(b []byte, addr net.Addr, _ []byte, gsoSize uint16, ecn protocol.ECN) (n int, err error) {
if gsoSize != 0 {
panic("cannot use GSO with a basicConn")
}
if ecn != protocol.ECNUnsupported {
panic("cannot use ECN with a basicConn")
}
return c.PacketConn.WriteTo(b, addr)
}
func (c *basicConn) capabilities() connCapabilities { return connCapabilities{DF: c.supportsDF} }

68
vendor/github.com/quic-go/quic-go/sys_conn_buffers.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package quic
import (
"errors"
"fmt"
"net"
"syscall"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
//go:generate sh -c "echo '// Code generated by go generate. DO NOT EDIT.\n// Source: sys_conn_buffers.go\n' > sys_conn_buffers_write.go && sed -e 's/SetReadBuffer/SetWriteBuffer/g' -e 's/setReceiveBuffer/setSendBuffer/g' -e 's/inspectReadBuffer/inspectWriteBuffer/g' -e 's/protocol\\.DesiredReceiveBufferSize/protocol\\.DesiredSendBufferSize/g' -e 's/forceSetReceiveBuffer/forceSetSendBuffer/g' -e 's/receive buffer/send buffer/g' sys_conn_buffers.go | sed '/^\\/\\/go:generate/d' >> sys_conn_buffers_write.go"
func setReceiveBuffer(c net.PacketConn) error {
conn, ok := c.(interface{ SetReadBuffer(int) error })
if !ok {
return errors.New("connection doesn't allow setting of receive buffer size. Not a *net.UDPConn?")
}
var syscallConn syscall.RawConn
if sc, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
}); ok {
var err error
syscallConn, err = sc.SyscallConn()
if err != nil {
syscallConn = nil
}
}
// The connection has a SetReadBuffer method, but we couldn't obtain a syscall.RawConn.
// This shouldn't happen for a net.UDPConn, but is possible if the connection just implements the
// net.PacketConn interface and the SetReadBuffer method.
// We have no way of checking if increasing the buffer size actually worked.
if syscallConn == nil {
return conn.SetReadBuffer(protocol.DesiredReceiveBufferSize)
}
size, err := inspectReadBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
if size >= protocol.DesiredReceiveBufferSize {
utils.DefaultLogger.Debugf("Conn has receive buffer of %d kiB (wanted: at least %d kiB)", size/1024, protocol.DesiredReceiveBufferSize/1024)
return nil
}
// Ignore the error. We check if we succeeded by querying the buffer size afterward.
_ = conn.SetReadBuffer(protocol.DesiredReceiveBufferSize)
newSize, err := inspectReadBuffer(syscallConn)
if newSize < protocol.DesiredReceiveBufferSize {
// Try again with RCVBUFFORCE on Linux
_ = forceSetReceiveBuffer(syscallConn, protocol.DesiredReceiveBufferSize)
newSize, err = inspectReadBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
}
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
if newSize == size {
return fmt.Errorf("failed to increase receive buffer size (wanted: %d kiB, got %d kiB)", protocol.DesiredReceiveBufferSize/1024, newSize/1024)
}
if newSize < protocol.DesiredReceiveBufferSize {
return fmt.Errorf("failed to sufficiently increase receive buffer size (was: %d kiB, wanted: %d kiB, got: %d kiB)", size/1024, protocol.DesiredReceiveBufferSize/1024, newSize/1024)
}
utils.DefaultLogger.Debugf("Increased receive buffer size to %d kiB", newSize/1024)
return nil
}

View File

@@ -0,0 +1,70 @@
// Code generated by go generate. DO NOT EDIT.
// Source: sys_conn_buffers.go
package quic
import (
"errors"
"fmt"
"net"
"syscall"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
func setSendBuffer(c net.PacketConn) error {
conn, ok := c.(interface{ SetWriteBuffer(int) error })
if !ok {
return errors.New("connection doesn't allow setting of send buffer size. Not a *net.UDPConn?")
}
var syscallConn syscall.RawConn
if sc, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
}); ok {
var err error
syscallConn, err = sc.SyscallConn()
if err != nil {
syscallConn = nil
}
}
// The connection has a SetWriteBuffer method, but we couldn't obtain a syscall.RawConn.
// This shouldn't happen for a net.UDPConn, but is possible if the connection just implements the
// net.PacketConn interface and the SetWriteBuffer method.
// We have no way of checking if increasing the buffer size actually worked.
if syscallConn == nil {
return conn.SetWriteBuffer(protocol.DesiredSendBufferSize)
}
size, err := inspectWriteBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
if size >= protocol.DesiredSendBufferSize {
utils.DefaultLogger.Debugf("Conn has send buffer of %d kiB (wanted: at least %d kiB)", size/1024, protocol.DesiredSendBufferSize/1024)
return nil
}
// Ignore the error. We check if we succeeded by querying the buffer size afterward.
_ = conn.SetWriteBuffer(protocol.DesiredSendBufferSize)
newSize, err := inspectWriteBuffer(syscallConn)
if newSize < protocol.DesiredSendBufferSize {
// Try again with RCVBUFFORCE on Linux
_ = forceSetSendBuffer(syscallConn, protocol.DesiredSendBufferSize)
newSize, err = inspectWriteBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
}
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
if newSize == size {
return fmt.Errorf("failed to increase send buffer size (wanted: %d kiB, got %d kiB)", protocol.DesiredSendBufferSize/1024, newSize/1024)
}
if newSize < protocol.DesiredSendBufferSize {
return fmt.Errorf("failed to sufficiently increase send buffer size (was: %d kiB, wanted: %d kiB, got: %d kiB)", size/1024, protocol.DesiredSendBufferSize/1024, newSize/1024)
}
utils.DefaultLogger.Debugf("Increased send buffer size to %d kiB", newSize/1024)
return nil
}

View File

@@ -1,15 +1,22 @@
//go:build !linux && !windows
//go:build !linux && !windows && !darwin
package quic
import "syscall"
import (
"syscall"
)
func setDF(rawConn syscall.RawConn) error {
func setDF(syscall.RawConn) (bool, error) {
// no-op on unsupported platforms
return nil
return false, nil
}
func isMsgSizeErr(err error) bool {
func isSendMsgSizeErr(err error) bool {
// to be implemented for more specific platforms
return false
}
func isRecvMsgSizeErr(err error) bool {
// to be implemented for more specific platforms
return false
}

View File

@@ -0,0 +1,74 @@
//go:build darwin
package quic
import (
"errors"
"strconv"
"strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/quic-go/quic-go/internal/utils"
)
func setDF(rawConn syscall.RawConn) (bool, error) {
// Setting DF bit is only supported from macOS11
// https://github.com/chromium/chromium/blob/117.0.5881.2/net/socket/udp_socket_posix.cc#L555
if supportsDF, err := isAtLeastMacOS11(); !supportsDF || err != nil {
return false, err
}
// Enabling IP_DONTFRAG will force the kernel to return "sendto: message too long"
// and the datagram will not be fragmented
var errDFIPv4, errDFIPv6 error
if err := rawConn.Control(func(fd uintptr) {
errDFIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_DONTFRAG, 1)
errDFIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_DONTFRAG, 1)
}); err != nil {
return false, err
}
switch {
case errDFIPv4 == nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv4 and IPv6.")
case errDFIPv4 == nil && errDFIPv6 != nil:
utils.DefaultLogger.Debugf("Setting DF for IPv4.")
case errDFIPv4 != nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv6.")
// On macOS, the syscall for setting DF bit for IPv4 fails on dual-stack listeners.
// Treat the connection as not having DF enabled, even though the DF bit will be set
// when used for IPv6.
// See https://github.com/quic-go/quic-go/issues/3793 for details.
return false, nil
case errDFIPv4 != nil && errDFIPv6 != nil:
return false, errors.New("setting DF failed for both IPv4 and IPv6")
}
return true, nil
}
func isSendMsgSizeErr(err error) bool {
return errors.Is(err, unix.EMSGSIZE)
}
func isRecvMsgSizeErr(error) bool { return false }
func isAtLeastMacOS11() (bool, error) {
uname := &unix.Utsname{}
err := unix.Uname(uname)
if err != nil {
return false, err
}
release := string(uname.Release[:])
if idx := strings.Index(release, "."); idx != -1 {
version, err := strconv.Atoi(release[:idx])
if err != nil {
return false, err
}
// Darwin version 20 is macOS version 11
// https://en.wikipedia.org/wiki/Darwin_(operating_system)#Darwin_20_onwards
return version >= 20, nil
}
return false, nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/quic-go/quic-go/internal/utils"
)
func setDF(rawConn syscall.RawConn) error {
func setDF(rawConn syscall.RawConn) (bool, error) {
// Enabling IP_MTU_DISCOVER will force the kernel to return "sendto: message too long"
// and the datagram will not be fragmented
var errDFIPv4, errDFIPv6 error
@@ -19,7 +19,7 @@ func setDF(rawConn syscall.RawConn) error {
errDFIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_MTU_DISCOVER, unix.IP_PMTUDISC_DO)
errDFIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_MTU_DISCOVER, unix.IPV6_PMTUDISC_DO)
}); err != nil {
return err
return false, err
}
switch {
case errDFIPv4 == nil && errDFIPv6 == nil:
@@ -29,12 +29,14 @@ func setDF(rawConn syscall.RawConn) error {
case errDFIPv4 != nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv6.")
case errDFIPv4 != nil && errDFIPv6 != nil:
return errors.New("setting DF failed for both IPv4 and IPv6")
return false, errors.New("setting DF failed for both IPv4 and IPv6")
}
return nil
return true, nil
}
func isMsgSizeErr(err error) bool {
func isSendMsgSizeErr(err error) bool {
// https://man7.org/linux/man-pages/man7/udp.7.html
return errors.Is(err, unix.EMSGSIZE)
}
func isRecvMsgSizeErr(error) bool { return false }

View File

@@ -12,20 +12,23 @@ import (
)
const (
// same for both IPv4 and IPv6 on Windows
// IP_DONTFRAGMENT controls the Don't Fragment (DF) bit.
//
// It's the same code point for both IPv4 and IPv6 on Windows.
// https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Networking/WinSock/constant.IP_DONTFRAG.html
// https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Networking/WinSock/constant.IPV6_DONTFRAG.html
//
//nolint:stylecheck
IP_DONTFRAGMENT = 14
IPV6_DONTFRAG = 14
)
func setDF(rawConn syscall.RawConn) error {
func setDF(rawConn syscall.RawConn) (bool, error) {
var errDFIPv4, errDFIPv6 error
if err := rawConn.Control(func(fd uintptr) {
errDFIPv4 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_DONTFRAGMENT, 1)
errDFIPv6 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_DONTFRAG, 1)
errDFIPv6 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IP_DONTFRAGMENT, 1)
}); err != nil {
return err
return false, err
}
switch {
case errDFIPv4 == nil && errDFIPv6 == nil:
@@ -35,12 +38,17 @@ func setDF(rawConn syscall.RawConn) error {
case errDFIPv4 != nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv6.")
case errDFIPv4 != nil && errDFIPv6 != nil:
return errors.New("setting DF failed for both IPv4 and IPv6")
return false, errors.New("setting DF failed for both IPv4 and IPv6")
}
return nil
return true, nil
}
func isMsgSizeErr(err error) bool {
func isSendMsgSizeErr(err error) bool {
// https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
return errors.Is(err, windows.WSAEMSGSIZE)
}
func isRecvMsgSizeErr(err error) bool {
// https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
return errors.Is(err, windows.WSAEMSGSIZE)
}

View File

@@ -2,20 +2,35 @@
package quic
import "golang.org/x/sys/unix"
import (
"encoding/binary"
"net/netip"
"syscall"
const msgTypeIPTOS = unix.IP_RECVTOS
const (
ipv4RECVPKTINFO = unix.IP_RECVPKTINFO
ipv6RECVPKTINFO = 0x3d
"golang.org/x/sys/unix"
)
const (
msgTypeIPv4PKTINFO = unix.IP_PKTINFO
msgTypeIPv6PKTINFO = 0x2e
msgTypeIPTOS = unix.IP_RECVTOS
ipv4PKTINFO = unix.IP_RECVPKTINFO
)
const ecnIPv4DataLen = 4
// ReadBatch only returns a single packet on OSX,
// see https://godoc.org/golang.org/x/net/ipv4#PacketConn.ReadBatch.
const batchSize = 1
func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
if len(body) != 12 {
return netip.Addr{}, 0, false
}
return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
}
func isGSOSupported(syscall.RawConn) bool { return false }

View File

@@ -2,20 +2,30 @@
package quic
import "golang.org/x/sys/unix"
import (
"net/netip"
"syscall"
"golang.org/x/sys/unix"
)
const (
msgTypeIPTOS = unix.IP_RECVTOS
ipv4PKTINFO = 0x7
)
const (
ipv4RECVPKTINFO = 0x7
ipv6RECVPKTINFO = 0x24
)
const (
msgTypeIPv4PKTINFO = 0x7
msgTypeIPv6PKTINFO = 0x2e
)
const ecnIPv4DataLen = 1
const batchSize = 8
func parseIPv4PktInfo(body []byte) (ip netip.Addr, _ uint32, ok bool) {
// struct in_pktinfo {
// struct in_addr ipi_addr; /* Header Destination address */
// };
if len(body) != 4 {
return netip.Addr{}, 0, false
}
return netip.AddrFrom4(*(*[4]byte)(body)), 0, true
}
func isGSOSupported(syscall.RawConn) bool { return false }

View File

@@ -2,18 +2,109 @@
package quic
import "golang.org/x/sys/unix"
import (
"encoding/binary"
"errors"
"net/netip"
"os"
"strconv"
"syscall"
"unsafe"
const msgTypeIPTOS = unix.IP_TOS
const (
ipv4RECVPKTINFO = unix.IP_PKTINFO
ipv6RECVPKTINFO = unix.IPV6_RECVPKTINFO
"golang.org/x/sys/unix"
)
const (
msgTypeIPv4PKTINFO = unix.IP_PKTINFO
msgTypeIPv6PKTINFO = unix.IPV6_PKTINFO
msgTypeIPTOS = unix.IP_TOS
ipv4PKTINFO = unix.IP_PKTINFO
)
const ecnIPv4DataLen = 1
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
var serr error
if err := c.Control(func(fd uintptr) {
serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
}); err != nil {
return err
}
return serr
}
func forceSetSendBuffer(c syscall.RawConn, bytes int) error {
var serr error
if err := c.Control(func(fd uintptr) {
serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
}); err != nil {
return err
}
return serr
}
func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
if len(body) != 12 {
return netip.Addr{}, 0, false
}
return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
}
// isGSOSupported tests if the kernel supports GSO.
// Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
func isGSOSupported(conn syscall.RawConn) bool {
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
if err == nil && disabled {
return false
}
var serr error
if err := conn.Control(func(fd uintptr) {
_, serr = unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
}); err != nil {
return false
}
return serr == nil
}
func appendUDPSegmentSizeMsg(b []byte, size uint16) []byte {
startLen := len(b)
const dataLen = 2 // payload is a uint16
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_UDP
h.Type = unix.UDP_SEGMENT
h.SetLen(unix.CmsgLen(dataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
*(*uint16)(unsafe.Pointer(&b[offset])) = size
return b
}
func isGSOError(err error) bool {
var serr *os.SyscallError
if errors.As(err, &serr) {
// EIO is returned by udp_send_skb() if the device driver does not have tx checksums enabled,
// which is a hard requirement of UDP_SEGMENT. See:
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
return serr.Err == unix.EIO
}
return false
}
// The first sendmsg call on a new UDP socket sometimes errors on Linux.
// It's not clear why this happens.
// See https://github.com/golang/go/issues/63322.
func isPermissionError(err error) bool {
var serr *os.SyscallError
if errors.As(err, &serr) {
return serr.Syscall == "sendmsg" && serr.Err == unix.EPERM
}
return false
}

View File

@@ -0,0 +1,10 @@
//go:build !linux
package quic
func forceSetReceiveBuffer(c any, bytes int) error { return nil }
func forceSetSendBuffer(c any, bytes int) error { return nil }
func appendUDPSegmentSizeMsg([]byte, uint16) []byte { return nil }
func isGSOError(error) bool { return false }
func isPermissionError(err error) bool { return false }

View File

@@ -2,14 +2,20 @@
package quic
import "net"
import (
"net"
"net/netip"
)
func newConn(c net.PacketConn) (rawConn, error) {
return &basicConn{PacketConn: c}, nil
func newConn(c net.PacketConn, supportsDF bool) (*basicConn, error) {
return &basicConn{PacketConn: c, supportsDF: supportsDF}, nil
}
func inspectReadBuffer(interface{}) (int, error) {
return 0, nil
func inspectReadBuffer(any) (int, error) { return 0, nil }
func inspectWriteBuffer(any) (int, error) { return 0, nil }
type packetInfo struct {
addr netip.Addr
}
func (i *packetInfo) OOB() []byte { return nil }

View File

@@ -5,10 +5,15 @@ package quic
import (
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"net/netip"
"os"
"strconv"
"sync"
"syscall"
"time"
"unsafe"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
@@ -32,20 +37,10 @@ type batchConn interface {
ReadBatch(ms []ipv4.Message, flags int) (int, error)
}
func inspectReadBuffer(c interface{}) (int, error) {
conn, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
})
if !ok {
return 0, errors.New("doesn't have a SyscallConn")
}
rawConn, err := conn.SyscallConn()
if err != nil {
return 0, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
func inspectReadBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := rawConn.Control(func(fd uintptr) {
if err := c.Control(func(fd uintptr) {
size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF)
}); err != nil {
return 0, err
@@ -53,6 +48,22 @@ func inspectReadBuffer(c interface{}) (int, error) {
return size, serr
}
func inspectWriteBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := c.Control(func(fd uintptr) {
size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF)
}); err != nil {
return 0, err
}
return size, serr
}
func isECNDisabled() bool {
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
return err == nil && disabled
}
type oobConn struct {
OOBCapablePacketConn
batchConn batchConn
@@ -61,11 +72,13 @@ type oobConn struct {
// Packets received from the kernel, but not yet returned by ReadPacket().
messages []ipv4.Message
buffers [batchSize]*packetBuffer
cap connCapabilities
}
var _ rawConn = &oobConn{}
func newConn(c OOBCapablePacketConn) (*oobConn, error) {
func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) {
rawConn, err := c.SyscallConn()
if err != nil {
return nil, err
@@ -83,8 +96,8 @@ func newConn(c OOBCapablePacketConn) (*oobConn, error) {
errECNIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)
if needsPacketInfo {
errPIIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, ipv4RECVPKTINFO, 1)
errPIIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, ipv6RECVPKTINFO, 1)
errPIIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, ipv4PKTINFO, 1)
errPIIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1)
}
}); err != nil {
return nil, err
@@ -132,6 +145,11 @@ func newConn(c OOBCapablePacketConn) (*oobConn, error) {
batchConn: bc,
messages: msgs,
readPos: batchSize,
cap: connCapabilities{
DF: supportsDF,
GSO: isGSOSupported(rawConn),
ECN: !isECNDisabled(),
},
}
for i := 0; i < batchSize; i++ {
oobConn.messages[i].OOB = make([]byte, oobBufferSize)
@@ -139,7 +157,9 @@ func newConn(c OOBCapablePacketConn) (*oobConn, error) {
return oobConn, nil
}
func (c *oobConn) ReadPacket() (*receivedPacket, error) {
var invalidCmsgOnceV4, invalidCmsgOnceV6 sync.Once
func (c *oobConn) ReadPacket() (receivedPacket, error) {
if len(c.messages) == int(c.readPos) { // all messages read. Read the next batch of messages.
c.messages = c.messages[:batchSize]
// replace buffers data buffers up to the packet that has been consumed during the last ReadBatch call
@@ -153,7 +173,7 @@ func (c *oobConn) ReadPacket() (*receivedPacket, error) {
n, err := c.batchConn.ReadBatch(c.messages, 0)
if n == 0 || err != nil {
return nil, err
return receivedPacket{}, err
}
c.messages = c.messages[:n]
}
@@ -163,102 +183,149 @@ func (c *oobConn) ReadPacket() (*receivedPacket, error) {
c.readPos++
data := msg.OOB[:msg.NN]
var ecn protocol.ECN
var destIP net.IP
var ifIndex uint32
p := receivedPacket{
remoteAddr: msg.Addr,
rcvTime: time.Now(),
data: msg.Buffers[0][:msg.N],
buffer: buffer,
}
for len(data) > 0 {
hdr, body, remainder, err := unix.ParseOneSocketControlMessage(data)
if err != nil {
return nil, err
return receivedPacket{}, err
}
if hdr.Level == unix.IPPROTO_IP {
switch hdr.Type {
case msgTypeIPTOS:
ecn = protocol.ECN(body[0] & ecnMask)
case msgTypeIPv4PKTINFO:
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination
// address */
// };
ip := make([]byte, 4)
if len(body) == 12 {
ifIndex = binary.LittleEndian.Uint32(body)
copy(ip, body[8:12])
} else if len(body) == 4 {
// FreeBSD
copy(ip, body)
p.ecn = protocol.ParseECNHeaderBits(body[0] & ecnMask)
case ipv4PKTINFO:
ip, ifIndex, ok := parseIPv4PktInfo(body)
if ok {
p.info.addr = ip
p.info.ifIndex = ifIndex
} else {
invalidCmsgOnceV4.Do(func() {
log.Printf("Received invalid IPv4 packet info control message: %+x. "+
"This should never occur, please open a new issue and include details about the architecture.", body)
})
}
destIP = net.IP(ip)
}
}
if hdr.Level == unix.IPPROTO_IPV6 {
switch hdr.Type {
case unix.IPV6_TCLASS:
ecn = protocol.ECN(body[0] & ecnMask)
case msgTypeIPv6PKTINFO:
p.ecn = protocol.ParseECNHeaderBits(body[0] & ecnMask)
case unix.IPV6_PKTINFO:
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
if len(body) == 20 {
ip := make([]byte, 16)
copy(ip, body[:16])
destIP = net.IP(ip)
ifIndex = binary.LittleEndian.Uint32(body[16:])
p.info.addr = netip.AddrFrom16(*(*[16]byte)(body[:16]))
p.info.ifIndex = binary.LittleEndian.Uint32(body[16:])
} else {
invalidCmsgOnceV6.Do(func() {
log.Printf("Received invalid IPv6 packet info control message: %+x. "+
"This should never occur, please open a new issue and include details about the architecture.", body)
})
}
}
}
data = remainder
}
var info *packetInfo
if destIP != nil {
info = &packetInfo{
addr: destIP,
ifIndex: ifIndex,
}
}
return &receivedPacket{
remoteAddr: msg.Addr,
rcvTime: time.Now(),
data: msg.Buffers[0][:msg.N],
ecn: ecn,
info: info,
buffer: buffer,
}, nil
return p, nil
}
func (c *oobConn) WritePacket(b []byte, addr net.Addr, oob []byte) (n int, err error) {
n, _, err = c.OOBCapablePacketConn.WriteMsgUDP(b, oob, addr.(*net.UDPAddr))
// WritePacket writes a new packet.
func (c *oobConn) WritePacket(b []byte, addr net.Addr, packetInfoOOB []byte, gsoSize uint16, ecn protocol.ECN) (int, error) {
oob := packetInfoOOB
if gsoSize > 0 {
if !c.capabilities().GSO {
panic("GSO disabled")
}
oob = appendUDPSegmentSizeMsg(oob, gsoSize)
}
if ecn != protocol.ECNUnsupported {
if !c.capabilities().ECN {
panic("tried to send a ECN-marked packet although ECN is disabled")
}
if remoteUDPAddr, ok := addr.(*net.UDPAddr); ok {
if remoteUDPAddr.IP.To4() != nil {
oob = appendIPv4ECNMsg(oob, ecn)
} else {
oob = appendIPv6ECNMsg(oob, ecn)
}
}
}
n, _, err := c.OOBCapablePacketConn.WriteMsgUDP(b, oob, addr.(*net.UDPAddr))
return n, err
}
func (c *oobConn) capabilities() connCapabilities {
return c.cap
}
type packetInfo struct {
addr netip.Addr
ifIndex uint32
}
func (info *packetInfo) OOB() []byte {
if info == nil {
return nil
}
if ip4 := info.addr.To4(); ip4 != nil {
if info.addr.Is4() {
ip := info.addr.As4()
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
cm := ipv4.ControlMessage{
Src: ip4,
Src: ip[:],
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
} else if len(info.addr) == 16 {
} else if info.addr.Is6() {
ip := info.addr.As16()
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
cm := ipv6.ControlMessage{
Src: info.addr,
Src: ip[:],
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
}
return nil
}
func appendIPv4ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
b = append(b, make([]byte, unix.CmsgSpace(ecnIPv4DataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IP
h.Type = unix.IP_TOS
h.SetLen(unix.CmsgLen(ecnIPv4DataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
b[offset] = val.ToHeaderBits()
return b
}
func appendIPv6ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
const dataLen = 4
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IPV6
h.Type = unix.IPV6_TCLASS
h.SetLen(unix.CmsgLen(dataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
b[offset] = val.ToHeaderBits()
return b
}

View File

@@ -3,32 +3,20 @@
package quic
import (
"errors"
"fmt"
"net"
"net/netip"
"syscall"
"golang.org/x/sys/windows"
)
func newConn(c OOBCapablePacketConn) (rawConn, error) {
return &basicConn{PacketConn: c}, nil
func newConn(c OOBCapablePacketConn, supportsDF bool) (*basicConn, error) {
return &basicConn{PacketConn: c, supportsDF: supportsDF}, nil
}
func inspectReadBuffer(c net.PacketConn) (int, error) {
conn, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
})
if !ok {
return 0, errors.New("doesn't have a SyscallConn")
}
rawConn, err := conn.SyscallConn()
if err != nil {
return 0, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
func inspectReadBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := rawConn.Control(func(fd uintptr) {
if err := c.Control(func(fd uintptr) {
size, serr = windows.GetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_RCVBUF)
}); err != nil {
return 0, err
@@ -36,4 +24,19 @@ func inspectReadBuffer(c net.PacketConn) (int, error) {
return size, serr
}
func inspectWriteBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := c.Control(func(fd uintptr) {
size, serr = windows.GetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_SNDBUF)
}); err != nil {
return 0, err
}
return size, serr
}
type packetInfo struct {
addr netip.Addr
}
func (i *packetInfo) OOB() []byte { return nil }

View File

@@ -3,6 +3,6 @@
package quic
import (
_ "github.com/golang/mock/mockgen"
_ "github.com/onsi/ginkgo/v2/ginkgo"
_ "go.uber.org/mock/mockgen"
)

Some files were not shown because too many files have changed in this diff Show More