TUN-6191: Update quic-go to v0.27.1 and with custom patch to allow keep alive period to be configurable

The idle period is set to 5sec.

We now also ping every second since last activity.
This makes the quic.Connection less prone to being closed with
no network activity, since we send multiple pings per idle
period, and thus a single packet loss cannot cause the problem.
This commit is contained in:
Nuno Diegues 2022-06-06 14:15:35 +01:00
parent 4ccef23dbc
commit 475939a77f
49 changed files with 671 additions and 486 deletions

View File

@ -35,7 +35,7 @@ const (
// QUICConnection represents the type that facilitates Proxying via QUIC streams.
type QUICConnection struct {
session quic.Session
session quic.Connection
logger *zerolog.Logger
orchestrator Orchestrator
sessionManager datagramsession.Manager

View File

@ -31,7 +31,7 @@ import (
var (
testTLSServerConfig = quicpogs.GenerateTLSConfig()
testQUICConfig = &quic.Config{
KeepAlive: true,
KeepAlivePeriod: 5 * time.Second,
EnableDatagrams: true,
}
)
@ -502,7 +502,7 @@ func TestServeUDPSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
// Establish QUIC connection with edge
edgeQUICSessionChan := make(chan quic.Session)
edgeQUICSessionChan := make(chan quic.Connection)
go func() {
earlyListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig)
require.NoError(t, err)
@ -522,7 +522,7 @@ func TestServeUDPSession(t *testing.T) {
cancel()
}
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Session, closeType closeReason, expectedReason string, t *testing.T) {
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
var (
payload = []byte(t.Name())
)
@ -583,7 +583,7 @@ const (
closedByTimeout
)
func runRPCServer(ctx context.Context, session quic.Session, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
stream, err := session.AcceptStream(ctx)
require.NoError(t, err)

10
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/json-iterator/go v1.1.12
github.com/lucas-clemente/quic-go v0.24.0
github.com/lucas-clemente/quic-go v0.27.1
github.com/mattn/go-colorable v0.1.8
github.com/miekg/dns v1.1.45
github.com/mitchellh/go-homedir v1.1.0
@ -74,9 +74,9 @@ require (
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@ -105,7 +105,7 @@ require (
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da
// Avoid 'CVE-2022-21698'
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1

17
go.sum
View File

@ -111,8 +111,8 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62 h1:PLTB4iA6sOgAItzQY642tYdcGKfG/7i2gu93JQGgUcM=
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da h1:FmuwbQ8RU/ftTKnfz5diawqvQFH1KDB9wN2Q8S2wqds=
github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -405,13 +405,12 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=

View File

@ -14,11 +14,11 @@ const (
)
type DatagramMuxer struct {
session quic.Session
session quic.Connection
logger *zerolog.Logger
}
func NewDatagramMuxer(quicSession quic.Session, logger *zerolog.Logger) (*DatagramMuxer, error) {
func NewDatagramMuxer(quicSession quic.Connection, logger *zerolog.Logger) (*DatagramMuxer, error) {
return &DatagramMuxer{
session: quicSession,
logger: logger,

View File

@ -56,7 +56,7 @@ func TestMaxDatagramPayload(t *testing.T) {
payload := make([]byte, maxDatagramPayloadSize)
quicConfig := &quic.Config{
KeepAlive: true,
KeepAlivePeriod: 5 * time.Millisecond,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
}

View File

@ -37,7 +37,8 @@ const (
protocolVersionLength = 2
HandshakeIdleTimeout = 5 * time.Second
MaxIdleTimeout = 15 * time.Second
MaxIdleTimeout = 5 * time.Second
MaxIdlePingPeriod = 1 * time.Second
)
// RequestServerStream is a stream to serve requests

View File

@ -7,6 +7,7 @@ import (
"net"
"sync"
"testing"
"time"
"github.com/lucas-clemente/quic-go"
"github.com/stretchr/testify/require"
@ -15,7 +16,7 @@ import (
var (
testTLSServerConfig = GenerateTLSConfig()
testQUICConfig = &quic.Config{
KeepAlive: true,
KeepAlivePeriod: 5 * time.Second,
EnableDatagrams: true,
}
exchanges = 1000

View File

@ -550,9 +550,9 @@ func ServeQUIC(
quicConfig := &quic.Config{
HandshakeIdleTimeout: quicpogs.HandshakeIdleTimeout,
MaxIdleTimeout: quicpogs.MaxIdleTimeout,
KeepAlivePeriod: quicpogs.MaxIdlePingPeriod,
MaxIncomingStreams: connection.MaxConcurrentStreams,
MaxIncomingUniStreams: connection.MaxConcurrentStreams,
KeepAlive: true,
EnableDatagrams: true,
MaxDatagramFrameSize: quicpogs.MaxDatagramFrameSize,
Tracer: quicpogs.NewClientTracer(connLogger.Logger(), connIndex),

View File

@ -28,7 +28,6 @@ linters:
- ineffassign
- misspell
- prealloc
- scopelint
- staticcheck
- stylecheck
- structcheck

View File

@ -3,9 +3,6 @@
<img src="docs/quic.png" width=303 height=124>
[![PkgGoDev](https://pkg.go.dev/badge/github.com/lucas-clemente/quic-go)](https://pkg.go.dev/github.com/lucas-clemente/quic-go)
[![Travis Build Status](https://img.shields.io/travis/lucas-clemente/quic-go/master.svg?style=flat-square&label=Travis+build)](https://travis-ci.org/lucas-clemente/quic-go)
[![CircleCI Build Status](https://img.shields.io/circleci/project/github/lucas-clemente/quic-go.svg?style=flat-square&label=CircleCI+build)](https://circleci.com/gh/lucas-clemente/quic-go)
[![Windows Build Status](https://img.shields.io/appveyor/ci/lucas-clemente/quic-go/master.svg?style=flat-square&label=windows+build)](https://ci.appveyor.com/project/lucas-clemente/quic-go/branch/master)
[![Code Coverage](https://img.shields.io/codecov/c/github/lucas-clemente/quic-go/master.svg?style=flat-square)](https://codecov.io/gh/lucas-clemente/quic-go/)
quic-go is an implementation of the [QUIC protocol, RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000) protocol in Go.
@ -13,7 +10,7 @@ In addition to RFC 9000, it currently implements the [IETF QUIC draft-29](https:
## Guides
*We currently support Go 1.16.x and Go 1.17.x.*
*We currently support Go 1.16.x, Go 1.17.x, and Go 1.18.x.*
Running tests:
@ -51,10 +48,11 @@ http.Client{
| [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) |
| [go-ipfs](https://github.com/ipfs/go-ipfs) | IPFS implementation in go | ![GitHub Repo stars](https://img.shields.io/github/stars/ipfs/go-ipfs?style=flat-square) |
| [nextdns](https://github.com/nextdns/nextdns) | NextDNS CLI client (DoH Proxy) | ![GitHub Repo stars](https://img.shields.io/github/stars/nextdns/nextdns?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) |
| [traefik](https://github.com/traefik/traefik) | The Cloud Native Application Proxy | ![GitHub Repo stars](https://img.shields.io/github/stars/traefik/traefik?style=flat-square) |
| [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) |
| [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) |
| [OONI Probe](https://github.com/ooni/probe-cli) | The Open Observatory of Network Interference (OONI) aims to empower decentralized efforts in documenting Internet censorship around the world. | ![GitHub Repo stars](https://img.shields.io/github/stars/ooni/probe-cli?style=flat-square) |
## Contributing

View File

@ -14,7 +14,7 @@ import (
)
type client struct {
conn sendConn
sconn sendConn
// If the client is created with DialAddr, we create a packet conn.
// If it is started with Dial, we take a packet conn as a parameter.
createdPacketConn bool
@ -35,7 +35,7 @@ type client struct {
handshakeChan chan struct{}
session quicSession
conn quicConn
tracer logging.ConnectionTracer
tracingID uint64
@ -49,26 +49,26 @@ var (
)
// DialAddr establishes a new QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
// It uses a new UDP connection and closes this connection when the QUIC connection is closed.
// The hostname for SNI is taken from the given address.
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
func DialAddr(
addr string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
) (Connection, error) {
return DialAddrContext(context.Background(), addr, tlsConf, config)
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
// It uses a new UDP connection and closes this connection when the QUIC connection is closed.
// The hostname for SNI is taken from the given address.
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
func DialAddrEarly(
addr string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
) (EarlyConnection, error) {
return DialAddrEarlyContext(context.Background(), addr, tlsConf, config)
}
@ -79,13 +79,13 @@ func DialAddrEarlyContext(
addr string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
sess, err := dialAddrContext(ctx, addr, tlsConf, config, true)
) (EarlyConnection, error) {
conn, err := dialAddrContext(ctx, addr, tlsConf, config, true)
if err != nil {
return nil, err
}
utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early session")
return sess, nil
utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early connection")
return conn, nil
}
// DialAddrContext establishes a new QUIC connection to a server using the provided context.
@ -95,7 +95,7 @@ func DialAddrContext(
addr string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
) (Connection, error) {
return dialAddrContext(ctx, addr, tlsConf, config, false)
}
@ -105,7 +105,7 @@ func dialAddrContext(
tlsConf *tls.Config,
config *Config,
use0RTT bool,
) (quicSession, error) {
) (quicConn, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
@ -131,7 +131,7 @@ func Dial(
host string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
) (Connection, error) {
return dialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config, false, false)
}
@ -146,7 +146,7 @@ func DialEarly(
host string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
) (EarlyConnection, error) {
return DialEarlyContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
}
@ -159,7 +159,7 @@ func DialEarlyContext(
host string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
) (EarlyConnection, error) {
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, true, false)
}
@ -172,7 +172,7 @@ func DialContext(
host string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
) (Connection, error) {
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false, false)
}
@ -185,7 +185,7 @@ func dialContext(
config *Config,
use0RTT bool,
createdPacketConn bool,
) (quicSession, error) {
) (quicConn, error) {
if tlsConf == nil {
return nil, errors.New("quic: tls.Config not set")
}
@ -203,21 +203,21 @@ func dialContext(
}
c.packetHandlers = packetHandlers
c.tracingID = nextSessionTracingID()
c.tracingID = nextConnTracingID()
if c.config.Tracer != nil {
c.tracer = c.config.Tracer.TracerForConnection(
context.WithValue(ctx, SessionTracingKey, c.tracingID),
context.WithValue(ctx, ConnectionTracingKey, c.tracingID),
protocol.PerspectiveClient,
c.destConnID,
)
}
if c.tracer != nil {
c.tracer.StartedConnection(c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID)
c.tracer.StartedConnection(c.sconn.LocalAddr(), c.sconn.RemoteAddr(), c.srcConnID, c.destConnID)
}
if err := c.dial(ctx); err != nil {
return nil, err
}
return c.session, nil
return c.conn, nil
}
func newClient(
@ -265,7 +265,7 @@ func newClient(
c := &client{
srcConnID: srcConnID,
destConnID: destConnID,
conn: newSendPconn(pconn, remoteAddr),
sconn: newSendPconn(pconn, remoteAddr),
createdPacketConn: createdPacketConn,
use0RTT: use0RTT,
tlsConf: tlsConf,
@ -278,10 +278,10 @@ func newClient(
}
func (c *client) dial(ctx context.Context) error {
c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sconn.LocalAddr(), c.sconn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
c.session = newClientSession(
c.conn,
c.conn = newClientConnection(
c.sconn,
c.packetHandlers,
c.destConnID,
c.srcConnID,
@ -295,11 +295,11 @@ func (c *client) dial(ctx context.Context) error {
c.logger,
c.version,
)
c.packetHandlers.Add(c.srcConnID, c.session)
c.packetHandlers.Add(c.srcConnID, c.conn)
errorChan := make(chan error, 1)
go func() {
err := c.session.run() // returns as soon as the session is closed
err := c.conn.run() // returns as soon as the connection is closed
if e := (&errCloseForRecreating{}); !errors.As(err, &e) && c.createdPacketConn {
c.packetHandlers.Destroy()
@ -308,15 +308,15 @@ func (c *client) dial(ctx context.Context) error {
}()
// only set when we're using 0-RTT
// Otherwise, earlySessionChan will be nil. Receiving from a nil chan blocks forever.
var earlySessionChan <-chan struct{}
// Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever.
var earlyConnChan <-chan struct{}
if c.use0RTT {
earlySessionChan = c.session.earlySessionReady()
earlyConnChan = c.conn.earlyConnReady()
}
select {
case <-ctx.Done():
c.session.shutdown()
c.conn.shutdown()
return ctx.Err()
case err := <-errorChan:
var recreateErr *errCloseForRecreating
@ -327,10 +327,10 @@ func (c *client) dial(ctx context.Context) error {
return c.dial(ctx)
}
return err
case <-earlySessionChan:
case <-earlyConnChan:
// ready to send 0-RTT data
return nil
case <-c.session.HandshakeComplete().Done():
case <-c.conn.HandshakeComplete().Done():
// handshake successfully completed
return nil
}

View File

@ -7,15 +7,15 @@ import (
"github.com/lucas-clemente/quic-go/internal/utils"
)
// A closedLocalSession is a session that we closed locally.
// When receiving packets for such a session, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
// A closedLocalConn is a connection that we closed locally.
// When receiving packets for such a connection, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
// with an exponential backoff.
type closedLocalSession struct {
type closedLocalConn struct {
conn sendConn
connClosePacket []byte
closeOnce sync.Once
closeChan chan struct{} // is closed when the session is closed or destroyed
closeChan chan struct{} // is closed when the connection is closed or destroyed
receivedPackets chan *receivedPacket
counter uint64 // number of packets received
@ -25,16 +25,16 @@ type closedLocalSession struct {
logger utils.Logger
}
var _ packetHandler = &closedLocalSession{}
var _ packetHandler = &closedLocalConn{}
// newClosedLocalSession creates a new closedLocalSession and runs it.
func newClosedLocalSession(
// newClosedLocalConn creates a new closedLocalConn and runs it.
func newClosedLocalConn(
conn sendConn,
connClosePacket []byte,
perspective protocol.Perspective,
logger utils.Logger,
) packetHandler {
s := &closedLocalSession{
s := &closedLocalConn{
conn: conn,
connClosePacket: connClosePacket,
perspective: perspective,
@ -46,7 +46,7 @@ func newClosedLocalSession(
return s
}
func (s *closedLocalSession) run() {
func (s *closedLocalConn) run() {
for {
select {
case p := <-s.receivedPackets:
@ -57,14 +57,14 @@ func (s *closedLocalSession) run() {
}
}
func (s *closedLocalSession) handlePacket(p *receivedPacket) {
func (s *closedLocalConn) handlePacket(p *receivedPacket) {
select {
case s.receivedPackets <- p:
default:
}
}
func (s *closedLocalSession) handlePacketImpl(_ *receivedPacket) {
func (s *closedLocalConn) handlePacketImpl(_ *receivedPacket) {
s.counter++
// exponential backoff
// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
@ -79,34 +79,34 @@ func (s *closedLocalSession) handlePacketImpl(_ *receivedPacket) {
}
}
func (s *closedLocalSession) shutdown() {
func (s *closedLocalConn) shutdown() {
s.destroy(nil)
}
func (s *closedLocalSession) destroy(error) {
func (s *closedLocalConn) destroy(error) {
s.closeOnce.Do(func() {
close(s.closeChan)
})
}
func (s *closedLocalSession) getPerspective() protocol.Perspective {
func (s *closedLocalConn) getPerspective() protocol.Perspective {
return s.perspective
}
// A closedRemoteSession is a session that was closed remotely.
// For such a session, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
// A closedRemoteConn is a connection that was closed remotely.
// For such a connection, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
// We can just ignore those packets.
type closedRemoteSession struct {
type closedRemoteConn struct {
perspective protocol.Perspective
}
var _ packetHandler = &closedRemoteSession{}
var _ packetHandler = &closedRemoteConn{}
func newClosedRemoteSession(pers protocol.Perspective) packetHandler {
return &closedRemoteSession{perspective: pers}
func newClosedRemoteConn(pers protocol.Perspective) packetHandler {
return &closedRemoteConn{perspective: pers}
}
func (s *closedRemoteSession) handlePacket(*receivedPacket) {}
func (s *closedRemoteSession) shutdown() {}
func (s *closedRemoteSession) destroy(error) {}
func (s *closedRemoteSession) getPerspective() protocol.Perspective { return s.perspective }
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

@ -109,11 +109,12 @@ func populateConfig(config *Config) *Config {
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
AcceptToken: config.AcceptToken,
KeepAlive: config.KeepAlive,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
ConnectionIDLength: config.ConnectionIDLength,

View File

@ -90,7 +90,7 @@ func (p *receivedPacket) Clone() *receivedPacket {
}
}
type sessionRunner interface {
type connRunner interface {
Add(protocol.ConnectionID, packetHandler) bool
GetStatelessResetToken(protocol.ConnectionID) protocol.StatelessResetToken
Retire(protocol.ConnectionID)
@ -124,18 +124,14 @@ type errCloseForRecreating struct {
}
func (e *errCloseForRecreating) Error() string {
return "closing session in order to recreate it"
return "closing connection in order to recreate it"
}
var sessionTracingID uint64 // to be accessed atomically
func nextSessionTracingID() uint64 { return atomic.AddUint64(&sessionTracingID, 1) }
var connTracingID uint64 // to be accessed atomically
func nextConnTracingID() uint64 { return atomic.AddUint64(&connTracingID, 1) }
func pathMTUDiscoveryEnabled(config *Config) bool {
return !disablePathMTUDiscovery && !config.DisablePathMTUDiscovery
}
// A Session is a QUIC session
type session struct {
// A Connection is a QUIC connection
type connection struct {
// Destination connection ID used during the handshake.
// Used to check source connection ID on incoming packets.
handshakeDestConnID protocol.ConnectionID
@ -192,7 +188,7 @@ type session struct {
undecryptablePacketsToProcess []*receivedPacket
clientHelloWritten <-chan *wire.TransportParameters
earlySessionReadyChan chan struct{}
earlyConnReadyChan chan struct{}
handshakeCompleteChan chan struct{} // is closed when the handshake completes
handshakeComplete bool
handshakeConfirmed bool
@ -202,7 +198,7 @@ type session struct {
receivedFirstPacket bool
idleTimeout time.Duration
sessionCreationTime time.Time
creationTime time.Time
// The idle timeout is set based on the max of the time we received the last packet...
lastPacketReceivedTime time.Time
// ... and the time we sent a new ack-eliciting packet after receiving a packet.
@ -226,15 +222,15 @@ type session struct {
}
var (
_ Session = &session{}
_ EarlySession = &session{}
_ streamSender = &session{}
_ Connection = &connection{}
_ EarlyConnection = &connection{}
_ streamSender = &connection{}
deadlineSendImmediately = time.Time{}.Add(42 * time.Millisecond) // any value > time.Time{} and before time.Now() is fine
)
var newSession = func(
var newConnection = func(
conn sendConn,
runner sessionRunner,
runner connRunner,
origDestConnID protocol.ConnectionID,
retrySrcConnID *protocol.ConnectionID,
clientDestConnID protocol.ConnectionID,
@ -249,8 +245,8 @@ var newSession = func(
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
) quicSession {
s := &session{
) quicConn {
s := &connection{
conn: conn,
config: conf,
handshakeDestConnID: destConnID,
@ -286,7 +282,7 @@ var newSession = func(
s.version,
)
s.preSetup()
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), SessionTracingKey, tracingID))
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
0,
getMaxPacketSize(s.conn.RemoteAddr()),
@ -369,9 +365,9 @@ var newSession = func(
}
// declare this as a variable, such that we can it mock it in the tests
var newClientSession = func(
var newClientConnection = func(
conn sendConn,
runner sessionRunner,
runner connRunner,
destConnID protocol.ConnectionID,
srcConnID protocol.ConnectionID,
conf *Config,
@ -383,8 +379,8 @@ var newClientSession = func(
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
) quicSession {
s := &session{
) quicConn {
s := &connection{
conn: conn,
config: conf,
origDestConnID: destConnID,
@ -416,7 +412,7 @@ var newClientSession = func(
s.version,
)
s.preSetup()
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), SessionTracingKey, tracingID))
s.ctx, s.ctxCancel = context.WithCancel(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
initialPacketNumber,
getMaxPacketSize(s.conn.RemoteAddr()),
@ -500,7 +496,7 @@ var newClientSession = func(
return s
}
func (s *session) preSetup() {
func (s *connection) preSetup() {
s.sendQueue = newSendQueue(s.conn)
s.retransmissionQueue = newRetransmissionQueue(s.version)
s.frameParser = wire.NewFrameParser(s.config.EnableDatagrams, s.version)
@ -509,10 +505,16 @@ func (s *session) preSetup() {
protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
protocol.ByteCount(s.config.MaxConnectionReceiveWindow),
s.onHasConnectionWindowUpdate,
func(size protocol.ByteCount) bool {
if s.config.AllowConnectionWindowIncrease == nil {
return true
}
return s.config.AllowConnectionWindowIncrease(s, uint64(size))
},
s.rttStats,
s.logger,
)
s.earlySessionReadyChan = make(chan struct{})
s.earlyConnReadyChan = make(chan struct{})
s.streamsMap = newStreamsMap(
s,
s.newFlowController,
@ -522,14 +524,14 @@ func (s *session) preSetup() {
s.version,
)
s.framer = newFramer(s.streamsMap, s.version)
s.receivedPackets = make(chan *receivedPacket, protocol.MaxSessionUnprocessedPackets)
s.receivedPackets = make(chan *receivedPacket, protocol.MaxConnUnprocessedPackets)
s.closeChan = make(chan closeError, 1)
s.sendingScheduled = make(chan struct{}, 1)
s.handshakeCtx, s.handshakeCtxCancel = context.WithCancel(context.Background())
now := time.Now()
s.lastPacketReceivedTime = now
s.sessionCreationTime = now
s.creationTime = now
s.windowUpdateQueue = newWindowUpdateQueue(s.streamsMap, s.connFlowController, s.framer.QueueControlFrame)
if s.config.EnableDatagrams {
@ -537,8 +539,8 @@ func (s *session) preSetup() {
}
}
// run the session main loop
func (s *session) run() error {
// run the connection main loop
func (s *connection) run() error {
defer s.ctxCancel()
s.timer = utils.NewTimer()
@ -556,7 +558,7 @@ func (s *session) run() error {
s.scheduleSending()
if zeroRTTParams != nil {
s.restoreTransportParameters(zeroRTTParams)
close(s.earlySessionReadyChan)
close(s.earlyConnReadyChan)
}
case closeErr := <-s.closeChan:
// put the close error back into the channel, so that the run loop can receive it
@ -590,7 +592,7 @@ runLoop:
if processed := s.handlePacketImpl(p); processed {
processedUndecryptablePacket = true
}
// Don't set timers and send packets if the packet made us close the session.
// Don't set timers and send packets if the packet made us close the connection.
select {
case closeErr = <-s.closeChan:
break runLoop
@ -613,7 +615,7 @@ runLoop:
case <-sendQueueAvailable:
case firstPacket := <-s.receivedPackets:
wasProcessed := s.handlePacketImpl(firstPacket)
// Don't set timers and send packets if the packet made us close the session.
// Don't set timers and send packets if the packet made us close the connection.
select {
case closeErr = <-s.closeChan:
break runLoop
@ -662,11 +664,11 @@ runLoop:
}
if keepAliveTime := s.nextKeepAliveTime(); !keepAliveTime.IsZero() && !now.Before(keepAliveTime) {
// send a PING frame since there is no activity in the session
// send a PING frame since there is no activity in the connection
s.logger.Debugf("Sending a keep-alive PING to keep the connection alive.")
s.framer.QueueControlFrame(&wire.PingFrame{})
s.keepAlivePingSent = true
} else if !s.handshakeComplete && now.Sub(s.sessionCreationTime) >= s.config.handshakeTimeout() {
} else if !s.handshakeComplete && now.Sub(s.creationTime) >= s.config.handshakeTimeout() {
s.destroyImpl(qerr.ErrHandshakeTimeout)
continue
} else {
@ -705,24 +707,24 @@ runLoop:
return closeErr.err
}
// blocks until the early session can be used
func (s *session) earlySessionReady() <-chan struct{} {
return s.earlySessionReadyChan
// blocks until the early connection can be used
func (s *connection) earlyConnReady() <-chan struct{} {
return s.earlyConnReadyChan
}
func (s *session) HandshakeComplete() context.Context {
func (s *connection) HandshakeComplete() context.Context {
return s.handshakeCtx
}
func (s *session) Context() context.Context {
func (s *connection) Context() context.Context {
return s.ctx
}
func (s *session) supportsDatagrams() bool {
func (s *connection) supportsDatagrams() bool {
return s.peerParams.MaxDatagramFrameSize != protocol.InvalidByteCount
}
func (s *session) ConnectionState() ConnectionState {
func (s *connection) ConnectionState() ConnectionState {
return ConnectionState{
TLS: s.cryptoStreamHandler.ConnectionState(),
SupportsDatagrams: s.supportsDatagrams(),
@ -731,18 +733,18 @@ func (s *session) ConnectionState() ConnectionState {
// Time when the next keep-alive packet should be sent.
// It returns a zero time if no keep-alive should be sent.
func (s *session) nextKeepAliveTime() time.Time {
if !s.config.KeepAlive || s.keepAlivePingSent || !s.firstAckElicitingPacketAfterIdleSentTime.IsZero() {
func (s *connection) nextKeepAliveTime() time.Time {
if s.config.KeepAlivePeriod == 0 || s.keepAlivePingSent || !s.firstAckElicitingPacketAfterIdleSentTime.IsZero() {
return time.Time{}
}
return s.lastPacketReceivedTime.Add(s.keepAliveInterval)
}
func (s *session) maybeResetTimer() {
func (s *connection) maybeResetTimer() {
var deadline time.Time
if !s.handshakeComplete {
deadline = utils.MinTime(
s.sessionCreationTime.Add(s.config.handshakeTimeout()),
s.creationTime.Add(s.config.handshakeTimeout()),
s.idleTimeoutStartTime().Add(s.config.HandshakeIdleTimeout),
)
} else {
@ -752,11 +754,6 @@ func (s *session) maybeResetTimer() {
deadline = s.idleTimeoutStartTime().Add(s.idleTimeout)
}
}
if s.handshakeConfirmed && pathMTUDiscoveryEnabled(s.config) {
if probeTime := s.mtuDiscoverer.NextProbeTime(); !probeTime.IsZero() {
deadline = utils.MinTime(deadline, probeTime)
}
}
if ackAlarm := s.receivedPacketHandler.GetAlarmTimeout(); !ackAlarm.IsZero() {
deadline = utils.MinTime(deadline, ackAlarm)
@ -771,11 +768,11 @@ func (s *session) maybeResetTimer() {
s.timer.Reset(deadline)
}
func (s *session) idleTimeoutStartTime() time.Time {
func (s *connection) idleTimeoutStartTime() time.Time {
return utils.MaxTime(s.lastPacketReceivedTime, s.firstAckElicitingPacketAfterIdleSentTime)
}
func (s *session) handleHandshakeComplete() {
func (s *connection) handleHandshakeComplete() {
s.handshakeComplete = true
s.handshakeCompleteChan = nil // prevent this case from ever being selected again
defer s.handshakeCtxCancel()
@ -811,12 +808,12 @@ func (s *session) handleHandshakeComplete() {
s.queueControlFrame(&wire.HandshakeDoneFrame{})
}
func (s *session) handleHandshakeConfirmed() {
func (s *connection) handleHandshakeConfirmed() {
s.handshakeConfirmed = true
s.sentPacketHandler.SetHandshakeConfirmed()
s.cryptoStreamHandler.SetHandshakeConfirmed()
if pathMTUDiscoveryEnabled(s.config) {
if !s.config.DisablePathMTUDiscovery {
maxPacketSize := s.peerParams.MaxUDPPayloadSize
if maxPacketSize == 0 {
maxPacketSize = protocol.MaxByteCount
@ -834,7 +831,7 @@ func (s *session) handleHandshakeConfirmed() {
}
}
func (s *session) handlePacketImpl(rp *receivedPacket) bool {
func (s *connection) handlePacketImpl(rp *receivedPacket) bool {
s.sentPacketHandler.ReceivedBytes(rp.Size())
if wire.IsVersionNegotiationPacket(rp.data) {
@ -902,7 +899,7 @@ func (s *session) handlePacketImpl(rp *receivedPacket) bool {
return processed
}
func (s *session) handleSinglePacket(p *receivedPacket, hdr *wire.Header) bool /* was the packet successfully processed */ {
func (s *connection) handleSinglePacket(p *receivedPacket, hdr *wire.Header) bool /* was the packet successfully processed */ {
var wasQueued bool
defer func() {
@ -994,7 +991,7 @@ func (s *session) handleSinglePacket(p *receivedPacket, hdr *wire.Header) bool /
return true
}
func (s *session) handleRetryPacket(hdr *wire.Header, data []byte) bool /* was this a valid Retry */ {
func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte) bool /* was this a valid Retry */ {
if s.perspective == protocol.PerspectiveServer {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
@ -1056,7 +1053,7 @@ func (s *session) handleRetryPacket(hdr *wire.Header, data []byte) bool /* was t
return true
}
func (s *session) handleVersionNegotiationPacket(p *receivedPacket) {
func (s *connection) handleVersionNegotiationPacket(p *receivedPacket) {
if s.perspective == protocol.PerspectiveServer || // servers never receive version negotiation packets
s.receivedFirstPacket || s.versionNegotiated { // ignore delayed / duplicated version negotiation packets
if s.tracer != nil {
@ -1110,7 +1107,7 @@ func (s *session) handleVersionNegotiationPacket(p *receivedPacket) {
})
}
func (s *session) handleUnpackedPacket(
func (s *connection) handleUnpackedPacket(
packet *unpackedPacket,
ecn protocol.ECN,
rcvTime time.Time,
@ -1142,10 +1139,10 @@ func (s *session) handleUnpackedPacket(
s.handshakeDestConnID = cid
s.connIDManager.ChangeInitialConnID(cid)
}
// We create the session as soon as we receive the first packet from the client.
// We create the connection as soon as we receive the first packet from the client.
// We do that before authenticating the packet.
// That means that if the source connection ID was corrupted,
// we might have create a session with an incorrect source connection ID.
// we might have create a connection with an incorrect source connection ID.
// Once we authenticate the first packet, we need to update it.
if s.perspective == protocol.PerspectiveServer {
if !packet.hdr.SrcConnectionID.Equal(s.handshakeDestConnID) {
@ -1210,7 +1207,7 @@ func (s *session) handleUnpackedPacket(
return s.receivedPacketHandler.ReceivedPacket(packet.packetNumber, ecn, packet.encryptionLevel, rcvTime, isAckEliciting)
}
func (s *session) handleFrame(f wire.Frame, encLevel protocol.EncryptionLevel, destConnID protocol.ConnectionID) error {
func (s *connection) handleFrame(f wire.Frame, encLevel protocol.EncryptionLevel, destConnID protocol.ConnectionID) error {
var err error
wire.LogFrame(s.logger, f, false)
switch frame := f.(type) {
@ -1258,9 +1255,9 @@ func (s *session) handleFrame(f wire.Frame, encLevel protocol.EncryptionLevel, d
}
// handlePacket is called by the server with a new packet
func (s *session) handlePacket(p *receivedPacket) {
func (s *connection) handlePacket(p *receivedPacket) {
// Discard packets once the amount of queued packets is larger than
// the channel size, protocol.MaxSessionUnprocessedPackets
// the channel size, protocol.MaxConnUnprocessedPackets
select {
case s.receivedPackets <- p:
default:
@ -1270,7 +1267,7 @@ func (s *session) handlePacket(p *receivedPacket) {
}
}
func (s *session) handleConnectionCloseFrame(frame *wire.ConnectionCloseFrame) {
func (s *connection) handleConnectionCloseFrame(frame *wire.ConnectionCloseFrame) {
if frame.IsApplicationError {
s.closeRemote(&qerr.ApplicationError{
Remote: true,
@ -1287,7 +1284,7 @@ func (s *session) handleConnectionCloseFrame(frame *wire.ConnectionCloseFrame) {
})
}
func (s *session) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) error {
func (s *connection) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) error {
encLevelChanged, err := s.cryptoStreamManager.HandleCryptoFrame(frame, encLevel)
if err != nil {
return err
@ -1300,7 +1297,7 @@ func (s *session) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.E
return nil
}
func (s *session) handleStreamFrame(frame *wire.StreamFrame) error {
func (s *connection) handleStreamFrame(frame *wire.StreamFrame) error {
str, err := s.streamsMap.GetOrOpenReceiveStream(frame.StreamID)
if err != nil {
return err
@ -1313,11 +1310,11 @@ func (s *session) handleStreamFrame(frame *wire.StreamFrame) error {
return str.handleStreamFrame(frame)
}
func (s *session) handleMaxDataFrame(frame *wire.MaxDataFrame) {
func (s *connection) handleMaxDataFrame(frame *wire.MaxDataFrame) {
s.connFlowController.UpdateSendWindow(frame.MaximumData)
}
func (s *session) handleMaxStreamDataFrame(frame *wire.MaxStreamDataFrame) error {
func (s *connection) handleMaxStreamDataFrame(frame *wire.MaxStreamDataFrame) error {
str, err := s.streamsMap.GetOrOpenSendStream(frame.StreamID)
if err != nil {
return err
@ -1330,11 +1327,11 @@ func (s *session) handleMaxStreamDataFrame(frame *wire.MaxStreamDataFrame) error
return nil
}
func (s *session) handleMaxStreamsFrame(frame *wire.MaxStreamsFrame) {
func (s *connection) handleMaxStreamsFrame(frame *wire.MaxStreamsFrame) {
s.streamsMap.HandleMaxStreamsFrame(frame)
}
func (s *session) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
func (s *connection) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
str, err := s.streamsMap.GetOrOpenReceiveStream(frame.StreamID)
if err != nil {
return err
@ -1346,7 +1343,7 @@ func (s *session) handleResetStreamFrame(frame *wire.ResetStreamFrame) error {
return str.handleResetStreamFrame(frame)
}
func (s *session) handleStopSendingFrame(frame *wire.StopSendingFrame) error {
func (s *connection) handleStopSendingFrame(frame *wire.StopSendingFrame) error {
str, err := s.streamsMap.GetOrOpenSendStream(frame.StreamID)
if err != nil {
return err
@ -1359,11 +1356,11 @@ func (s *session) handleStopSendingFrame(frame *wire.StopSendingFrame) error {
return nil
}
func (s *session) handlePathChallengeFrame(frame *wire.PathChallengeFrame) {
func (s *connection) handlePathChallengeFrame(frame *wire.PathChallengeFrame) {
s.queueControlFrame(&wire.PathResponseFrame{Data: frame.Data})
}
func (s *session) handleNewTokenFrame(frame *wire.NewTokenFrame) error {
func (s *connection) handleNewTokenFrame(frame *wire.NewTokenFrame) error {
if s.perspective == protocol.PerspectiveServer {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
@ -1376,15 +1373,15 @@ func (s *session) handleNewTokenFrame(frame *wire.NewTokenFrame) error {
return nil
}
func (s *session) handleNewConnectionIDFrame(f *wire.NewConnectionIDFrame) error {
func (s *connection) handleNewConnectionIDFrame(f *wire.NewConnectionIDFrame) error {
return s.connIDManager.Add(f)
}
func (s *session) handleRetireConnectionIDFrame(f *wire.RetireConnectionIDFrame, destConnID protocol.ConnectionID) error {
func (s *connection) handleRetireConnectionIDFrame(f *wire.RetireConnectionIDFrame, destConnID protocol.ConnectionID) error {
return s.connIDGenerator.Retire(f.SequenceNumber, destConnID)
}
func (s *session) handleHandshakeDoneFrame() error {
func (s *connection) handleHandshakeDoneFrame() error {
if s.perspective == protocol.PerspectiveServer {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
@ -1397,7 +1394,7 @@ func (s *session) handleHandshakeDoneFrame() error {
return nil
}
func (s *session) handleAckFrame(frame *wire.AckFrame, encLevel protocol.EncryptionLevel) error {
func (s *connection) handleAckFrame(frame *wire.AckFrame, encLevel protocol.EncryptionLevel) error {
acked1RTTPacket, err := s.sentPacketHandler.ReceivedAck(frame, encLevel, s.lastPacketReceivedTime)
if err != nil {
return err
@ -1411,7 +1408,7 @@ func (s *session) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encrypt
return s.cryptoStreamHandler.SetLargest1RTTAcked(frame.LargestAcked())
}
func (s *session) handleDatagramFrame(f *wire.DatagramFrame) error {
func (s *connection) handleDatagramFrame(f *wire.DatagramFrame) error {
if f.Length(s.version) > protocol.ByteCount(s.config.MaxDatagramFrameSize) {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
@ -1422,50 +1419,50 @@ func (s *session) handleDatagramFrame(f *wire.DatagramFrame) error {
return nil
}
// closeLocal closes the session and send a CONNECTION_CLOSE containing the error
func (s *session) closeLocal(e error) {
// closeLocal closes the connection and send a CONNECTION_CLOSE containing the error
func (s *connection) closeLocal(e error) {
s.closeOnce.Do(func() {
if e == nil {
s.logger.Infof("Closing session.")
s.logger.Infof("Closing connection.")
} else {
s.logger.Errorf("Closing session with error: %s", e)
s.logger.Errorf("Closing connection with error: %s", e)
}
s.closeChan <- closeError{err: e, immediate: false, remote: false}
})
}
// destroy closes the session without sending the error on the wire
func (s *session) destroy(e error) {
// destroy closes the connection without sending the error on the wire
func (s *connection) destroy(e error) {
s.destroyImpl(e)
<-s.ctx.Done()
}
func (s *session) destroyImpl(e error) {
func (s *connection) destroyImpl(e error) {
s.closeOnce.Do(func() {
if nerr, ok := e.(net.Error); ok && nerr.Timeout() {
s.logger.Errorf("Destroying session: %s", e)
s.logger.Errorf("Destroying connection: %s", e)
} else {
s.logger.Errorf("Destroying session with error: %s", e)
s.logger.Errorf("Destroying connection with error: %s", e)
}
s.closeChan <- closeError{err: e, immediate: true, remote: false}
})
}
func (s *session) closeRemote(e error) {
func (s *connection) closeRemote(e error) {
s.closeOnce.Do(func() {
s.logger.Errorf("Peer closed session with error: %s", e)
s.logger.Errorf("Peer closed connection with error: %s", e)
s.closeChan <- closeError{err: e, immediate: true, remote: true}
})
}
// Close the connection. It sends a NO_ERROR application error.
// It waits until the run loop has stopped before returning
func (s *session) shutdown() {
func (s *connection) shutdown() {
s.closeLocal(nil)
<-s.ctx.Done()
}
func (s *session) CloseWithError(code ApplicationErrorCode, desc string) error {
func (s *connection) CloseWithError(code ApplicationErrorCode, desc string) error {
s.closeLocal(&qerr.ApplicationError{
ErrorCode: code,
ErrorMessage: desc,
@ -1474,7 +1471,7 @@ func (s *session) CloseWithError(code ApplicationErrorCode, desc string) error {
return nil
}
func (s *session) handleCloseError(closeErr *closeError) {
func (s *connection) handleCloseError(closeErr *closeError) {
e := closeErr.err
if e == nil {
e = &qerr.ApplicationError{}
@ -1518,7 +1515,7 @@ func (s *session) handleCloseError(closeErr *closeError) {
// If this is a remote close we're done here
if closeErr.remote {
s.connIDGenerator.ReplaceWithClosed(newClosedRemoteSession(s.perspective))
s.connIDGenerator.ReplaceWithClosed(newClosedRemoteConn(s.perspective))
return
}
if closeErr.immediate {
@ -1529,11 +1526,11 @@ func (s *session) handleCloseError(closeErr *closeError) {
if err != nil {
s.logger.Debugf("Error sending CONNECTION_CLOSE: %s", err)
}
cs := newClosedLocalSession(s.conn, connClosePacket, s.perspective, s.logger)
cs := newClosedLocalConn(s.conn, connClosePacket, s.perspective, s.logger)
s.connIDGenerator.ReplaceWithClosed(cs)
}
func (s *session) dropEncryptionLevel(encLevel protocol.EncryptionLevel) {
func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel) {
s.sentPacketHandler.DropPackets(encLevel)
s.receivedPacketHandler.DropPackets(encLevel)
if s.tracer != nil {
@ -1551,7 +1548,7 @@ func (s *session) dropEncryptionLevel(encLevel protocol.EncryptionLevel) {
}
// is called for the client, when restoring transport parameters saved for 0-RTT
func (s *session) restoreTransportParameters(params *wire.TransportParameters) {
func (s *connection) restoreTransportParameters(params *wire.TransportParameters) {
if s.logger.Debug() {
s.logger.Debugf("Restoring Transport Parameters: %s", params)
}
@ -1562,7 +1559,7 @@ func (s *session) restoreTransportParameters(params *wire.TransportParameters) {
s.streamsMap.UpdateLimits(params)
}
func (s *session) handleTransportParameters(params *wire.TransportParameters) {
func (s *connection) handleTransportParameters(params *wire.TransportParameters) {
if err := s.checkTransportParameters(params); err != nil {
s.closeLocal(&qerr.TransportError{
ErrorCode: qerr.TransportParameterError,
@ -1574,13 +1571,13 @@ func (s *session) handleTransportParameters(params *wire.TransportParameters) {
// During a 0-RTT connection, we are only allowed to use the new transport parameters for 1-RTT packets.
if s.perspective == protocol.PerspectiveServer {
s.applyTransportParameters()
// On the server side, the early session is ready as soon as we processed
// On the server side, the early connection is ready as soon as we processed
// the client's transport parameters.
close(s.earlySessionReadyChan)
close(s.earlyConnReadyChan)
}
}
func (s *session) checkTransportParameters(params *wire.TransportParameters) error {
func (s *connection) checkTransportParameters(params *wire.TransportParameters) error {
if s.logger.Debug() {
s.logger.Debugf("Processed Transport Parameters: %s", params)
}
@ -1613,11 +1610,11 @@ func (s *session) checkTransportParameters(params *wire.TransportParameters) err
return nil
}
func (s *session) applyTransportParameters() {
func (s *connection) applyTransportParameters() {
params := s.peerParams
// Our local idle timeout will always be > 0.
s.idleTimeout = utils.MinNonZeroDuration(s.config.MaxIdleTimeout, params.MaxIdleTimeout)
s.keepAliveInterval = utils.MinDuration(s.idleTimeout/2, protocol.MaxKeepAliveInterval)
s.keepAliveInterval = utils.MinDuration(s.config.KeepAlivePeriod, utils.MinDuration(s.idleTimeout/2, protocol.MaxKeepAliveInterval))
s.streamsMap.UpdateLimits(params)
s.packer.HandleTransportParameters(params)
s.frameParser.SetAckDelayExponent(params.AckDelayExponent)
@ -1634,7 +1631,7 @@ func (s *session) applyTransportParameters() {
}
}
func (s *session) sendPackets() error {
func (s *connection) sendPackets() error {
s.pacingDeadline = time.Time{}
var sentPacket bool // only used in for packets sent in send mode SendAny
@ -1700,7 +1697,7 @@ func (s *session) sendPackets() error {
}
}
func (s *session) maybeSendAckOnlyPacket() error {
func (s *connection) maybeSendAckOnlyPacket() error {
packet, err := s.packer.MaybePackAckPacket(s.handshakeConfirmed)
if err != nil {
return err
@ -1712,7 +1709,7 @@ func (s *session) maybeSendAckOnlyPacket() error {
return nil
}
func (s *session) sendProbePacket(encLevel protocol.EncryptionLevel) error {
func (s *connection) sendProbePacket(encLevel protocol.EncryptionLevel) error {
// Queue probe packets until we actually send out a packet,
// or until there are no more packets to queue.
var packet *packedPacket
@ -1748,13 +1745,13 @@ func (s *session) sendProbePacket(encLevel protocol.EncryptionLevel) error {
}
}
if packet == nil || packet.packetContents == nil {
return fmt.Errorf("session BUG: couldn't pack %s probe packet", encLevel)
return fmt.Errorf("connection BUG: couldn't pack %s probe packet", encLevel)
}
s.sendPackedPacket(packet, time.Now())
return nil
}
func (s *session) sendPacket() (bool, error) {
func (s *connection) sendPacket() (bool, error) {
if isBlocked, offset := s.connFlowController.IsNewlyBlocked(); isBlocked {
s.framer.QueueControlFrame(&wire.DataBlockedFrame{MaximumData: offset})
}
@ -1777,7 +1774,7 @@ func (s *session) sendPacket() (bool, error) {
s.sendQueue.Send(packet.buffer)
return true, nil
}
if pathMTUDiscoveryEnabled(s.config) && s.mtuDiscoverer.ShouldSendProbe(now) {
if !s.config.DisablePathMTUDiscovery && s.mtuDiscoverer.ShouldSendProbe(now) {
packet, err := s.packer.PackMTUProbePacket(s.mtuDiscoverer.GetPing())
if err != nil {
return false, err
@ -1793,7 +1790,7 @@ func (s *session) sendPacket() (bool, error) {
return true, nil
}
func (s *session) sendPackedPacket(packet *packedPacket, now time.Time) {
func (s *connection) sendPackedPacket(packet *packedPacket, now time.Time) {
if s.firstAckElicitingPacketAfterIdleSentTime.IsZero() && packet.IsAckEliciting() {
s.firstAckElicitingPacketAfterIdleSentTime = now
}
@ -1803,7 +1800,7 @@ func (s *session) sendPackedPacket(packet *packedPacket, now time.Time) {
s.sendQueue.Send(packet.buffer)
}
func (s *session) sendConnectionClose(e error) ([]byte, error) {
func (s *connection) sendConnectionClose(e error) ([]byte, error) {
var packet *coalescedPacket
var err error
var transportErr *qerr.TransportError
@ -1815,7 +1812,7 @@ func (s *session) sendConnectionClose(e error) ([]byte, error) {
} else {
packet, err = s.packer.PackConnectionClose(&qerr.TransportError{
ErrorCode: qerr.InternalError,
ErrorMessage: fmt.Sprintf("session BUG: unspecified error type (msg: %s)", e.Error()),
ErrorMessage: fmt.Sprintf("connection BUG: unspecified error type (msg: %s)", e.Error()),
})
}
if err != nil {
@ -1825,7 +1822,7 @@ func (s *session) sendConnectionClose(e error) ([]byte, error) {
return packet.buffer.Data, s.conn.Write(packet.buffer.Data)
}
func (s *session) logPacketContents(p *packetContents) {
func (s *connection) logPacketContents(p *packetContents) {
// tracing
if s.tracer != nil {
frames := make([]logging.Frame, 0, len(p.frames))
@ -1848,7 +1845,7 @@ func (s *session) logPacketContents(p *packetContents) {
}
}
func (s *session) logCoalescedPacket(packet *coalescedPacket) {
func (s *connection) logCoalescedPacket(packet *coalescedPacket) {
if s.logger.Debug() {
if len(packet.packets) > 1 {
s.logger.Debugf("-> Sending coalesced packet (%d parts, %d bytes) for connection %s", len(packet.packets), packet.buffer.Len(), s.logID)
@ -1861,7 +1858,7 @@ func (s *session) logCoalescedPacket(packet *coalescedPacket) {
}
}
func (s *session) logPacket(packet *packedPacket) {
func (s *connection) logPacket(packet *packedPacket) {
if s.logger.Debug() {
s.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, %s", packet.header.PacketNumber, packet.buffer.Len(), s.logID, packet.EncryptionLevel())
}
@ -1869,32 +1866,32 @@ func (s *session) logPacket(packet *packedPacket) {
}
// AcceptStream returns the next stream openend by the peer
func (s *session) AcceptStream(ctx context.Context) (Stream, error) {
func (s *connection) AcceptStream(ctx context.Context) (Stream, error) {
return s.streamsMap.AcceptStream(ctx)
}
func (s *session) AcceptUniStream(ctx context.Context) (ReceiveStream, error) {
func (s *connection) AcceptUniStream(ctx context.Context) (ReceiveStream, error) {
return s.streamsMap.AcceptUniStream(ctx)
}
// OpenStream opens a stream
func (s *session) OpenStream() (Stream, error) {
func (s *connection) OpenStream() (Stream, error) {
return s.streamsMap.OpenStream()
}
func (s *session) OpenStreamSync(ctx context.Context) (Stream, error) {
func (s *connection) OpenStreamSync(ctx context.Context) (Stream, error) {
return s.streamsMap.OpenStreamSync(ctx)
}
func (s *session) OpenUniStream() (SendStream, error) {
func (s *connection) OpenUniStream() (SendStream, error) {
return s.streamsMap.OpenUniStream()
}
func (s *session) OpenUniStreamSync(ctx context.Context) (SendStream, error) {
func (s *connection) OpenUniStreamSync(ctx context.Context) (SendStream, error) {
return s.streamsMap.OpenUniStreamSync(ctx)
}
func (s *session) newFlowController(id protocol.StreamID) flowcontrol.StreamFlowController {
func (s *connection) newFlowController(id protocol.StreamID) flowcontrol.StreamFlowController {
initialSendWindow := s.peerParams.InitialMaxStreamDataUni
if id.Type() == protocol.StreamTypeBidi {
if id.InitiatedBy() == s.perspective {
@ -1916,14 +1913,14 @@ func (s *session) newFlowController(id protocol.StreamID) flowcontrol.StreamFlow
}
// scheduleSending signals that we have data for sending
func (s *session) scheduleSending() {
func (s *connection) scheduleSending() {
select {
case s.sendingScheduled <- struct{}{}:
default:
}
}
func (s *session) tryQueueingUndecryptablePacket(p *receivedPacket, hdr *wire.Header) {
func (s *connection) tryQueueingUndecryptablePacket(p *receivedPacket, hdr *wire.Header) {
if s.handshakeComplete {
panic("shouldn't queue undecryptable packets after handshake completion")
}
@ -1941,33 +1938,33 @@ func (s *session) tryQueueingUndecryptablePacket(p *receivedPacket, hdr *wire.He
s.undecryptablePackets = append(s.undecryptablePackets, p)
}
func (s *session) queueControlFrame(f wire.Frame) {
func (s *connection) queueControlFrame(f wire.Frame) {
s.framer.QueueControlFrame(f)
s.scheduleSending()
}
func (s *session) onHasStreamWindowUpdate(id protocol.StreamID) {
func (s *connection) onHasStreamWindowUpdate(id protocol.StreamID) {
s.windowUpdateQueue.AddStream(id)
s.scheduleSending()
}
func (s *session) onHasConnectionWindowUpdate() {
func (s *connection) onHasConnectionWindowUpdate() {
s.windowUpdateQueue.AddConnection()
s.scheduleSending()
}
func (s *session) onHasStreamData(id protocol.StreamID) {
func (s *connection) onHasStreamData(id protocol.StreamID) {
s.framer.AddActiveStream(id)
s.scheduleSending()
}
func (s *session) onStreamCompleted(id protocol.StreamID) {
func (s *connection) onStreamCompleted(id protocol.StreamID) {
if err := s.streamsMap.DeleteStream(id); err != nil {
s.closeLocal(err)
}
}
func (s *session) SendMessage(p []byte) error {
func (s *connection) SendMessage(p []byte) error {
f := &wire.DatagramFrame{DataLenPresent: true}
if protocol.ByteCount(len(p)) > f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version) {
return errors.New("message too large")
@ -1977,27 +1974,27 @@ func (s *session) SendMessage(p []byte) error {
return s.datagramQueue.AddAndWait(f)
}
func (s *session) ReceiveMessage() ([]byte, error) {
func (s *connection) ReceiveMessage() ([]byte, error) {
return s.datagramQueue.Receive()
}
func (s *session) LocalAddr() net.Addr {
func (s *connection) LocalAddr() net.Addr {
return s.conn.LocalAddr()
}
func (s *session) RemoteAddr() net.Addr {
func (s *connection) RemoteAddr() net.Addr {
return s.conn.RemoteAddr()
}
func (s *session) getPerspective() protocol.Perspective {
func (s *connection) getPerspective() protocol.Perspective {
return s.perspective
}
func (s *session) GetVersion() protocol.VersionNumber {
func (s *connection) GetVersion() protocol.VersionNumber {
return s.version
}
func (s *session) NextSession() Session {
func (s *connection) NextConnection() Connection {
<-s.HandshakeComplete().Done()
s.streamsMap.UseResetMaps()
return s

View File

@ -59,15 +59,15 @@ type TokenStore interface {
// when the server rejects a 0-RTT connection attempt.
var Err0RTTRejected = errors.New("0-RTT rejected")
// SessionTracingKey can be used to associate a ConnectionTracer with a Session.
// It is set on the Session.Context() context,
// ConnectionTracingKey can be used to associate a ConnectionTracer with a Connection.
// It is set on the Connection.Context() context,
// as well as on the context passed to logging.Tracer.NewConnectionTracer.
var SessionTracingKey = sessionTracingCtxKey{}
var ConnectionTracingKey = connTracingCtxKey{}
type sessionTracingCtxKey struct{}
type connTracingCtxKey struct{}
// Stream is the interface implemented by QUIC streams
// In addition to the errors listed on the Session,
// In addition to the errors listed on the Connection,
// calls to stream functions can return a StreamError if the stream is canceled.
type Stream interface {
ReceiveStream
@ -87,7 +87,7 @@ type ReceiveStream interface {
// after a fixed time limit; see SetDeadline and SetReadDeadline.
// If the stream was canceled by the peer, the error implements the StreamError
// interface, and Canceled() == true.
// If the session was closed due to a timeout, the error satisfies
// If the connection was closed due to a timeout, the error satisfies
// the net.Error interface, and Timeout() will be true.
io.Reader
// CancelRead aborts receiving on this stream.
@ -111,7 +111,7 @@ type SendStream interface {
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
// If the stream was canceled by the peer, the error implements the StreamError
// interface, and Canceled() == true.
// If the session was closed due to a timeout, the error satisfies
// If the connection was closed due to a timeout, the error satisfies
// the net.Error interface, and Timeout() will be true.
io.Writer
// Close closes the write-direction of the stream.
@ -124,7 +124,7 @@ type SendStream interface {
// Write will unblock immediately, and future calls to Write will fail.
// When called multiple times or after closing the stream it is a no-op.
CancelWrite(StreamErrorCode)
// The context is canceled as soon as the write-side of the stream is closed.
// The Context is canceled as soon as the write-side of the stream is closed.
// This happens when Close() or CancelWrite() is called, or when the peer
// cancels the read-side of their stream.
// Warning: This API should not be considered stable and might change soon.
@ -132,26 +132,26 @@ type SendStream interface {
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// some data was successfully written.
// A zero value for t means Write will not time out.
SetWriteDeadline(t time.Time) error
}
// A Session is a QUIC connection between two peers.
// Calls to the session (and to streams) can return the following types of errors:
// A Connection is a QUIC connection between two peers.
// Calls to the connection (and to streams) can return the following types of errors:
// * ApplicationError: for errors triggered by the application running on top of QUIC
// * TransportError: for errors triggered by the QUIC transport (in many cases a misbehaving peer)
// * IdleTimeoutError: when the peer goes away unexpectedly (this is a net.Error timeout error)
// * HandshakeTimeoutError: when the cryptographic handshake takes too long (this is a net.Error timeout error)
// * StatelessResetError: when we receive a stateless reset (this is a net.Error temporary error)
// * VersionNegotiationError: returned by the client, when there's no version overlap between the peers
type Session interface {
type Connection interface {
// AcceptStream returns the next stream opened by the peer, blocking until one is available.
// If the session was closed due to a timeout, the error satisfies
// If the connection was closed due to a timeout, the error satisfies
// the net.Error interface, and Timeout() will be true.
AcceptStream(context.Context) (Stream, error)
// AcceptUniStream returns the next unidirectional stream opened by the peer, blocking until one is available.
// If the session was closed due to a timeout, the error satisfies
// If the connection was closed due to a timeout, the error satisfies
// the net.Error interface, and Timeout() will be true.
AcceptUniStream(context.Context) (ReceiveStream, error)
// OpenStream opens a new bidirectional QUIC stream.
@ -159,22 +159,22 @@ type Session interface {
// The peer can only accept the stream after data has been sent on the stream.
// If the error is non-nil, it satisfies the net.Error interface.
// When reaching the peer's stream limit, err.Temporary() will be true.
// If the session was closed due to a timeout, Timeout() will be true.
// If the connection was closed due to a timeout, Timeout() will be true.
OpenStream() (Stream, error)
// OpenStreamSync opens a new bidirectional QUIC stream.
// It blocks until a new stream can be opened.
// If the error is non-nil, it satisfies the net.Error interface.
// If the session was closed due to a timeout, Timeout() will be true.
// If the connection was closed due to a timeout, Timeout() will be true.
OpenStreamSync(context.Context) (Stream, error)
// OpenUniStream opens a new outgoing unidirectional QUIC stream.
// If the error is non-nil, it satisfies the net.Error interface.
// When reaching the peer's stream limit, Temporary() will be true.
// If the session was closed due to a timeout, Timeout() will be true.
// If the connection was closed due to a timeout, Timeout() will be true.
OpenUniStream() (SendStream, error)
// OpenUniStreamSync opens a new outgoing unidirectional QUIC stream.
// It blocks until a new stream can be opened.
// If the error is non-nil, it satisfies the net.Error interface.
// If the session was closed due to a timeout, Timeout() will be true.
// If the connection was closed due to a timeout, Timeout() will be true.
OpenUniStreamSync(context.Context) (SendStream, error)
// LocalAddr returns the local address.
LocalAddr() net.Addr
@ -183,7 +183,7 @@ type Session interface {
// CloseWithError closes the connection with an error.
// The error string will be sent to the peer.
CloseWithError(ApplicationErrorCode, string) error
// The context is cancelled when the session is closed.
// The context is cancelled when the connection is closed.
// Warning: This API should not be considered stable and might change soon.
Context() context.Context
// ConnectionState returns basic details about the QUIC connection.
@ -199,19 +199,19 @@ type Session interface {
ReceiveMessage() ([]byte, error)
}
// An EarlySession is a session that is handshaking.
// An EarlyConnection is a connection that is handshaking.
// Data sent during the handshake is encrypted using the forward secure keys.
// When using client certificates, the client's identity is only verified
// after completion of the handshake.
type EarlySession interface {
Session
type EarlyConnection interface {
Connection
// Blocks until the handshake completes (or fails).
// HandshakeComplete blocks until the handshake completes (or fails).
// Data sent before completion of the handshake is encrypted with 1-RTT keys.
// Note that the client's identity hasn't been verified yet.
HandshakeComplete() context.Context
NextSession() Session
NextConnection() Connection
}
// Config contains all configuration data needed for a QUIC server or client.
@ -266,6 +266,13 @@ type Config struct {
// MaxConnectionReceiveWindow is the connection-level flow control window for receiving data.
// If this value is zero, it will default to 15 MB.
MaxConnectionReceiveWindow uint64
// AllowConnectionWindowIncrease is called every time the connection flow controller attempts
// to increase the connection flow control window.
// If set, the caller can prevent an increase of the window. Typically, it would do so to
// limit the memory usage.
// To avoid deadlocks, it is not valid to call other functions on the connection or on streams
// in this callback.
AllowConnectionWindowIncrease func(sess 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.
@ -279,11 +286,13 @@ type Config struct {
// The StatelessResetKey is used to generate stateless reset tokens.
// If no key is configured, sending of stateless resets is disabled.
StatelessResetKey []byte
// KeepAlive defines whether this peer will periodically send a packet to keep the connection alive.
KeepAlive bool
// 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 Path MTU discovery is always disabled on Windows, see https://github.com/lucas-clemente/quic-go/issues/3273.
// Note that if Path MTU discovery is causing issues on your system, please open a new issue
DisablePathMTUDiscovery bool
// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
// This can be useful if version information is exchanged out-of-band.
@ -304,21 +313,21 @@ type ConnectionState struct {
// A Listener for incoming QUIC connections
type Listener interface {
// Close the server. All active sessions will be closed.
// Close the server. All active connections will be closed.
Close() error
// Addr returns the local network addr that the server is listening on.
Addr() net.Addr
// Accept returns new sessions. It should be called in a loop.
Accept(context.Context) (Session, error)
// Accept returns new connections. It should be called in a loop.
Accept(context.Context) (Connection, error)
}
// An EarlyListener listens for incoming QUIC connections,
// and returns them before the handshake completes.
type EarlyListener interface {
// Close the server. All active sessions will be closed.
// Close the server. All active connections will be closed.
Close() error
// Addr returns the local network addr that the server is listening on.
Addr() net.Addr
// Accept returns new early sessions. It should be called in a loop.
Accept(context.Context) (EarlySession, error)
// Accept returns new early connections. It should be called in a loop.
Accept(context.Context) (EarlyConnection, error)
}

View File

@ -23,6 +23,8 @@ type baseFlowController struct {
receiveWindowSize protocol.ByteCount
maxReceiveWindowSize protocol.ByteCount
allowWindowIncrease func(size protocol.ByteCount) bool
epochStartTime time.Time
epochStartOffset protocol.ByteCount
rttStats *utils.RTTStats
@ -105,7 +107,10 @@ func (c *baseFlowController) maybeAdjustWindowSize() {
now := time.Now()
if now.Sub(c.epochStartTime) < time.Duration(4*fraction*float64(rtt)) {
// window is consumed too fast, try to increase the window size
c.receiveWindowSize = utils.MinByteCount(2*c.receiveWindowSize, c.maxReceiveWindowSize)
newSize := utils.MinByteCount(2*c.receiveWindowSize, c.maxReceiveWindowSize)
if newSize > c.receiveWindowSize && (c.allowWindowIncrease == nil || c.allowWindowIncrease(newSize-c.receiveWindowSize)) {
c.receiveWindowSize = newSize
}
}
c.startNewAutoTuningEpoch(now)
}

View File

@ -19,11 +19,12 @@ type connectionFlowController struct {
var _ ConnectionFlowController = &connectionFlowController{}
// NewConnectionFlowController gets a new flow controller for the connection
// It is created before we receive the peer's transport paramenters, thus it starts with a sendWindow of 0.
// It is created before we receive the peer's transport parameters, thus it starts with a sendWindow of 0.
func NewConnectionFlowController(
receiveWindow protocol.ByteCount,
maxReceiveWindow protocol.ByteCount,
queueWindowUpdate func(),
allowWindowIncrease func(size protocol.ByteCount) bool,
rttStats *utils.RTTStats,
logger utils.Logger,
) ConnectionFlowController {
@ -33,6 +34,7 @@ func NewConnectionFlowController(
receiveWindow: receiveWindow,
receiveWindowSize: receiveWindow,
maxReceiveWindowSize: maxReceiveWindow,
allowWindowIncrease: allowWindowIncrease,
logger: logger,
},
queueWindowUpdate: queueWindowUpdate,
@ -85,13 +87,16 @@ func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCoun
c.mutex.Lock()
if inc > c.receiveWindowSize {
c.logger.Debugf("Increasing receive flow control window for the connection to %d kB, in response to stream flow control window increase", c.receiveWindowSize/(1<<10))
c.receiveWindowSize = utils.MinByteCount(inc, c.maxReceiveWindowSize)
newSize := utils.MinByteCount(inc, c.maxReceiveWindowSize)
if delta := newSize - c.receiveWindowSize; delta > 0 && c.allowWindowIncrease(delta) {
c.receiveWindowSize = newSize
}
c.startNewAutoTuningEpoch(time.Now())
}
c.mutex.Unlock()
}
// The flow controller is reset when 0-RTT is rejected.
// Reset rests the flow controller. This happens when 0-RTT is rejected.
// All stream data is invalidated, it's if we had never opened a stream and never sent any data.
// At that point, we only have sent stream data, but we didn't have the keys to open 1-RTT keys yet.
func (c *connectionFlowController) Reset() error {

View File

@ -14,7 +14,7 @@ const InitialPacketSizeIPv6 = 1232
// MaxCongestionWindowPackets is the maximum congestion window in packet.
const MaxCongestionWindowPackets = 10000
// MaxUndecryptablePackets limits the number of undecryptable packets that are queued in the session.
// MaxUndecryptablePackets limits the number of undecryptable packets that are queued in the connection.
const MaxUndecryptablePackets = 32
// ConnectionFlowControlMultiplier determines how much larger the connection flow control windows needs to be relative to any stream's flow control window
@ -45,8 +45,8 @@ const DefaultMaxIncomingUniStreams = 100
// MaxServerUnprocessedPackets is the max number of packets stored in the server that are not yet processed.
const MaxServerUnprocessedPackets = 1024
// MaxSessionUnprocessedPackets is the max number of packets stored in each session that are not yet processed.
const MaxSessionUnprocessedPackets = 256
// MaxConnUnprocessedPackets is the max number of packets stored in each connection that are not yet processed.
const MaxConnUnprocessedPackets = 256
// SkipPacketInitialPeriod is the initial period length used for packet number skipping to prevent an Optimistic ACK attack.
// Every time a packet number is skipped, the period is doubled, up to SkipPacketMaxPeriod.
@ -55,7 +55,7 @@ const SkipPacketInitialPeriod PacketNumber = 256
// SkipPacketMaxPeriod is the maximum period length used for packet number skipping.
const SkipPacketMaxPeriod PacketNumber = 128 * 1024
// MaxAcceptQueueSize is the maximum number of sessions that the server queues for accepting.
// MaxAcceptQueueSize is the maximum number of connections that the server queues for accepting.
// If the queue is full, new connection attempts will be rejected.
const MaxAcceptQueueSize = 32
@ -112,7 +112,7 @@ const DefaultHandshakeTimeout = 10 * time.Second
// It should be shorter than the time that NATs clear their mapping.
const MaxKeepAliveInterval = 20 * time.Second
// RetiredConnectionIDDeleteTimeout is the time we keep closed sessions around in order to retransmit the CONNECTION_CLOSE.
// RetiredConnectionIDDeleteTimeout is the time we keep closed connections around in order to retransmit the CONNECTION_CLOSE.
// after this time all information about the old connection will be deleted
const RetiredConnectionIDDeleteTimeout = 5 * time.Second
@ -189,7 +189,7 @@ const Max0RTTQueueingDuration = 100 * time.Millisecond
const Max0RTTQueues = 32
// Max0RTTQueueLen is the maximum number of 0-RTT packets that we buffer for each connection.
// When a new session is created, all buffered packets are passed to the session immediately.
// To avoid blocking, this value has to be smaller than MaxSessionUnprocessedPackets.
// To avoid packets being dropped as undecryptable by the session, this value has to be smaller than MaxUndecryptablePackets.
// When a new connection is created, all buffered packets are passed to the connection immediately.
// To avoid blocking, this value has to be smaller than MaxConnUnprocessedPackets.
// To avoid packets being dropped as undecryptable by the connection, this value has to be smaller than MaxUndecryptablePackets.
const Max0RTTQueueLen = 31

View File

@ -3,4 +3,4 @@
package qtls
var _ int = "quic-go doesn't build on Go 1.19 yet."
var _ int = "The version of quic-go you're using can't be built on Go 1.19 yet. For more details, please see https://github.com/lucas-clemente/quic-go/wiki/quic-go-and-Go-versions."

View File

@ -0,0 +1,7 @@
//go:build (go1.9 || go1.10 || go1.11 || go1.12 || go1.13 || go1.14 || go1.15) && !go1.16
// +build go1.9 go1.10 go1.11 go1.12 go1.13 go1.14 go1.15
// +build !go1.16
package qtls
var _ int = "The version of quic-go you're using can't be built using outdated Go versions. For more details, please see https://github.com/lucas-clemente/quic-go/wiki/quic-go-and-Go-versions."

View File

@ -68,14 +68,14 @@ const (
TimerTypePTO
)
// TimeoutReason is the reason why a session is closed
// TimeoutReason is the reason why a connection is closed
type TimeoutReason uint8
const (
// TimeoutReasonHandshake is used when the session is closed due to a handshake timeout
// TimeoutReasonHandshake is used when the connection is closed due to a handshake timeout
// This reason is not defined in the qlog draft, but very useful for debugging.
TimeoutReasonHandshake TimeoutReason = iota
// TimeoutReasonIdle is used when the session is closed due to an idle timeout
// TimeoutReasonIdle is used when the connection is closed due to an idle timeout
// This reason is not defined in the qlog draft, but very useful for debugging.
TimeoutReasonIdle
)
@ -87,7 +87,7 @@ const (
CongestionStateSlowStart CongestionState = iota
// CongestionStateCongestionAvoidance is the slow start phase of Reno / Cubic
CongestionStateCongestionAvoidance
// CongestionStateCongestionAvoidance is the recovery phase of Reno / Cubic
// CongestionStateRecovery is the recovery phase of Reno / Cubic
CongestionStateRecovery
// CongestionStateApplicationLimited means that the congestion controller is application limited
CongestionStateApplicationLimited

View File

@ -16,8 +16,8 @@ package quic
//go:generate sh -c "./mockgen_private.sh quic mock_unpacker_test.go github.com/lucas-clemente/quic-go unpacker"
//go:generate sh -c "./mockgen_private.sh quic mock_packer_test.go github.com/lucas-clemente/quic-go packer"
//go:generate sh -c "./mockgen_private.sh quic mock_mtu_discoverer_test.go github.com/lucas-clemente/quic-go mtuDiscoverer"
//go:generate sh -c "./mockgen_private.sh quic mock_session_runner_test.go github.com/lucas-clemente/quic-go sessionRunner"
//go:generate sh -c "./mockgen_private.sh quic mock_quic_session_test.go github.com/lucas-clemente/quic-go quicSession"
//go:generate sh -c "./mockgen_private.sh quic mock_conn_runner_test.go github.com/lucas-clemente/quic-go connRunner"
//go:generate sh -c "./mockgen_private.sh quic mock_quic_conn_test.go github.com/lucas-clemente/quic-go quicConn"
//go:generate sh -c "./mockgen_private.sh quic mock_packet_handler_test.go github.com/lucas-clemente/quic-go packetHandler"
//go:generate sh -c "./mockgen_private.sh quic mock_unknown_packet_handler_test.go github.com/lucas-clemente/quic-go unknownPacketHandler"
//go:generate sh -c "./mockgen_private.sh quic mock_packet_handler_manager_test.go github.com/lucas-clemente/quic-go packetHandlerManager"

View File

@ -11,7 +11,6 @@ import (
type mtuDiscoverer interface {
ShouldSendProbe(now time.Time) bool
NextProbeTime() time.Time
GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
}
@ -53,16 +52,7 @@ func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
if f.probeInFlight || f.done() {
return false
}
return !now.Before(f.NextProbeTime())
}
// NextProbeTime returns the time when the next probe packet should be sent.
// It returns the zero value if no probe packet should be sent.
func (f *mtuFinder) NextProbeTime() time.Time {
if f.probeInFlight || f.done() {
return time.Time{}
}
return f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT())
return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
}
func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) {

View File

@ -32,7 +32,7 @@ type connManager struct {
}
// The connMultiplexer listens on multiple net.PacketConns and dispatches
// incoming packets to the session handler.
// incoming packets to the connection handler.
type connMultiplexer struct {
mutex sync.Mutex

View File

@ -7,8 +7,12 @@ import (
"errors"
"fmt"
"hash"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
@ -45,6 +49,14 @@ func (h *zeroRTTQueue) Clear() {
}
}
// rawConn is a connection that allow reading of a receivedPacket.
type rawConn interface {
ReadPacket() (*receivedPacket, error)
WritePacket(b []byte, addr net.Addr, oob []byte) (int, error)
LocalAddr() net.Addr
io.Closer
}
type packetHandlerMapEntry struct {
packetHandler packetHandler
is0RTTQueue bool
@ -52,12 +64,12 @@ type packetHandlerMapEntry struct {
// The packetHandlerMap stores packetHandlers, identified by connection ID.
// It is used:
// * by the server to store sessions
// * by the server to store connections
// * when multiplexing outgoing connections to store clients
type packetHandlerMap struct {
mutex sync.Mutex
conn connection
conn rawConn
connIDLen int
handlers map[string] /* string(ConnectionID)*/ packetHandlerMapEntry
@ -68,7 +80,7 @@ type packetHandlerMap struct {
listening chan struct{} // is closed when listen returns
closed bool
deleteRetiredSessionsAfter time.Duration
deleteRetiredConnsAfter time.Duration
zeroRTTQueueDuration time.Duration
statelessResetEnabled bool
@ -110,7 +122,7 @@ func setReceiveBuffer(c net.PacketConn, logger utils.Logger) error {
return nil
}
// only print warnings about the UPD receive buffer size once
// only print warnings about the UDP receive buffer size once
var receiveBufferWarningOnce sync.Once
func newPacketHandlerMap(
@ -121,10 +133,15 @@ func newPacketHandlerMap(
logger utils.Logger,
) (packetHandlerManager, error) {
if err := setReceiveBuffer(c, logger); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
receiveBufferWarningOnce.Do(func() {
if disable, _ := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING")); disable {
return
}
log.Printf("%s. See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details.", err)
})
}
}
conn, err := wrapConn(c)
if err != nil {
return nil, err
@ -135,7 +152,7 @@ func newPacketHandlerMap(
listening: make(chan struct{}),
handlers: make(map[string]packetHandlerMapEntry),
resetTokens: make(map[protocol.StatelessResetToken]packetHandler),
deleteRetiredSessionsAfter: protocol.RetiredConnectionIDDeleteTimeout,
deleteRetiredConnsAfter: protocol.RetiredConnectionIDDeleteTimeout,
zeroRTTQueueDuration: protocol.Max0RTTQueueingDuration,
statelessResetEnabled: len(statelessResetKey) > 0,
statelessResetHasher: hmac.New(sha256.New, statelessResetKey),
@ -196,7 +213,7 @@ func (h *packetHandlerMap) AddWithConnID(clientDestConnID, newConnID protocol.Co
var q *zeroRTTQueue
if entry, ok := h.handlers[string(clientDestConnID)]; ok {
if !entry.is0RTTQueue {
h.logger.Debugf("Not adding connection ID %s for a new session, as it already exists.", clientDestConnID)
h.logger.Debugf("Not adding connection ID %s for a new connection, as it already exists.", clientDestConnID)
return false
}
q = entry.packetHandler.(*zeroRTTQueue)
@ -212,7 +229,7 @@ func (h *packetHandlerMap) AddWithConnID(clientDestConnID, newConnID protocol.Co
}
h.handlers[string(clientDestConnID)] = packetHandlerMapEntry{packetHandler: sess}
h.handlers[string(newConnID)] = packetHandlerMapEntry{packetHandler: sess}
h.logger.Debugf("Adding connection IDs %s and %s for a new session.", clientDestConnID, newConnID)
h.logger.Debugf("Adding connection IDs %s and %s for a new connection.", clientDestConnID, newConnID)
return true
}
@ -224,8 +241,8 @@ func (h *packetHandlerMap) Remove(id protocol.ConnectionID) {
}
func (h *packetHandlerMap) Retire(id protocol.ConnectionID) {
h.logger.Debugf("Retiring connection ID %s in %s.", id, h.deleteRetiredSessionsAfter)
time.AfterFunc(h.deleteRetiredSessionsAfter, func() {
h.logger.Debugf("Retiring connection ID %s in %s.", id, h.deleteRetiredConnsAfter)
time.AfterFunc(h.deleteRetiredConnsAfter, func() {
h.mutex.Lock()
delete(h.handlers, string(id))
h.mutex.Unlock()
@ -237,14 +254,14 @@ func (h *packetHandlerMap) ReplaceWithClosed(id protocol.ConnectionID, handler p
h.mutex.Lock()
h.handlers[string(id)] = packetHandlerMapEntry{packetHandler: handler}
h.mutex.Unlock()
h.logger.Debugf("Replacing session for connection ID %s with a closed session.", id)
h.logger.Debugf("Replacing connection for connection ID %s with a closed connection.", id)
time.AfterFunc(h.deleteRetiredSessionsAfter, func() {
time.AfterFunc(h.deleteRetiredConnsAfter, func() {
h.mutex.Lock()
handler.shutdown()
delete(h.handlers, string(id))
h.mutex.Unlock()
h.logger.Debugf("Removing connection ID %s for a closed session after it has been retired.", id)
h.logger.Debugf("Removing connection ID %s for a closed connection after it has been retired.", id)
})
}
@ -289,7 +306,7 @@ func (h *packetHandlerMap) CloseServer() {
}
// Destroy closes the underlying connection and waits until listen() has returned.
// It does not close active sessions.
// It does not close active connections.
func (h *packetHandlerMap) Destroy() error {
if err := h.conn.Close(); err != nil {
return err
@ -327,6 +344,10 @@ func (h *packetHandlerMap) listen() {
defer close(h.listening)
for {
p, err := h.conn.ReadPacket()
//nolint:staticcheck // SA1019 ignore this!
// TODO: This code is used to ignore wsa errors on Windows.
// Since net.Error.Temporary is deprecated as of Go 1.18, we should find a better solution.
// See https://github.com/lucas-clemente/quic-go/issues/1737 for details.
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
h.logger.Debugf("Temporary error reading from conn: %w", err)
continue
@ -363,7 +384,7 @@ func (h *packetHandlerMap) handlePacket(p *receivedPacket) {
entry.packetHandler.handlePacket(p)
return
}
} else { // existing session
} else { // existing connection
entry.packetHandler.handlePacket(p)
return
}
@ -389,7 +410,7 @@ func (h *packetHandlerMap) handlePacket(p *receivedPacket) {
queue.retireTimer = time.AfterFunc(h.zeroRTTQueueDuration, func() {
h.mutex.Lock()
defer h.mutex.Unlock()
// The entry might have been replaced by an actual session.
// The entry might have been replaced by an actual connection.
// Only delete it if it's still a 0-RTT queue.
if entry, ok := h.handlers[string(connID)]; ok && entry.is0RTTQueue {
delete(h.handlers, string(connID))
@ -421,7 +442,7 @@ func (h *packetHandlerMap) maybeHandleStatelessReset(data []byte) bool {
var token protocol.StatelessResetToken
copy(token[:], data[len(data)-16:])
if sess, ok := h.resetTokens[token]; ok {
h.logger.Debugf("Received a stateless reset with token %#x. Closing session.", token)
h.logger.Debugf("Received a stateless reset with token %#x. Closing connection.", token)
go sess.destroy(&StatelessResetError{Token: token})
return true
}

View File

@ -13,7 +13,7 @@ type sendConn interface {
}
type sconn struct {
connection
rawConn
remoteAddr net.Addr
info *packetInfo
@ -22,9 +22,9 @@ type sconn struct {
var _ sendConn = &sconn{}
func newSendConn(c connection, remote net.Addr, info *packetInfo) sendConn {
func newSendConn(c rawConn, remote net.Addr, info *packetInfo) sendConn {
return &sconn{
connection: c,
rawConn: c,
remoteAddr: remote,
info: info,
oob: info.OOB(),
@ -41,7 +41,7 @@ func (c *sconn) RemoteAddr() net.Addr {
}
func (c *sconn) LocalAddr() net.Addr {
addr := c.connection.LocalAddr()
addr := c.rawConn.LocalAddr()
if c.info != nil {
if udpAddr, ok := addr.(*net.UDPAddr); ok {
addrCopy := *udpAddr

View File

@ -64,8 +64,14 @@ func (h *sendQueue) Run() error {
shouldClose = true
case p := <-h.queue:
if err := h.conn.Write(p.Data); 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) {
return err
}
}
p.Release()
select {
case h.available <- struct{}{}:

View File

@ -36,14 +36,14 @@ type unknownPacketHandler interface {
type packetHandlerManager interface {
AddWithConnID(protocol.ConnectionID, protocol.ConnectionID, func() packetHandler) bool
Destroy() error
sessionRunner
connRunner
SetServer(unknownPacketHandler)
CloseServer()
}
type quicSession interface {
EarlySession
earlySessionReady() <-chan struct{}
type quicConn interface {
EarlyConnection
earlyConnReady() <-chan struct{}
handlePacket(*receivedPacket)
GetVersion() protocol.VersionNumber
getPerspective() protocol.Perspective
@ -56,26 +56,26 @@ type quicSession interface {
type baseServer struct {
mutex sync.Mutex
acceptEarlySessions bool
acceptEarlyConns bool
tlsConf *tls.Config
config *Config
conn connection
conn rawConn
// If the server is started with ListenAddr, we create a packet conn.
// If it is started with Listen, we take a packet conn as a parameter.
createdPacketConn bool
tokenGenerator *handshake.TokenGenerator
sessionHandler packetHandlerManager
connHandler packetHandlerManager
receivedPackets chan *receivedPacket
// set as a member, so they can be set in the tests
newSession func(
newConn func(
sendConn,
sessionRunner,
connRunner,
protocol.ConnectionID, /* original dest connection ID */
*protocol.ConnectionID, /* retry src connection ID */
protocol.ConnectionID, /* client dest connection ID */
@ -90,15 +90,15 @@ type baseServer struct {
uint64,
utils.Logger,
protocol.VersionNumber,
) quicSession
) quicConn
serverError error
errorChan chan struct{}
closed bool
running chan struct{} // closed as soon as run() returns
sessionQueue chan quicSession
sessionQueueLen int32 // to be used as an atomic
connQueue chan quicConn
connQueueLen int32 // to be used as an atomic
logger utils.Logger
}
@ -112,7 +112,7 @@ type earlyServer struct{ *baseServer }
var _ EarlyListener = &earlyServer{}
func (s *earlyServer) Accept(ctx context.Context) (EarlySession, error) {
func (s *earlyServer) Accept(ctx context.Context) (EarlyConnection, error) {
return s.baseServer.accept(ctx)
}
@ -123,7 +123,7 @@ func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (Listener, err
return listenAddr(addr, tlsConf, config, false)
}
// ListenAddrEarly works like ListenAddr, but it returns sessions before the handshake completes.
// ListenAddrEarly works like ListenAddr, but it returns connections before the handshake completes.
func ListenAddrEarly(addr string, tlsConf *tls.Config, config *Config) (EarlyListener, error) {
s, err := listenAddr(addr, tlsConf, config, true)
if err != nil {
@ -164,7 +164,7 @@ func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (Listener,
return listen(conn, tlsConf, config, false)
}
// ListenEarly works like Listen, but it returns sessions before the handshake completes.
// ListenEarly works like Listen, but it returns connections before the handshake completes.
func ListenEarly(conn net.PacketConn, tlsConf *tls.Config, config *Config) (EarlyListener, error) {
s, err := listen(conn, tlsConf, config, true)
if err != nil {
@ -187,7 +187,7 @@ func listen(conn net.PacketConn, tlsConf *tls.Config, config *Config, acceptEarl
}
}
sessionHandler, err := getMultiplexer().AddConn(conn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
connHandler, err := getMultiplexer().AddConn(conn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
if err != nil {
return nil, err
}
@ -204,17 +204,17 @@ func listen(conn net.PacketConn, tlsConf *tls.Config, config *Config, acceptEarl
tlsConf: tlsConf,
config: config,
tokenGenerator: tokenGenerator,
sessionHandler: sessionHandler,
sessionQueue: make(chan quicSession),
connHandler: connHandler,
connQueue: make(chan quicConn),
errorChan: make(chan struct{}),
running: make(chan struct{}),
receivedPackets: make(chan *receivedPacket, protocol.MaxServerUnprocessedPackets),
newSession: newSession,
newConn: newConnection,
logger: utils.DefaultLogger.WithPrefix("server"),
acceptEarlySessions: acceptEarly,
acceptEarlyConns: acceptEarly,
}
go s.run()
sessionHandler.SetServer(s)
connHandler.SetServer(s)
s.logger.Debugf("Listening for %s connections on %s", conn.LocalAddr().Network(), conn.LocalAddr().String())
return s, nil
}
@ -258,19 +258,19 @@ var defaultAcceptToken = func(clientAddr net.Addr, token *Token) bool {
return sourceAddr == token.RemoteAddr
}
// Accept returns sessions that already completed the handshake.
// It is only valid if acceptEarlySessions is false.
func (s *baseServer) Accept(ctx context.Context) (Session, error) {
// 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) {
return s.accept(ctx)
}
func (s *baseServer) accept(ctx context.Context) (quicSession, error) {
func (s *baseServer) accept(ctx context.Context) (quicConn, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case sess := <-s.sessionQueue:
atomic.AddInt32(&s.sessionQueueLen, -1)
return sess, nil
case conn := <-s.connQueue:
atomic.AddInt32(&s.connQueueLen, -1)
return conn, nil
case <-s.errorChan:
return nil, s.serverError
}
@ -294,9 +294,9 @@ func (s *baseServer) Close() error {
s.mutex.Unlock()
<-s.running
s.sessionHandler.CloseServer()
s.connHandler.CloseServer()
if createdPacketConn {
return s.sessionHandler.Destroy()
return s.connHandler.Destroy()
}
return nil
}
@ -336,7 +336,7 @@ func (s *baseServer) handlePacketImpl(p *receivedPacket) bool /* is the buffer s
}
return false
}
// If we're creating a new session, the packet will be passed to the session.
// If we're creating a new connection, the packet will be passed to the connection.
// The header will then be parsed again.
hdr, _, _, err := wire.ParsePacket(p.data, s.config.ConnectionIDLength)
if err != nil && err != wire.ErrUnsupportedVersion {
@ -436,7 +436,7 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
return nil
}
if queueLen := atomic.LoadInt32(&s.sessionQueueLen); queueLen >= protocol.MaxAcceptQueueSize {
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()
@ -452,9 +452,9 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
return err
}
s.logger.Debugf("Changing connection ID to %s.", connID)
var sess quicSession
tracingID := nextSessionTracingID()
if added := s.sessionHandler.AddWithConnID(hdr.DestConnectionID, connID, func() packetHandler {
var conn quicConn
tracingID := nextConnTracingID()
if added := s.connHandler.AddWithConnID(hdr.DestConnectionID, connID, func() packetHandler {
var tracer logging.ConnectionTracer
if s.config.Tracer != nil {
// Use the same connection ID that is passed to the client's GetLogWriter callback.
@ -463,74 +463,74 @@ func (s *baseServer) handleInitialImpl(p *receivedPacket, hdr *wire.Header) erro
connID = origDestConnID
}
tracer = s.config.Tracer.TracerForConnection(
context.WithValue(context.Background(), SessionTracingKey, tracingID),
context.WithValue(context.Background(), ConnectionTracingKey, tracingID),
protocol.PerspectiveServer,
connID,
)
}
sess = s.newSession(
conn = s.newConn(
newSendConn(s.conn, p.remoteAddr, p.info),
s.sessionHandler,
s.connHandler,
origDestConnID,
retrySrcConnID,
hdr.DestConnectionID,
hdr.SrcConnectionID,
connID,
s.sessionHandler.GetStatelessResetToken(connID),
s.connHandler.GetStatelessResetToken(connID),
s.config,
s.tlsConf,
s.tokenGenerator,
s.acceptEarlySessions,
s.acceptEarlyConns,
tracer,
tracingID,
s.logger,
hdr.Version,
)
sess.handlePacket(p)
return sess
conn.handlePacket(p)
return conn
}); !added {
return nil
}
go sess.run()
go s.handleNewSession(sess)
if sess == nil {
go conn.run()
go s.handleNewConn(conn)
if conn == nil {
p.buffer.Release()
return nil
}
return nil
}
func (s *baseServer) handleNewSession(sess quicSession) {
sessCtx := sess.Context()
if s.acceptEarlySessions {
// wait until the early session is ready (or the handshake fails)
func (s *baseServer) handleNewConn(conn quicConn) {
connCtx := conn.Context()
if s.acceptEarlyConns {
// wait until the early connection is ready (or the handshake fails)
select {
case <-sess.earlySessionReady():
case <-sessCtx.Done():
case <-conn.earlyConnReady():
case <-connCtx.Done():
return
}
} else {
// wait until the handshake is complete (or fails)
select {
case <-sess.HandshakeComplete().Done():
case <-sessCtx.Done():
case <-conn.HandshakeComplete().Done():
case <-connCtx.Done():
return
}
}
atomic.AddInt32(&s.sessionQueueLen, 1)
atomic.AddInt32(&s.connQueueLen, 1)
select {
case s.sessionQueue <- sess:
// blocks until the session is accepted
case <-sessCtx.Done():
atomic.AddInt32(&s.sessionQueueLen, -1)
// don't pass sessions that were already closed to Accept()
case s.connQueue <- conn:
// blocks until the connection is accepted
case <-connCtx.Done():
atomic.AddInt32(&s.connQueueLen, -1)
// don't pass connections that were already closed to Accept()
}
}
func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header, info *packetInfo) error {
// Log the Initial packet now.
// If no Retry is sent, the packet will be logged by the session.
// If no Retry is sent, the packet will be logged by the connection.
(&wire.ExtendedHeader{Header: *hdr}).Log(s.logger)
srcConnID, err := protocol.GenerateConnectionID(s.config.ConnectionIDLength)
if err != nil {

View File

@ -1,7 +1,6 @@
package quic
import (
"io"
"net"
"syscall"
"time"
@ -10,14 +9,8 @@ import (
"github.com/lucas-clemente/quic-go/internal/utils"
)
type connection interface {
ReadPacket() (*receivedPacket, error)
WritePacket(b []byte, addr net.Addr, oob []byte) (int, error)
LocalAddr() net.Addr
io.Closer
}
// If the PacketConn passed to Dial or Listen satisfies this interface, quic-go will read the ECN bits from the IP header.
// OOBCapablePacketConn is a connection that allows the reading of ECN bits from the IP header.
// If the PacketConn passed to Dial or Listen satisfies this interface, quic-go will use it.
// In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets.
type OOBCapablePacketConn interface {
net.PacketConn
@ -28,7 +21,20 @@ type OOBCapablePacketConn interface {
var _ OOBCapablePacketConn = &net.UDPConn{}
func wrapConn(pc net.PacketConn) (connection, error) {
func wrapConn(pc net.PacketConn) (rawConn, error) {
conn, ok := pc.(interface {
SyscallConn() (syscall.RawConn, error)
})
if ok {
rawConn, err := conn.SyscallConn()
if err != nil {
return nil, err
}
err = setDF(rawConn)
if err != nil {
return nil, err
}
}
c, ok := pc.(OOBCapablePacketConn)
if !ok {
utils.DefaultLogger.Infof("PacketConn is not a net.UDPConn. Disabling optimizations possible on UDP connections.")
@ -37,11 +43,16 @@ func wrapConn(pc net.PacketConn) (connection, error) {
return newConn(c)
}
// The basicConn is the most trivial implementation of a connection.
// 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
}
var _ connection = &basicConn{}
var _ rawConn = &basicConn{}
func (c *basicConn) ReadPacket() (*receivedPacket, error) {
buffer := getPacketBuffer()

View File

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

View File

@ -0,0 +1,41 @@
//go:build linux
// +build linux
package quic
import (
"errors"
"syscall"
"golang.org/x/sys/unix"
"github.com/lucas-clemente/quic-go/internal/utils"
)
func setDF(rawConn syscall.RawConn) 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
if err := rawConn.Control(func(fd uintptr) {
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
}
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.")
case errDFIPv4 != nil && errDFIPv6 != nil:
utils.DefaultLogger.Errorf("setting DF failed for both IPv4 and IPv6")
}
return nil
}
func isMsgSizeErr(err error) bool {
// https://man7.org/linux/man-pages/man7/udp.7.html
return errors.Is(err, unix.EMSGSIZE)
}

View File

@ -0,0 +1,46 @@
//go:build windows
// +build windows
package quic
import (
"errors"
"syscall"
"github.com/lucas-clemente/quic-go/internal/utils"
"golang.org/x/sys/windows"
)
const (
// same 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
IP_DONTFRAGMENT = 14
IPV6_DONTFRAG = 14
)
func setDF(rawConn syscall.RawConn) 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)
}); err != nil {
return 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.")
case errDFIPv4 != nil && errDFIPv6 != nil:
return errors.New("setting DF failed for both IPv4 and IPv6")
}
return nil
}
func isMsgSizeErr(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

@ -5,10 +5,7 @@ package quic
import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_RECVTOS
disablePathMTUDiscovery = false
)
const msgTypeIPTOS = unix.IP_RECVTOS
const (
ipv4RECVPKTINFO = unix.IP_RECVPKTINFO

View File

@ -7,7 +7,6 @@ import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_RECVTOS
disablePathMTUDiscovery = false
)
const (

View File

@ -5,10 +5,7 @@ package quic
import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_TOS
disablePathMTUDiscovery = false
)
const msgTypeIPTOS = unix.IP_TOS
const (
ipv4RECVPKTINFO = unix.IP_PKTINFO

View File

@ -5,9 +5,7 @@ package quic
import "net"
const disablePathMTUDiscovery = false
func newConn(c net.PacketConn) (connection, error) {
func newConn(c net.PacketConn) (rawConn, error) {
return &basicConn{PacketConn: c}, nil
}

View File

@ -64,7 +64,7 @@ type oobConn struct {
buffers [batchSize]*packetBuffer
}
var _ connection = &oobConn{}
var _ rawConn = &oobConn{}
func newConn(c OOBCapablePacketConn) (*oobConn, error) {
rawConn, err := c.SyscallConn()

View File

@ -12,24 +12,7 @@ import (
"golang.org/x/sys/windows"
)
const (
disablePathMTUDiscovery = true
IP_DONTFRAGMENT = 14
)
func newConn(c OOBCapablePacketConn) (connection, error) {
rawConn, err := c.SyscallConn()
if err != nil {
return nil, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
if err := rawConn.Control(func(fd uintptr) {
// This should succeed if the connection is a IPv4 or a dual-stack connection.
// It will fail for IPv6 connections.
// TODO: properly handle error.
_ = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_DONTFRAGMENT, 1)
}); err != nil {
return nil, err
}
func newConn(c OOBCapablePacketConn) (rawConn, error) {
return &basicConn{PacketConn: c}, nil
}

View File

@ -20,13 +20,10 @@ import (
"fmt"
"io"
"net"
"runtime"
"sort"
"strings"
"sync"
"time"
"golang.org/x/sys/cpu"
)
const (
@ -1463,17 +1460,6 @@ func defaultCipherSuitesTLS13() []uint16 {
return varDefaultCipherSuitesTLS13
}
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)
func initDefaultCipherSuites() {
var topCipherSuites []uint16

View File

@ -0,0 +1,12 @@
// +build js
package qtls
var (
hasGCMAsmAMD64 = false
hasGCMAsmARM64 = false
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = false
hasAESGCMHardwareSupport = false
)

View File

@ -0,0 +1,20 @@
// +build !js
package qtls
import (
"runtime"
"golang.org/x/sys/cpu"
)
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)

View File

@ -15,10 +15,8 @@ import (
"crypto/sha256"
"fmt"
"hash"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/sys/cpu"
)
// CipherSuite is a TLS cipher suite. Note that most functions in this package
@ -365,18 +363,6 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
TLS_AES_256_GCM_SHA384,
}
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)
var aesgcmCiphers = map[uint16]bool{
// TLS 1.2
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,

22
vendor/github.com/marten-seemann/qtls-go1-17/cpu.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
//go:build !js
// +build !js
package qtls
import (
"runtime"
"golang.org/x/sys/cpu"
)
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)

View File

@ -0,0 +1,12 @@
//go:build js
// +build js
package qtls
var (
hasGCMAsmAMD64 = false
hasGCMAsmARM64 = false
hasGCMAsmS390X = false
hasAESGCMHardwareSupport = false
)

View File

@ -15,10 +15,8 @@ import (
"crypto/sha256"
"fmt"
"hash"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/sys/cpu"
)
// CipherSuite is a TLS cipher suite. Note that most functions in this package
@ -365,18 +363,6 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
TLS_AES_256_GCM_SHA384,
}
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)
var aesgcmCiphers = map[uint16]bool{
// TLS 1.2
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,

22
vendor/github.com/marten-seemann/qtls-go1-18/cpu.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
//go:build !js
// +build !js
package qtls
import (
"runtime"
"golang.org/x/sys/cpu"
)
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)

View File

@ -0,0 +1,12 @@
//go:build js
// +build js
package qtls
var (
hasGCMAsmAMD64 = false
hasGCMAsmARM64 = false
hasGCMAsmS390X = false
hasAESGCMHardwareSupport = false
)

10
vendor/modules.txt vendored
View File

@ -201,7 +201,7 @@ github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc
github.com/json-iterator/go
# github.com/kylelemons/godebug v1.1.0
## explicit; go 1.11
# github.com/lucas-clemente/quic-go v0.24.0 => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
# github.com/lucas-clemente/quic-go v0.27.1 => github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da
## explicit; go 1.16
github.com/lucas-clemente/quic-go
github.com/lucas-clemente/quic-go/internal/ackhandler
@ -219,13 +219,13 @@ github.com/lucas-clemente/quic-go/quicvarint
# github.com/lucasb-eyer/go-colorful v1.0.3
## explicit; go 1.12
github.com/lucasb-eyer/go-colorful
# github.com/marten-seemann/qtls-go1-16 v0.1.4
# github.com/marten-seemann/qtls-go1-16 v0.1.5
## explicit; go 1.16
github.com/marten-seemann/qtls-go1-16
# github.com/marten-seemann/qtls-go1-17 v0.1.0
# github.com/marten-seemann/qtls-go1-17 v0.1.1
## explicit; go 1.17
github.com/marten-seemann/qtls-go1-17
# github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1
# github.com/marten-seemann/qtls-go1-18 v0.1.1
## explicit; go 1.18
github.com/marten-seemann/qtls-go1-18
# github.com/mattn/go-colorable v0.1.8
@ -612,5 +612,5 @@ zombiezen.com/go/capnproto2/schemas
zombiezen.com/go/capnproto2/server
zombiezen.com/go/capnproto2/std/capnp/rpc
# github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
# github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
# github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da
# github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1