Modernizing the quic-go connection API
In this release, we've completely revamped our connection establishment API, following an engaging discussion with the quic-go community (#3727).
Key modifications are as follows:
- The context variants of the dial functions, including
DialContext
, have been removed. In their place,Dial
now incorporates a context. This development stems from our drive to modernize the API, given thatcontext.Context
wasn't in existence when quic-go was launched eight years ago. quic.Listener
andquic.EarlyListener
have transitioned from interfaces to structs.
- We've introduced a
quic.Transport
. More about that below.
Introducing the Transport
The QUIC protocols demultiplexes connections based on the QUIC Connection IDs. This has interesting implications, first and foremost that multiple QUIC connections can run on the same UDP socket (and even connect to the same remote QUIC server). Interestingly, it's feasible to run a QUIC server on the same socket as outgoing QUIC connections. In fact, that's a really useful thing to do when using QUIC for holepunching through NATs.
Previously, it was possible to utilize this feature, but the API lacked clarity. When the same net.PacketConn
was passed to sequential Listen
and Dial
calls, quic-go would identify this and multiplex several QUIC connections on that net.PacketConn
. This behavior was not obvious and, additionally, it demanded that certain values of the Config
[matched](https://github.com/quic-go/quic-go/blob/v0.34.0/multiplexer.go#L82-L90).
We've now made multiplexing explicit with the Transport
introduction. A Transport
manages a single net.PacketConn
. The usage is as follows:
laddr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:443")
// handle err
conn, err := net.ListenUDP("udp4", laddr)
// handle err
tr := quic.Transport{
Conn: conn,
StatelessResetKey: <a key that survives reboots>,
}
// start listening for incoming QUIC connection
ln, err := tr.Listen(<tls.Config>, &quic.Config{})
// handle err
go func() {
conn, err := ln.Accept(context.Background())
if err != nil {
return
}
// handle accepted QUIC connection...
}()
// establish QUIC connections to remote nodes, on the same UDP socket
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel
conn, err := tr.Dial(ctx, <remote addr>, <tls.Config>, <quic.Config>)
// handle err
// handle dialed QUIC connection
This modification enables us to shift several configuration options logically tied to the UDP sockets from the Config
. Specifically, ConnectionIDLength
/ ConnectionIDGenerator
and StatelessResetKey
are now configured on the Transport
.
Migration Guide
To update to the new version, applications might need to:
- Substitute calls to
DialContext
with calls toDial
. - Replace all instances of
Listener
with*Listener
(and similarly forEarlyListener
).
Other Notable Changes
- The HTTP/3 response writer is now compatible with the
http.ResponseController
introduced in the Go 1.20 release (#3790). Thanks @dunglas! - The
http3.RoundTripper
now implementsCloseIdleConnections
method, allowing the use ofhttp.Client.CloseIdleConnections
. Thanks @Glonee! - DoS resiliency was improved by only using a single Go routine to send stateless reset, version negotiation and INVALID_TOKEN error packets (#3842 and #3854). Thanks @sukunrt!
- We now use the
SO_RCVBUFFORCE
syscall to attempt to increase the UDP receive buffer. Increasing the receive buffer is absolutely crucial for QUIC performance, and quic-go will print a log message if increasing the buffer size fails. Unfortunately, due to small default buffer sizes in most Linux distributions, this happened quite frequently and required [manual configuration](https://github.com/quic-go/quic-go/wiki/UDP-Receive-Buffer-Size). UsingSO_RCVBUFFORCE
call will only succeed when the process hasCAP_NET_ADMIN
permissions, but in these cases no manual configuration will be necessary any more. Thanks to @MarcoPolo!
Full Changelog
- http3: add compatibility with net/http.ResponseController by @dunglas in #3790
- ci: allow changing runners through config vars by @marten-seemann in #3783
- set the version for integration tests using a command line flag by @marten-seemann in #3782
- ci: speed up the cross compilation job by parallelizing by @marten-seemann in #3784
- put a context on a dial functions, remove Dial*Context, remove host parameter by @marten-seemann in #3785
- congestion: fix overflow when calculating the pacing budget by @marten-seemann in #3796
- move 0-RTT queue handling from the packet handler map to the server by @marten-seemann in #3788
- make Listener and EarlyListener a struct by @marten-seemann in #3789
- docs: add Mercure in the list of projects using quic-go by @dunglas in #3791
- introduce a Transport by @marten-seemann in #3794
- docs: fix typo in documentation for EarlyConnection by @Zxilly in #3798
- transport: fix flaky stateless reset test by @marten-seemann in #3810
- use SO_RCVBUFFORCE to force receive buffer increase on Linux by @MarcoPolo in #3804
- increase the UDP send buffer size to 2 MB by @marten-seemann in #3812
- quicvarint: remove deprecated Write function by @marten-seemann in #3781
- change how the multiplex test is skipped on Linux by @marten-seemann in #3817
- fix flaky timeout integration test by @marten-seemann in #3818
- fix HTTP/3 connection test on draft-29 by @marten-seemann in #3819
- implement http3.RoundTripper.CloseIdleConnections by @Glonee in #3820
- ci: fix coverage report by @Glonee in #3837
- ackhandler: optimize memory layout of ackhandler.Packet by @marten-seemann in #3844
- transport: send stateless reset packets from a single Go routine by @sukunrt in #3842
- fix comment claiming ParseConnectionID reuses the data slice by @sukunrt in #3848
- rttstats: don't set initial RTT after obtaining a measurement by @marten-seemann in #3852
- wire: save ECN counts on the ACK frame by @marten-seemann in #3829
- quicproxy: increase UDP send and receive buffer sizes by @marten-seemann in #3813
- packet packer: don't try packing a 0-RTT packet with only an ACK by @marten-seemann in #3849
- server: send version negotiation and invalid token packets from a single Go routine by @sukunrt in #3854
- wire: apply the default value for the active_connection_id_limit by @marten-seemann in #3806
New Contributors
Full Changelog: v0.34.0...v0.35.0