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. // QUICConnection represents the type that facilitates Proxying via QUIC streams.
type QUICConnection struct { type QUICConnection struct {
session quic.Session session quic.Connection
logger *zerolog.Logger logger *zerolog.Logger
orchestrator Orchestrator orchestrator Orchestrator
sessionManager datagramsession.Manager sessionManager datagramsession.Manager

View File

@ -31,7 +31,7 @@ import (
var ( var (
testTLSServerConfig = quicpogs.GenerateTLSConfig() testTLSServerConfig = quicpogs.GenerateTLSConfig()
testQUICConfig = &quic.Config{ testQUICConfig = &quic.Config{
KeepAlive: true, KeepAlivePeriod: 5 * time.Second,
EnableDatagrams: true, EnableDatagrams: true,
} }
) )
@ -502,7 +502,7 @@ func TestServeUDPSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
// Establish QUIC connection with edge // Establish QUIC connection with edge
edgeQUICSessionChan := make(chan quic.Session) edgeQUICSessionChan := make(chan quic.Connection)
go func() { go func() {
earlyListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig) earlyListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig)
require.NoError(t, err) require.NoError(t, err)
@ -522,7 +522,7 @@ func TestServeUDPSession(t *testing.T) {
cancel() 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 ( var (
payload = []byte(t.Name()) payload = []byte(t.Name())
) )
@ -583,7 +583,7 @@ const (
closedByTimeout 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) stream, err := session.AcceptStream(ctx)
require.NoError(t, err) require.NoError(t, err)

10
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/json-iterator/go v1.1.12 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/mattn/go-colorable v0.1.8
github.com/miekg/dns v1.1.45 github.com/miekg/dns v1.1.45
github.com/mitchellh/go-homedir v1.1.0 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/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // 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-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.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-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // 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/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' // Avoid 'CVE-2022-21698'
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1 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/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 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 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.27.1-0.20220607112311-13144fbde8da h1:FmuwbQ8RU/ftTKnfz5diawqvQFH1KDB9wN2Q8S2wqds=
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/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/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/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.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
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.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk= github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 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-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=

View File

@ -14,11 +14,11 @@ const (
) )
type DatagramMuxer struct { type DatagramMuxer struct {
session quic.Session session quic.Connection
logger *zerolog.Logger 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{ return &DatagramMuxer{
session: quicSession, session: quicSession,
logger: logger, logger: logger,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,6 @@
<img src="docs/quic.png" width=303 height=124> <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) [![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/) [![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. 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 ## 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: 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) | | [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) | | [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) | | [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) | | [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) | | [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) | | [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 ## Contributing

View File

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

View File

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

View File

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

View File

@ -59,15 +59,15 @@ type TokenStore interface {
// when the server rejects a 0-RTT connection attempt. // when the server rejects a 0-RTT connection attempt.
var Err0RTTRejected = errors.New("0-RTT rejected") var Err0RTTRejected = errors.New("0-RTT rejected")
// SessionTracingKey can be used to associate a ConnectionTracer with a Session. // ConnectionTracingKey can be used to associate a ConnectionTracer with a Connection.
// It is set on the Session.Context() context, // It is set on the Connection.Context() context,
// as well as on the context passed to logging.Tracer.NewConnectionTracer. // 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 // 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. // calls to stream functions can return a StreamError if the stream is canceled.
type Stream interface { type Stream interface {
ReceiveStream ReceiveStream
@ -87,7 +87,7 @@ type ReceiveStream interface {
// after a fixed time limit; see SetDeadline and SetReadDeadline. // after a fixed time limit; see SetDeadline and SetReadDeadline.
// If the stream was canceled by the peer, the error implements the StreamError // If the stream was canceled by the peer, the error implements the StreamError
// interface, and Canceled() == true. // 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. // the net.Error interface, and Timeout() will be true.
io.Reader io.Reader
// CancelRead aborts receiving on this stream. // CancelRead aborts receiving on this stream.
@ -111,7 +111,7 @@ type SendStream interface {
// after a fixed time limit; see SetDeadline and SetWriteDeadline. // after a fixed time limit; see SetDeadline and SetWriteDeadline.
// If the stream was canceled by the peer, the error implements the StreamError // If the stream was canceled by the peer, the error implements the StreamError
// interface, and Canceled() == true. // 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. // the net.Error interface, and Timeout() will be true.
io.Writer io.Writer
// Close closes the write-direction of the stream. // 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. // Write will unblock immediately, and future calls to Write will fail.
// When called multiple times or after closing the stream it is a no-op. // When called multiple times or after closing the stream it is a no-op.
CancelWrite(StreamErrorCode) CancelWrite(StreamErrorCode)
// The context is canceled as soon as the write-side of the stream is closed. // The Context is canceled as soon as the write-side of the stream is closed.
// This happens when Close() or CancelWrite() is called, or when the peer // This happens when Close() or CancelWrite() is called, or when the peer
// cancels the read-side of their stream. // cancels the read-side of their stream.
// Warning: This API should not be considered stable and might change soon. // 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 // SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call. // and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that // 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. // A zero value for t means Write will not time out.
SetWriteDeadline(t time.Time) error SetWriteDeadline(t time.Time) error
} }
// A Session is a QUIC connection between two peers. // A Connection is a QUIC connection between two peers.
// Calls to the session (and to streams) can return the following types of errors: // 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 // * 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) // * 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) // * 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) // * 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) // * 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 // * 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. // 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. // the net.Error interface, and Timeout() will be true.
AcceptStream(context.Context) (Stream, error) AcceptStream(context.Context) (Stream, error)
// AcceptUniStream returns the next unidirectional stream opened by the peer, blocking until one is available. // 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. // the net.Error interface, and Timeout() will be true.
AcceptUniStream(context.Context) (ReceiveStream, error) AcceptUniStream(context.Context) (ReceiveStream, error)
// OpenStream opens a new bidirectional QUIC stream. // 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. // 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. // If the error is non-nil, it satisfies the net.Error interface.
// When reaching the peer's stream limit, err.Temporary() will be true. // 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) OpenStream() (Stream, error)
// OpenStreamSync opens a new bidirectional QUIC stream. // OpenStreamSync opens a new bidirectional QUIC stream.
// It blocks until a new stream can be opened. // It blocks until a new stream can be opened.
// If the error is non-nil, it satisfies the net.Error interface. // 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) OpenStreamSync(context.Context) (Stream, error)
// OpenUniStream opens a new outgoing unidirectional QUIC stream. // OpenUniStream opens a new outgoing unidirectional QUIC stream.
// If the error is non-nil, it satisfies the net.Error interface. // If the error is non-nil, it satisfies the net.Error interface.
// When reaching the peer's stream limit, Temporary() will be true. // 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) OpenUniStream() (SendStream, error)
// OpenUniStreamSync opens a new outgoing unidirectional QUIC stream. // OpenUniStreamSync opens a new outgoing unidirectional QUIC stream.
// It blocks until a new stream can be opened. // It blocks until a new stream can be opened.
// If the error is non-nil, it satisfies the net.Error interface. // 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) OpenUniStreamSync(context.Context) (SendStream, error)
// LocalAddr returns the local address. // LocalAddr returns the local address.
LocalAddr() net.Addr LocalAddr() net.Addr
@ -183,7 +183,7 @@ type Session interface {
// CloseWithError closes the connection with an error. // CloseWithError closes the connection with an error.
// The error string will be sent to the peer. // The error string will be sent to the peer.
CloseWithError(ApplicationErrorCode, string) error 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. // Warning: This API should not be considered stable and might change soon.
Context() context.Context Context() context.Context
// ConnectionState returns basic details about the QUIC connection. // ConnectionState returns basic details about the QUIC connection.
@ -199,19 +199,19 @@ type Session interface {
ReceiveMessage() ([]byte, error) 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. // Data sent during the handshake is encrypted using the forward secure keys.
// When using client certificates, the client's identity is only verified // When using client certificates, the client's identity is only verified
// after completion of the handshake. // after completion of the handshake.
type EarlySession interface { type EarlyConnection interface {
Session 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. // Data sent before completion of the handshake is encrypted with 1-RTT keys.
// Note that the client's identity hasn't been verified yet. // Note that the client's identity hasn't been verified yet.
HandshakeComplete() context.Context HandshakeComplete() context.Context
NextSession() Session NextConnection() Connection
} }
// Config contains all configuration data needed for a QUIC server or client. // 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. // MaxConnectionReceiveWindow is the connection-level flow control window for receiving data.
// If this value is zero, it will default to 15 MB. // If this value is zero, it will default to 15 MB.
MaxConnectionReceiveWindow uint64 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. // MaxIncomingStreams is the maximum number of concurrent bidirectional streams that a peer is allowed to open.
// Values above 2^60 are invalid. // Values above 2^60 are invalid.
// If not set, it will default to 100. // If not set, it will default to 100.
@ -279,11 +286,13 @@ type Config struct {
// The StatelessResetKey is used to generate stateless reset tokens. // The StatelessResetKey is used to generate stateless reset tokens.
// If no key is configured, sending of stateless resets is disabled. // If no key is configured, sending of stateless resets is disabled.
StatelessResetKey []byte StatelessResetKey []byte
// KeepAlive defines whether this peer will periodically send a packet to keep the connection alive. // KeepAlivePeriod defines whether this peer will periodically send a packet to keep the connection alive.
KeepAlive bool // 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). // DisablePathMTUDiscovery disables Path MTU Discovery (RFC 8899).
// Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size. // 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 DisablePathMTUDiscovery bool
// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets. // DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
// This can be useful if version information is exchanged out-of-band. // 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 // A Listener for incoming QUIC connections
type Listener interface { type Listener interface {
// Close the server. All active sessions will be closed. // Close the server. All active connections will be closed.
Close() error Close() error
// Addr returns the local network addr that the server is listening on. // Addr returns the local network addr that the server is listening on.
Addr() net.Addr Addr() net.Addr
// Accept returns new sessions. It should be called in a loop. // Accept returns new connections. It should be called in a loop.
Accept(context.Context) (Session, error) Accept(context.Context) (Connection, error)
} }
// An EarlyListener listens for incoming QUIC connections, // An EarlyListener listens for incoming QUIC connections,
// and returns them before the handshake completes. // and returns them before the handshake completes.
type EarlyListener interface { type EarlyListener interface {
// Close the server. All active sessions will be closed. // Close the server. All active connections will be closed.
Close() error Close() error
// Addr returns the local network addr that the server is listening on. // Addr returns the local network addr that the server is listening on.
Addr() net.Addr Addr() net.Addr
// Accept returns new early sessions. It should be called in a loop. // Accept returns new early connections. It should be called in a loop.
Accept(context.Context) (EarlySession, error) Accept(context.Context) (EarlyConnection, error)
} }

View File

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

View File

@ -19,11 +19,12 @@ type connectionFlowController struct {
var _ ConnectionFlowController = &connectionFlowController{} var _ ConnectionFlowController = &connectionFlowController{}
// NewConnectionFlowController gets a new flow controller for the connection // 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( func NewConnectionFlowController(
receiveWindow protocol.ByteCount, receiveWindow protocol.ByteCount,
maxReceiveWindow protocol.ByteCount, maxReceiveWindow protocol.ByteCount,
queueWindowUpdate func(), queueWindowUpdate func(),
allowWindowIncrease func(size protocol.ByteCount) bool,
rttStats *utils.RTTStats, rttStats *utils.RTTStats,
logger utils.Logger, logger utils.Logger,
) ConnectionFlowController { ) ConnectionFlowController {
@ -33,6 +34,7 @@ func NewConnectionFlowController(
receiveWindow: receiveWindow, receiveWindow: receiveWindow,
receiveWindowSize: receiveWindow, receiveWindowSize: receiveWindow,
maxReceiveWindowSize: maxReceiveWindow, maxReceiveWindowSize: maxReceiveWindow,
allowWindowIncrease: allowWindowIncrease,
logger: logger, logger: logger,
}, },
queueWindowUpdate: queueWindowUpdate, queueWindowUpdate: queueWindowUpdate,
@ -85,13 +87,16 @@ func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCoun
c.mutex.Lock() c.mutex.Lock()
if inc > c.receiveWindowSize { 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.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.startNewAutoTuningEpoch(time.Now())
} }
c.mutex.Unlock() 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. // 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. // 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 { func (c *connectionFlowController) Reset() error {

View File

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

View File

@ -3,4 +3,4 @@
package qtls 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 TimerTypePTO
) )
// TimeoutReason is the reason why a session is closed // TimeoutReason is the reason why a connection is closed
type TimeoutReason uint8 type TimeoutReason uint8
const ( 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. // This reason is not defined in the qlog draft, but very useful for debugging.
TimeoutReasonHandshake TimeoutReason = iota 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. // This reason is not defined in the qlog draft, but very useful for debugging.
TimeoutReasonIdle TimeoutReasonIdle
) )
@ -87,7 +87,7 @@ const (
CongestionStateSlowStart CongestionState = iota CongestionStateSlowStart CongestionState = iota
// CongestionStateCongestionAvoidance is the slow start phase of Reno / Cubic // CongestionStateCongestionAvoidance is the slow start phase of Reno / Cubic
CongestionStateCongestionAvoidance CongestionStateCongestionAvoidance
// CongestionStateCongestionAvoidance is the recovery phase of Reno / Cubic // CongestionStateRecovery is the recovery phase of Reno / Cubic
CongestionStateRecovery CongestionStateRecovery
// CongestionStateApplicationLimited means that the congestion controller is application limited // CongestionStateApplicationLimited means that the congestion controller is application limited
CongestionStateApplicationLimited 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_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_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_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_conn_runner_test.go github.com/lucas-clemente/quic-go connRunner"
//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_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_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_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" //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 { type mtuDiscoverer interface {
ShouldSendProbe(now time.Time) bool ShouldSendProbe(now time.Time) bool
NextProbeTime() time.Time
GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount) GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
} }
@ -53,16 +52,7 @@ func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
if f.probeInFlight || f.done() { if f.probeInFlight || f.done() {
return false return false
} }
return !now.Before(f.NextProbeTime()) return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
}
// 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())
} }
func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) { 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 // The connMultiplexer listens on multiple net.PacketConns and dispatches
// incoming packets to the session handler. // incoming packets to the connection handler.
type connMultiplexer struct { type connMultiplexer struct {
mutex sync.Mutex mutex sync.Mutex

View File

@ -7,8 +7,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"io"
"log" "log"
"net" "net"
"os"
"strconv"
"strings"
"sync" "sync"
"time" "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 { type packetHandlerMapEntry struct {
packetHandler packetHandler packetHandler packetHandler
is0RTTQueue bool is0RTTQueue bool
@ -52,12 +64,12 @@ type packetHandlerMapEntry struct {
// The packetHandlerMap stores packetHandlers, identified by connection ID. // The packetHandlerMap stores packetHandlers, identified by connection ID.
// It is used: // It is used:
// * by the server to store sessions // * by the server to store connections
// * when multiplexing outgoing connections to store clients // * when multiplexing outgoing connections to store clients
type packetHandlerMap struct { type packetHandlerMap struct {
mutex sync.Mutex mutex sync.Mutex
conn connection conn rawConn
connIDLen int connIDLen int
handlers map[string] /* string(ConnectionID)*/ packetHandlerMapEntry handlers map[string] /* string(ConnectionID)*/ packetHandlerMapEntry
@ -68,8 +80,8 @@ type packetHandlerMap struct {
listening chan struct{} // is closed when listen returns listening chan struct{} // is closed when listen returns
closed bool closed bool
deleteRetiredSessionsAfter time.Duration deleteRetiredConnsAfter time.Duration
zeroRTTQueueDuration time.Duration zeroRTTQueueDuration time.Duration
statelessResetEnabled bool statelessResetEnabled bool
statelessResetMutex sync.Mutex statelessResetMutex sync.Mutex
@ -110,7 +122,7 @@ func setReceiveBuffer(c net.PacketConn, logger utils.Logger) error {
return nil 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 var receiveBufferWarningOnce sync.Once
func newPacketHandlerMap( func newPacketHandlerMap(
@ -121,26 +133,31 @@ func newPacketHandlerMap(
logger utils.Logger, logger utils.Logger,
) (packetHandlerManager, error) { ) (packetHandlerManager, error) {
if err := setReceiveBuffer(c, logger); err != nil { if err := setReceiveBuffer(c, logger); err != nil {
receiveBufferWarningOnce.Do(func() { if !strings.Contains(err.Error(), "use of closed network connection") {
log.Printf("%s. See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details.", err) 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) conn, err := wrapConn(c)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m := &packetHandlerMap{ m := &packetHandlerMap{
conn: conn, conn: conn,
connIDLen: connIDLen, connIDLen: connIDLen,
listening: make(chan struct{}), listening: make(chan struct{}),
handlers: make(map[string]packetHandlerMapEntry), handlers: make(map[string]packetHandlerMapEntry),
resetTokens: make(map[protocol.StatelessResetToken]packetHandler), resetTokens: make(map[protocol.StatelessResetToken]packetHandler),
deleteRetiredSessionsAfter: protocol.RetiredConnectionIDDeleteTimeout, deleteRetiredConnsAfter: protocol.RetiredConnectionIDDeleteTimeout,
zeroRTTQueueDuration: protocol.Max0RTTQueueingDuration, zeroRTTQueueDuration: protocol.Max0RTTQueueingDuration,
statelessResetEnabled: len(statelessResetKey) > 0, statelessResetEnabled: len(statelessResetKey) > 0,
statelessResetHasher: hmac.New(sha256.New, statelessResetKey), statelessResetHasher: hmac.New(sha256.New, statelessResetKey),
tracer: tracer, tracer: tracer,
logger: logger, logger: logger,
} }
go m.listen() go m.listen()
@ -196,7 +213,7 @@ func (h *packetHandlerMap) AddWithConnID(clientDestConnID, newConnID protocol.Co
var q *zeroRTTQueue var q *zeroRTTQueue
if entry, ok := h.handlers[string(clientDestConnID)]; ok { if entry, ok := h.handlers[string(clientDestConnID)]; ok {
if !entry.is0RTTQueue { 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 return false
} }
q = entry.packetHandler.(*zeroRTTQueue) 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(clientDestConnID)] = packetHandlerMapEntry{packetHandler: sess}
h.handlers[string(newConnID)] = 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 return true
} }
@ -224,8 +241,8 @@ func (h *packetHandlerMap) Remove(id protocol.ConnectionID) {
} }
func (h *packetHandlerMap) Retire(id protocol.ConnectionID) { func (h *packetHandlerMap) Retire(id protocol.ConnectionID) {
h.logger.Debugf("Retiring connection ID %s in %s.", id, h.deleteRetiredSessionsAfter) h.logger.Debugf("Retiring connection ID %s in %s.", id, h.deleteRetiredConnsAfter)
time.AfterFunc(h.deleteRetiredSessionsAfter, func() { time.AfterFunc(h.deleteRetiredConnsAfter, func() {
h.mutex.Lock() h.mutex.Lock()
delete(h.handlers, string(id)) delete(h.handlers, string(id))
h.mutex.Unlock() h.mutex.Unlock()
@ -237,14 +254,14 @@ func (h *packetHandlerMap) ReplaceWithClosed(id protocol.ConnectionID, handler p
h.mutex.Lock() h.mutex.Lock()
h.handlers[string(id)] = packetHandlerMapEntry{packetHandler: handler} h.handlers[string(id)] = packetHandlerMapEntry{packetHandler: handler}
h.mutex.Unlock() 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() h.mutex.Lock()
handler.shutdown() handler.shutdown()
delete(h.handlers, string(id)) delete(h.handlers, string(id))
h.mutex.Unlock() 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. // 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 { func (h *packetHandlerMap) Destroy() error {
if err := h.conn.Close(); err != nil { if err := h.conn.Close(); err != nil {
return err return err
@ -327,6 +344,10 @@ func (h *packetHandlerMap) listen() {
defer close(h.listening) defer close(h.listening)
for { for {
p, err := h.conn.ReadPacket() 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() { if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
h.logger.Debugf("Temporary error reading from conn: %w", err) h.logger.Debugf("Temporary error reading from conn: %w", err)
continue continue
@ -363,7 +384,7 @@ func (h *packetHandlerMap) handlePacket(p *receivedPacket) {
entry.packetHandler.handlePacket(p) entry.packetHandler.handlePacket(p)
return return
} }
} else { // existing session } else { // existing connection
entry.packetHandler.handlePacket(p) entry.packetHandler.handlePacket(p)
return return
} }
@ -389,7 +410,7 @@ func (h *packetHandlerMap) handlePacket(p *receivedPacket) {
queue.retireTimer = time.AfterFunc(h.zeroRTTQueueDuration, func() { queue.retireTimer = time.AfterFunc(h.zeroRTTQueueDuration, func() {
h.mutex.Lock() h.mutex.Lock()
defer h.mutex.Unlock() 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. // Only delete it if it's still a 0-RTT queue.
if entry, ok := h.handlers[string(connID)]; ok && entry.is0RTTQueue { if entry, ok := h.handlers[string(connID)]; ok && entry.is0RTTQueue {
delete(h.handlers, string(connID)) delete(h.handlers, string(connID))
@ -421,7 +442,7 @@ func (h *packetHandlerMap) maybeHandleStatelessReset(data []byte) bool {
var token protocol.StatelessResetToken var token protocol.StatelessResetToken
copy(token[:], data[len(data)-16:]) copy(token[:], data[len(data)-16:])
if sess, ok := h.resetTokens[token]; ok { 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}) go sess.destroy(&StatelessResetError{Token: token})
return true return true
} }

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package quic package quic
import ( import (
"io"
"net" "net"
"syscall" "syscall"
"time" "time"
@ -10,14 +9,8 @@ import (
"github.com/lucas-clemente/quic-go/internal/utils" "github.com/lucas-clemente/quic-go/internal/utils"
) )
type connection interface { // OOBCapablePacketConn is a connection that allows the reading of ECN bits from the IP header.
ReadPacket() (*receivedPacket, error) // If the PacketConn passed to Dial or Listen satisfies this interface, quic-go will use it.
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.
// In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets. // In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets.
type OOBCapablePacketConn interface { type OOBCapablePacketConn interface {
net.PacketConn net.PacketConn
@ -28,7 +21,20 @@ type OOBCapablePacketConn interface {
var _ OOBCapablePacketConn = &net.UDPConn{} 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) c, ok := pc.(OOBCapablePacketConn)
if !ok { if !ok {
utils.DefaultLogger.Infof("PacketConn is not a net.UDPConn. Disabling optimizations possible on UDP connections.") 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) 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 { type basicConn struct {
net.PacketConn net.PacketConn
} }
var _ connection = &basicConn{} var _ rawConn = &basicConn{}
func (c *basicConn) ReadPacket() (*receivedPacket, error) { func (c *basicConn) ReadPacket() (*receivedPacket, error) {
buffer := getPacketBuffer() 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" import "golang.org/x/sys/unix"
const ( const msgTypeIPTOS = unix.IP_RECVTOS
msgTypeIPTOS = unix.IP_RECVTOS
disablePathMTUDiscovery = false
)
const ( const (
ipv4RECVPKTINFO = unix.IP_RECVPKTINFO ipv4RECVPKTINFO = unix.IP_RECVPKTINFO

View File

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

View File

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

View File

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

View File

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

View File

@ -12,24 +12,7 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
const ( func newConn(c OOBCapablePacketConn) (rawConn, error) {
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
}
return &basicConn{PacketConn: c}, nil return &basicConn{PacketConn: c}, nil
} }

View File

@ -20,13 +20,10 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"runtime"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"golang.org/x/sys/cpu"
) )
const ( const (
@ -1463,17 +1460,6 @@ func defaultCipherSuitesTLS13() []uint16 {
return varDefaultCipherSuitesTLS13 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() { func initDefaultCipherSuites() {
var topCipherSuites []uint16 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" "crypto/sha256"
"fmt" "fmt"
"hash" "hash"
"runtime"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/sys/cpu"
) )
// CipherSuite is a TLS cipher suite. Note that most functions in this package // 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, 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{ var aesgcmCiphers = map[uint16]bool{
// TLS 1.2 // TLS 1.2
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true, 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" "crypto/sha256"
"fmt" "fmt"
"hash" "hash"
"runtime"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/sys/cpu"
) )
// CipherSuite is a TLS cipher suite. Note that most functions in this package // 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, 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{ var aesgcmCiphers = map[uint16]bool{
// TLS 1.2 // TLS 1.2
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true, 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/json-iterator/go
# github.com/kylelemons/godebug v1.1.0 # github.com/kylelemons/godebug v1.1.0
## explicit; go 1.11 ## 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 ## explicit; go 1.16
github.com/lucas-clemente/quic-go github.com/lucas-clemente/quic-go
github.com/lucas-clemente/quic-go/internal/ackhandler 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 # github.com/lucasb-eyer/go-colorful v1.0.3
## explicit; go 1.12 ## explicit; go 1.12
github.com/lucasb-eyer/go-colorful 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 ## explicit; go 1.16
github.com/marten-seemann/qtls-go1-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 ## explicit; go 1.17
github.com/marten-seemann/qtls-go1-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 ## explicit; go 1.18
github.com/marten-seemann/qtls-go1-18 github.com/marten-seemann/qtls-go1-18
# github.com/mattn/go-colorable v0.1.8 # 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/server
zombiezen.com/go/capnproto2/std/capnp/rpc 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/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 # github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1