pax_global_header 0000666 0000000 0000000 00000000064 14171454516 0014522 g ustar 00root root 0000000 0000000 52 comment=496fbe9fda4bb094b1cbdb162a52130b6beda6b1
quic-go-0.25.0/ 0000775 0000000 0000000 00000000000 14171454516 0013152 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/.circleci/ 0000775 0000000 0000000 00000000000 14171454516 0015005 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/.circleci/config.yml 0000664 0000000 0000000 00000002755 14171454516 0017006 0 ustar 00root root 0000000 0000000 version: 2.1
executors:
test-go117:
docker:
- image: "cimg/go:1.17"
environment:
runrace: true
TIMESCALE_FACTOR: 3
test-go116:
docker:
- image: "cimg/go:1.16"
environment:
runrace: true
TIMESCALE_FACTOR: 3
jobs:
"test": &test
executor: test-go117
steps:
- checkout
- run:
name: "Setup build environment"
command: go install github.com/onsi/ginkgo/ginkgo
- run:
name: "Build infos"
command: go version
- run:
name: "Run benchmark tests"
command: ginkgo -randomizeAllSpecs -trace benchmark -- -size=10
- run:
name: "Run benchmark tests with race detector"
command: ginkgo -race -randomizeAllSpecs -trace benchmark -- -size=5
- run:
name: "Run tools tests"
command: ginkgo -race -r -v -randomizeAllSpecs -trace integrationtests/tools
- run:
name: "Run self integration tests"
command: ginkgo -v -randomizeAllSpecs -trace integrationtests/self
- run:
name: "Run self integration tests with race detector"
command: ginkgo -race -v -randomizeAllSpecs -trace integrationtests/self
- run:
name: "Run self integration tests with qlog"
command: ginkgo -v -randomizeAllSpecs -trace integrationtests/self -- -qlog
go117:
<<: *test
go116:
<<: *test
executor: test-go116
workflows:
workflow:
jobs:
- go116
- go117
quic-go-0.25.0/.githooks/ 0000775 0000000 0000000 00000000000 14171454516 0015057 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/.githooks/README.md 0000664 0000000 0000000 00000000231 14171454516 0016332 0 ustar 00root root 0000000 0000000 # Git Hooks
This directory contains useful Git hooks for working with quic-go.
Install them by running
```bash
git config core.hooksPath .githooks
```
quic-go-0.25.0/.githooks/pre-commit 0000775 0000000 0000000 00000001176 14171454516 0017066 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Check that test files don't contain focussed test cases.
errored=false
for f in $(git diff --cached --name-only); do
if [[ $f != *_test.go ]]; then continue; fi
output=$(git show :"$f" | grep -n -e "FIt(" -e "FContext(" -e "FDescribe(")
if [ $? -eq 0 ]; then
echo "$f contains a focussed test:"
echo "$output"
echo ""
errored=true
fi
done
# Check that all Go files are properly gofumpt-ed.
output=$(gofumpt -d $(git diff --cached --name-only -- '*.go'))
if [ -n "$output" ]; then
echo "Found files that are not properly gofumpt-ed."
echo "$output"
errored=true
fi
if [ "$errored" = true ]; then
exit 1
fi
quic-go-0.25.0/.github/ 0000775 0000000 0000000 00000000000 14171454516 0014512 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14171454516 0016547 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/.github/workflows/build-interop-docker.yml 0000664 0000000 0000000 00000000776 14171454516 0023326 0 ustar 00root root 0000000 0000000 name: Build interop Docker image
on:
push:
branches: [ interop ]
jobs:
interop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/build-push-action@v1
with:
always_pull: true
path: interop/
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: martenseemann/quic-go-interop
tags: latest
tag_with_ref: true
add_git_labels: true
quic-go-0.25.0/.github/workflows/cross-compile.sh 0000775 0000000 0000000 00000001506 14171454516 0021667 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
GOVERSION=$(go version | cut -d " " -f 3 | cut -b 3-6)
for dist in $(go tool dist list); do
goos=$(echo $dist | cut -d "/" -f1)
goarch=$(echo $dist | cut -d "/" -f2)
# cross-compiling for android is a pain...
if [[ "$goos" == "android" ]]; then continue; fi
# Go 1.14 lacks syscall.IPV6_RECVTCLASS
if [[ $GOVERSION == "1.14" && $goos == "darwin" && $goarch == "arm" ]]; then continue; fi
# darwin/arm64 requires Cgo for Go < 1.16
if [[ $GOVERSION != "1.16" && "$goos" == "darwin" && $goarch == "arm64" ]]; then continue; fi
# iOS builds require Cgo, see https://github.com/golang/go/issues/43343
# Cgo would then need a C cross compilation setup. Not worth the hassle.
if [[ "$goos" == "ios" ]]; then continue; fi
echo "$dist"
GOOS=$goos GOARCH=$goarch go build -o main example/main.go
rm main
done
quic-go-0.25.0/.github/workflows/cross-compile.yml 0000664 0000000 0000000 00000001316 14171454516 0022052 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
jobs:
crosscompile:
strategy:
fail-fast: false
matrix:
go: [ "1.16.x", "1.17.x" ]
runs-on: ubuntu-latest
name: "Cross Compilation (Go ${{matrix.go}})"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
stable: '!contains(${{ matrix.go }}, "beta") && !contains(${{ matrix.go }}, "rc")'
go-version: ${{ matrix.go }}
- name: Install build utils
run: |
sudo apt-get update
sudo apt-get install -y gcc-multilib
- name: Install dependencies
run: go build example/main.go
- name: Run cross compilation
run: .github/workflows/cross-compile.sh
quic-go-0.25.0/.github/workflows/go-generate.sh 0000775 0000000 0000000 00000001245 14171454516 0021305 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -e
DIR=$(pwd)
TMP=$(mktemp -d)
cd "$TMP"
cp -r "$DIR" orig
cp -r "$DIR" generated
cd generated
# delete all go-generated files generated (that adhere to the comment convention)
grep --include \*.go -lrIZ "^// Code generated .* DO NOT EDIT\.$" . | xargs --null rm
# delete all files generated by Genny
grep --include \*.go -lrIZ "This file was automatically generated by genny." . | xargs --null rm
# first generate Genny files to make the code compile
grep --include \*.go -lrI "//go:generate genny" | xargs -L 1 go generate
# now generate everything
go generate ./...
cd ..
# don't compare fuzzing corpora
diff --exclude=corpus -ruN orig generated
quic-go-0.25.0/.github/workflows/go-generate.yml 0000664 0000000 0000000 00000001046 14171454516 0021470 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
jobs:
gogenerate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "1.17.x"
- name: Install dependencies
run: go build
- name: Install code generators
run: |
go install -v github.com/cheekybits/genny
go install -v github.com/golang/mock/mockgen
go install -v golang.org/x/tools/cmd/goimports
- name: Run code generators
run: .github/workflows/go-generate.sh
quic-go-0.25.0/.github/workflows/integration.yml 0000664 0000000 0000000 00000003056 14171454516 0021621 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
jobs:
integration:
strategy:
fail-fast: false
matrix:
go: [ "1.16.x", "1.17.x", "1.18.0-beta1" ]
runs-on: ubuntu-latest
env:
DEBUG: false # set this to true to export qlogs and save them as artifacts
TIMESCALE_FACTOR: 3
name: Integration Tests (Go ${{ matrix.go }})
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
stable: '!contains(${{ matrix.go }}, "beta") && !contains(${{ matrix.go }}, "rc")'
go-version: ${{ matrix.go }}
- run: go version
- name: Install Ginkgo
run: go install github.com/onsi/ginkgo/ginkgo
- name: Install dependencies
run: go install
- name: set qlogger
if: env.DEBUG == 'true'
run: echo "QLOGFLAG=-- -qlog" >> $GITHUB_ENV
- name: Run tests
run: |
ginkgo -r -v -randomizeAllSpecs -randomizeSuites -trace -skipPackage self integrationtests
ginkgo -r -v -randomizeAllSpecs -randomizeSuites -trace integrationtests/self ${{ env.QLOGFLAG }}
- name: Run tests (32 bit)
env:
GOARCH: 386
run: |
ginkgo -r -v -randomizeAllSpecs -randomizeSuites -trace -skipPackage self integrationtests
ginkgo -r -v -randomizeAllSpecs -randomizeSuites -trace integrationtests/self ${{ env.QLOGFLAG }}
- name: save qlogs
if: ${{ always() && env.DEBUG == 'true' }}
uses: actions/upload-artifact@v2
with:
name: qlogs
path: integrationtests/self/*.qlog
quic-go-0.25.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000001515 14171454516 0020242 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "1.17.x"
- name: Check that no non-test files import Ginkgo or Gomega
run: .github/workflows/no_ginkgo.sh
- name: Check that go.mod is tidied
run: |
cp go.mod go.mod.orig
cp go.sum go.sum.orig
go mod tidy
diff go.mod go.mod.orig
diff go.sum go.sum.orig
- name: Check that go mod vendor works
run: |
cd integrationtests/gomodvendor
go mod vendor
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.41.1
quic-go-0.25.0/.github/workflows/no_ginkgo.sh 0000775 0000000 0000000 00000000723 14171454516 0021062 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# Verify that no non-test files import Ginkgo or Gomega.
set -e
HAS_TESTING=false
cd ..
for f in $(find . -name "*.go" ! -name "*_test.go" ! -name "tools.go"); do
if grep -q "github.com/onsi/ginkgo" $f; then
echo "$f imports github.com/onsi/ginkgo"
HAS_TESTING=true
fi
if grep -q "github.com/onsi/gomega" $f; then
echo "$f imports github.com/onsi/gomega"
HAS_TESTING=true
fi
done
if "$HAS_TESTING"; then
exit 1
fi
exit 0
quic-go-0.25.0/.github/workflows/unit.yml 0000664 0000000 0000000 00000003105 14171454516 0020250 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
jobs:
unit:
strategy:
fail-fast: false
matrix:
os: [ "ubuntu", "windows", "macos" ]
go: [ "1.16.x", "1.17.x", "1.18.0-beta1" ]
runs-on: ${{ matrix.os }}-latest
name: Unit tests (${{ matrix.os}}, Go ${{ matrix.go }})
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
stable: '!contains(${{ matrix.go }}, "beta") && !contains(${{ matrix.go }}, "rc")'
go-version: ${{ matrix.go }}
- run: go version
- name: Install Ginkgo
run: go install github.com/onsi/ginkgo/ginkgo
- name: Run tests
env:
TIMESCALE_FACTOR: 10
run: ginkgo -r -v -cover -randomizeAllSpecs -randomizeSuites -trace -skipPackage integrationtests,benchmark
- name: Run tests (32 bit)
if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.
env:
TIMESCALE_FACTOR: 10
GOARCH: 386
run: ginkgo -r -v -cover -coverprofile coverage.txt -outputdir . -randomizeAllSpecs -randomizeSuites -trace -skipPackage integrationtests,benchmark
- name: Run tests with race detector
if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow
env:
TIMESCALE_FACTOR: 20
run: ginkgo -r -v -race -randomizeAllSpecs -randomizeSuites -trace -skipPackage integrationtests,benchmark
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: coverage.txt
env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }}
quic-go-0.25.0/.gitignore 0000664 0000000 0000000 00000000322 14171454516 0015137 0 ustar 00root root 0000000 0000000 debug
debug.test
main
mockgen_tmp.go
*.qtr
*.qlog
*.txt
race.[0-9]*
fuzzing/*/*.zip
fuzzing/*/coverprofile
fuzzing/*/crashers
fuzzing/*/sonarprofile
fuzzing/*/suppressions
fuzzing/*/corpus/
gomock_reflect_*/
quic-go-0.25.0/.golangci.yml 0000664 0000000 0000000 00000001506 14171454516 0015540 0 ustar 00root root 0000000 0000000 run:
skip-files:
- internal/qtls/structs_equal_test.go
linters-settings:
depguard:
type: blacklist
packages:
- github.com/marten-seemann/qtls
packages-with-error-message:
- github.com/marten-seemann/qtls: "importing qtls only allowed in internal/qtls"
misspell:
ignore-words:
- ect
linters:
disable-all: true
enable:
- asciicheck
- deadcode
- depguard
- exhaustive
- exportloopref
- goimports
- gofmt # redundant, since gofmt *should* be a no-op after gofumpt
- gofumpt
- gosimple
- ineffassign
- misspell
- prealloc
- scopelint
- staticcheck
- stylecheck
- structcheck
- unconvert
- unparam
- unused
- varcheck
- vet
issues:
exclude-rules:
- path: internal/qtls
linters:
- depguard
quic-go-0.25.0/Changelog.md 0000664 0000000 0000000 00000010631 14171454516 0015364 0 ustar 00root root 0000000 0000000 # Changelog
## v0.22.0 (2021-07-25)
- Use `ReadBatch` to read multiple UDP packets from the socket with a single syscall
- Add a config option (`Config.DisableVersionNegotiationPackets`) to disable sending of Version Negotiation packets
- Drop support for QUIC draft versions 32 and 34
- Remove the `RetireBugBackwardsCompatibilityMode`, which was intended to mitigate a bug when retiring connection IDs in quic-go in v0.17.2 and ealier
## v0.21.2 (2021-07-15)
- Update qtls (for Go 1.15, 1.16 and 1.17rc1) to include the fix for the crypto/tls panic (see https://groups.google.com/g/golang-dev/c/5LJ2V7rd-Ag/m/YGLHVBZ6AAAJ for details)
## v0.21.0 (2021-06-01)
- quic-go now supports RFC 9000!
## v0.20.0 (2021-03-19)
- Remove the `quic.Config.HandshakeTimeout`. Introduce a `quic.Config.HandshakeIdleTimeout`.
## v0.17.1 (2020-06-20)
- Supports QUIC WG draft-29.
- Improve bundling of ACK frames (#2543).
## v0.16.0 (2020-05-31)
- Supports QUIC WG draft-28.
## v0.15.0 (2020-03-01)
- Supports QUIC WG draft-27.
- Add support for 0-RTT.
- Remove `Session.Close()`. Applications need to pass an application error code to the transport using `Session.CloseWithError()`.
- Make the TLS Cipher Suites configurable (via `tls.Config.CipherSuites`).
## v0.14.0 (2019-12-04)
- Supports QUIC WG draft-24.
## v0.13.0 (2019-11-05)
- Supports QUIC WG draft-23.
- Add an `EarlyListener` that allows sending of 0.5-RTT data.
- Add a `TokenStore` to store address validation tokens.
- Issue and use new connection IDs during a connection.
## v0.12.0 (2019-08-05)
- Implement HTTP/3.
- Rename `quic.Cookie` to `quic.Token` and `quic.Config.AcceptCookie` to `quic.Config.AcceptToken`.
- Distinguish between Retry tokens and tokens sent in NEW_TOKEN frames.
- Enforce application protocol negotiation (via `tls.Config.NextProtos`).
- Use a varint for error codes.
- Add support for [quic-trace](https://github.com/google/quic-trace).
- Add a context to `Listener.Accept`, `Session.Accept{Uni}Stream` and `Session.Open{Uni}StreamSync`.
- Implement TLS key updates.
## v0.11.0 (2019-04-05)
- Drop support for gQUIC. For qQUIC support, please switch to the *gquic* branch.
- Implement QUIC WG draft-19.
- Use [qtls](https://github.com/marten-seemann/qtls) for TLS 1.3.
- Return a `tls.ConnectionState` from `quic.Session.ConnectionState()`.
- Remove the error return values from `quic.Stream.CancelRead()` and `quic.Stream.CancelWrite()`
## v0.10.0 (2018-08-28)
- Add support for QUIC 44, drop support for QUIC 42.
## v0.9.0 (2018-08-15)
- Add a `quic.Config` option for the length of the connection ID (for IETF QUIC).
- Split Session.Close into one method for regular closing and one for closing with an error.
## v0.8.0 (2018-06-26)
- Add support for unidirectional streams (for IETF QUIC).
- Add a `quic.Config` option for the maximum number of incoming streams.
- Add support for QUIC 42 and 43.
- Add dial functions that use a context.
- Multiplex clients on a net.PacketConn, when using Dial(conn).
## v0.7.0 (2018-02-03)
- The lower boundary for packets included in ACKs is now derived, and the value sent in STOP_WAITING frames is ignored.
- Remove `DialNonFWSecure` and `DialAddrNonFWSecure`.
- Expose the `ConnectionState` in the `Session` (experimental API).
- Implement packet pacing.
## v0.6.0 (2017-12-12)
- Add support for QUIC 39, drop support for QUIC 35 - 37
- Added `quic.Config` options for maximal flow control windows
- Add a `quic.Config` option for QUIC versions
- Add a `quic.Config` option to request omission of the connection ID from a server
- Add a `quic.Config` option to configure the source address validation
- Add a `quic.Config` option to configure the handshake timeout
- Add a `quic.Config` option to configure the idle timeout
- Add a `quic.Config` option to configure keep-alive
- Rename the STK to Cookie
- Implement `net.Conn`-style deadlines for streams
- Remove the `tls.Config` from the `quic.Config`. The `tls.Config` must now be passed to the `Dial` and `Listen` functions as a separate parameter. See the [Godoc](https://godoc.org/github.com/lucas-clemente/quic-go) for details.
- Changed the log level environment variable to only accept strings ("DEBUG", "INFO", "ERROR"), see [the wiki](https://github.com/lucas-clemente/quic-go/wiki/Logging) for more details.
- Rename the `h2quic.QuicRoundTripper` to `h2quic.RoundTripper`
- Changed `h2quic.Server.Serve()` to accept a `net.PacketConn`
- Drop support for Go 1.7 and 1.8.
- Various bugfixes
quic-go-0.25.0/LICENSE 0000664 0000000 0000000 00000002103 14171454516 0014153 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2016 the quic-go authors & Google, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
quic-go-0.25.0/README.md 0000664 0000000 0000000 00000011306 14171454516 0014432 0 ustar 00root root 0000000 0000000 # A QUIC implementation in pure Go
[](https://pkg.go.dev/github.com/lucas-clemente/quic-go)
[](https://travis-ci.org/lucas-clemente/quic-go)
[](https://circleci.com/gh/lucas-clemente/quic-go)
[](https://ci.appveyor.com/project/lucas-clemente/quic-go/branch/master)
[](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.
In addition to RFC 9000, it currently implements the [IETF QUIC draft-29](https://tools.ietf.org/html/draft-ietf-quic-transport-29). Support for draft-29 will eventually be dropped, as it is phased out of the ecosystem.
## Guides
*We currently support Go 1.16.x and Go 1.17.x.*
Running tests:
go test ./...
### QUIC without HTTP/3
Take a look at [this echo example](example/echo/echo.go).
## Usage
### As a server
See the [example server](example/main.go). Starting a QUIC server is very similar to the standard lib http in go:
```go
http.Handle("/", http.FileServer(http.Dir(wwwDir)))
http3.ListenAndServeQUIC("localhost:4242", "/path/to/cert/chain.pem", "/path/to/privkey.pem", nil)
```
### As a client
See the [example client](example/client/main.go). Use a `http3.RoundTripper` as a `Transport` in a `http.Client`.
```go
http.Client{
Transport: &http3.RoundTripper{},
}
```
## Projects using quic-go
| Project | Description | Stars |
|------------------------------------------------------|--------------------------------------------------------------------------------------------------------|-------|
| [algernon](https://github.com/xyproto/algernon) | Small self-contained pure-Go web server with Lua, Markdown, HTTP/2, QUIC, Redis and PostgreSQL support |  |
| [caddy](https://github.com/caddyserver/caddy/) | Fast, multi-platform web server with automatic HTTPS |  |
| [go-ipfs](https://github.com/ipfs/go-ipfs) | IPFS implementation in go |  |
| [nextdns](https://github.com/nextdns/nextdns) | NextDNS CLI client (DoH Proxy) |  |
| [syncthing](https://github.com/syncthing/syncthing/) | Open Source Continuous File Synchronization |  |
| [traefik](https://github.com/traefik/traefik) | The Cloud Native Application Proxy |  |
| [v2ray-core](https://github.com/v2fly/v2ray-core) | A platform for building proxies to bypass network restrictions |  |
| [cloudflared](https://github.com/cloudflare/cloudflared) | A tunneling daemon that proxies traffic from the Cloudflare network to your origins |  |
## Contributing
We are always happy to welcome new contributors! We have a number of self-contained issues that are suitable for first-time contributors, they are tagged with [help wanted](https://github.com/lucas-clemente/quic-go/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). If you have any questions, please feel free to reach out by opening an issue or leaving a comment.
quic-go-0.25.0/benchmark/ 0000775 0000000 0000000 00000000000 14171454516 0015104 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/benchmark/benchmark_suite_test.go 0000664 0000000 0000000 00000000651 14171454516 0021637 0 ustar 00root root 0000000 0000000 package benchmark
import (
"flag"
"math/rand"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestBenchmark(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Benchmark Suite")
}
var size int // file size in MB, will be read from flags
func init() {
flag.IntVar(&size, "size", 50, "data length (in MB)")
}
var _ = BeforeSuite(func() {
rand.Seed(GinkgoRandomSeed())
flag.Parse()
})
quic-go-0.25.0/benchmark/benchmark_test.go 0000664 0000000 0000000 00000005213 14171454516 0020425 0 ustar 00root root 0000000 0000000 package benchmark
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"math/rand"
"net"
quic "github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/testdata"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Benchmarks", func() {
for i := range protocol.SupportedVersions {
version := protocol.SupportedVersions[i]
Context(fmt.Sprintf("with version %s", version), func() {
var data []byte
var dataLen int
BeforeEach(func() {
dataLen = size * /* MB */ 1e6
data = make([]byte, dataLen)
rand.Read(data) // no need to check for an error. math.Rand.Read never errors
})
Measure("transferring a file", func(b Benchmarker) {
var ln quic.Listener
serverAddr := make(chan net.Addr)
handshakeChan := make(chan struct{})
// start the server
go func() {
defer GinkgoRecover()
var err error
tlsConf := testdata.GetTLSConfig()
tlsConf.NextProtos = []string{"benchmark"}
ln, err = quic.ListenAddr(
"localhost:0",
tlsConf,
&quic.Config{Versions: []protocol.VersionNumber{version}},
)
Expect(err).ToNot(HaveOccurred())
serverAddr <- ln.Addr()
sess, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
// wait for the client to complete the handshake before sending the data
// this should not be necessary, but due to timing issues on the CIs, this is necessary to avoid sending too many undecryptable packets
<-handshakeChan
str, err := sess.OpenStream()
Expect(err).ToNot(HaveOccurred())
_, err = str.Write(data)
Expect(err).ToNot(HaveOccurred())
err = str.Close()
Expect(err).ToNot(HaveOccurred())
}()
// start the client
addr := <-serverAddr
sess, err := quic.DialAddr(
addr.String(),
&tls.Config{InsecureSkipVerify: true, NextProtos: []string{"benchmark"}},
&quic.Config{Versions: []protocol.VersionNumber{version}},
)
Expect(err).ToNot(HaveOccurred())
close(handshakeChan)
str, err := sess.AcceptStream(context.Background())
Expect(err).ToNot(HaveOccurred())
buf := &bytes.Buffer{}
// measure the time it takes to download the dataLen bytes
// note we're measuring the time for the transfer, i.e. excluding the handshake
runtime := b.Time("transfer time", func() {
_, err := io.Copy(buf, str)
Expect(err).NotTo(HaveOccurred())
})
Expect(buf.Bytes()).To(Equal(data))
b.RecordValue("transfer rate [MB/s]", float64(dataLen)/1e6/runtime.Seconds())
ln.Close()
sess.CloseWithError(0, "")
}, 3)
})
}
})
quic-go-0.25.0/buffer_pool.go 0000664 0000000 0000000 00000003424 14171454516 0016006 0 ustar 00root root 0000000 0000000 package quic
import (
"sync"
"github.com/lucas-clemente/quic-go/internal/protocol"
)
type packetBuffer struct {
Data []byte
// refCount counts how many packets Data is used in.
// It doesn't support concurrent use.
// It is > 1 when used for coalesced packet.
refCount int
}
// Split increases the refCount.
// It must be called when a packet buffer is used for more than one packet,
// e.g. when splitting coalesced packets.
func (b *packetBuffer) Split() {
b.refCount++
}
// Decrement decrements the reference counter.
// It doesn't put the buffer back into the pool.
func (b *packetBuffer) Decrement() {
b.refCount--
if b.refCount < 0 {
panic("negative packetBuffer refCount")
}
}
// MaybeRelease puts the packet buffer back into the pool,
// if the reference counter already reached 0.
func (b *packetBuffer) MaybeRelease() {
// only put the packetBuffer back if it's not used any more
if b.refCount == 0 {
b.putBack()
}
}
// Release puts back the packet buffer into the pool.
// It should be called when processing is definitely finished.
func (b *packetBuffer) Release() {
b.Decrement()
if b.refCount != 0 {
panic("packetBuffer refCount not zero")
}
b.putBack()
}
// Len returns the length of Data
func (b *packetBuffer) Len() protocol.ByteCount {
return protocol.ByteCount(len(b.Data))
}
func (b *packetBuffer) putBack() {
if cap(b.Data) != int(protocol.MaxPacketBufferSize) {
panic("putPacketBuffer called with packet of wrong size!")
}
bufferPool.Put(b)
}
var bufferPool sync.Pool
func getPacketBuffer() *packetBuffer {
buf := bufferPool.Get().(*packetBuffer)
buf.refCount = 1
buf.Data = buf.Data[:0]
return buf
}
func init() {
bufferPool.New = func() interface{} {
return &packetBuffer{
Data: make([]byte, 0, protocol.MaxPacketBufferSize),
}
}
}
quic-go-0.25.0/buffer_pool_test.go 0000664 0000000 0000000 00000002363 14171454516 0017046 0 ustar 00root root 0000000 0000000 package quic
import (
"github.com/lucas-clemente/quic-go/internal/protocol"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Buffer Pool", func() {
It("returns buffers of cap", func() {
buf := getPacketBuffer()
Expect(buf.Data).To(HaveCap(int(protocol.MaxPacketBufferSize)))
})
It("releases buffers", func() {
buf := getPacketBuffer()
buf.Release()
})
It("gets the length", func() {
buf := getPacketBuffer()
buf.Data = append(buf.Data, []byte("foobar")...)
Expect(buf.Len()).To(BeEquivalentTo(6))
})
It("panics if wrong-sized buffers are passed", func() {
buf := getPacketBuffer()
buf.Data = make([]byte, 10)
Expect(func() { buf.Release() }).To(Panic())
})
It("panics if it is released twice", func() {
buf := getPacketBuffer()
buf.Release()
Expect(func() { buf.Release() }).To(Panic())
})
It("panics if it is decremented too many times", func() {
buf := getPacketBuffer()
buf.Decrement()
Expect(func() { buf.Decrement() }).To(Panic())
})
It("waits until all parts have been released", func() {
buf := getPacketBuffer()
buf.Split()
buf.Split()
// now we have 3 parts
buf.Decrement()
buf.Decrement()
buf.Decrement()
Expect(func() { buf.Decrement() }).To(Panic())
})
})
quic-go-0.25.0/client.go 0000664 0000000 0000000 00000022273 14171454516 0014765 0 ustar 00root root 0000000 0000000 package quic
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strings"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/logging"
)
type client struct {
conn sendConn
// If the client is created with DialAddr, we create a packet conn.
// If it is started with Dial, we take a packet conn as a parameter.
createdPacketConn bool
use0RTT bool
packetHandlers packetHandlerManager
tlsConf *tls.Config
config *Config
srcConnID protocol.ConnectionID
destConnID protocol.ConnectionID
initialPacketNumber protocol.PacketNumber
hasNegotiatedVersion bool
version protocol.VersionNumber
handshakeChan chan struct{}
session quicSession
tracer logging.ConnectionTracer
tracingID uint64
logger utils.Logger
}
var (
// make it possible to mock connection ID generation in the tests
generateConnectionID = protocol.GenerateConnectionID
generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
)
// DialAddr establishes a new QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
// The hostname for SNI is taken from the given address.
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
func DialAddr(
addr string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return DialAddrContext(context.Background(), addr, tlsConf, config)
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
// The hostname for SNI is taken from the given address.
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
func DialAddrEarly(
addr string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
return DialAddrEarlyContext(context.Background(), addr, tlsConf, config)
}
// DialAddrEarlyContext establishes a new 0-RTT QUIC connection to a server using provided context.
// See DialAddrEarly for details
func DialAddrEarlyContext(
ctx context.Context,
addr string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
sess, err := dialAddrContext(ctx, addr, tlsConf, config, true)
if err != nil {
return nil, err
}
utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early session")
return sess, nil
}
// DialAddrContext establishes a new QUIC connection to a server using the provided context.
// See DialAddr for details.
func DialAddrContext(
ctx context.Context,
addr string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return dialAddrContext(ctx, addr, tlsConf, config, false)
}
func dialAddrContext(
ctx context.Context,
addr string,
tlsConf *tls.Config,
config *Config,
use0RTT bool,
) (quicSession, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return nil, err
}
return dialContext(ctx, udpConn, udpAddr, addr, tlsConf, config, use0RTT, true)
}
// Dial establishes a new QUIC connection to a server using a net.PacketConn. If
// the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn
// does), ECN and packet info support will be enabled. In this case, ReadMsgUDP
// and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write
// packets. The same PacketConn can be used for multiple calls to Dial and
// Listen, QUIC connection IDs are used for demultiplexing the different
// connections. The host parameter is used for SNI. The tls.Config must define
// an application protocol (using NextProtos).
func Dial(
pconn net.PacketConn,
remoteAddr net.Addr,
host string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return dialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config, false, false)
}
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
// The same PacketConn can be used for multiple calls to Dial and Listen,
// QUIC connection IDs are used for demultiplexing the different connections.
// The host parameter is used for SNI.
// The tls.Config must define an application protocol (using NextProtos).
func DialEarly(
pconn net.PacketConn,
remoteAddr net.Addr,
host string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
return DialEarlyContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
}
// DialEarlyContext establishes a new 0-RTT QUIC connection to a server using a net.PacketConn using the provided context.
// See DialEarly for details.
func DialEarlyContext(
ctx context.Context,
pconn net.PacketConn,
remoteAddr net.Addr,
host string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, true, false)
}
// DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
// See Dial for details.
func DialContext(
ctx context.Context,
pconn net.PacketConn,
remoteAddr net.Addr,
host string,
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false, false)
}
func dialContext(
ctx context.Context,
pconn net.PacketConn,
remoteAddr net.Addr,
host string,
tlsConf *tls.Config,
config *Config,
use0RTT bool,
createdPacketConn bool,
) (quicSession, error) {
if tlsConf == nil {
return nil, errors.New("quic: tls.Config not set")
}
if err := validateConfig(config); err != nil {
return nil, err
}
config = populateClientConfig(config, createdPacketConn)
packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
if err != nil {
return nil, err
}
c, err := newClient(pconn, remoteAddr, config, tlsConf, host, use0RTT, createdPacketConn)
if err != nil {
return nil, err
}
c.packetHandlers = packetHandlers
c.tracingID = nextSessionTracingID()
if c.config.Tracer != nil {
c.tracer = c.config.Tracer.TracerForConnection(
context.WithValue(ctx, SessionTracingKey, c.tracingID),
protocol.PerspectiveClient,
c.destConnID,
)
}
if c.tracer != nil {
c.tracer.StartedConnection(c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID)
}
if err := c.dial(ctx); err != nil {
return nil, err
}
return c.session, nil
}
func newClient(
pconn net.PacketConn,
remoteAddr net.Addr,
config *Config,
tlsConf *tls.Config,
host string,
use0RTT bool,
createdPacketConn bool,
) (*client, error) {
if tlsConf == nil {
tlsConf = &tls.Config{}
}
if tlsConf.ServerName == "" {
sni := host
if strings.IndexByte(sni, ':') != -1 {
var err error
sni, _, err = net.SplitHostPort(sni)
if err != nil {
return nil, err
}
}
tlsConf.ServerName = sni
}
// check that all versions are actually supported
if config != nil {
for _, v := range config.Versions {
if !protocol.IsValidVersion(v) {
return nil, fmt.Errorf("%s is not a valid QUIC version", v)
}
}
}
srcConnID, err := generateConnectionID(config.ConnectionIDLength)
if err != nil {
return nil, err
}
destConnID, err := generateConnectionIDForInitial()
if err != nil {
return nil, err
}
c := &client{
srcConnID: srcConnID,
destConnID: destConnID,
conn: newSendPconn(pconn, remoteAddr),
createdPacketConn: createdPacketConn,
use0RTT: use0RTT,
tlsConf: tlsConf,
config: config,
version: config.Versions[0],
handshakeChan: make(chan struct{}),
logger: utils.DefaultLogger.WithPrefix("client"),
}
return c, nil
}
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.session = newClientSession(
c.conn,
c.packetHandlers,
c.destConnID,
c.srcConnID,
c.config,
c.tlsConf,
c.initialPacketNumber,
c.use0RTT,
c.hasNegotiatedVersion,
c.tracer,
c.tracingID,
c.logger,
c.version,
)
c.packetHandlers.Add(c.srcConnID, c.session)
errorChan := make(chan error, 1)
go func() {
err := c.session.run() // returns as soon as the session is closed
if e := (&errCloseForRecreating{}); !errors.As(err, &e) && c.createdPacketConn {
c.packetHandlers.Destroy()
}
errorChan <- err
}()
// only set when we're using 0-RTT
// Otherwise, earlySessionChan will be nil. Receiving from a nil chan blocks forever.
var earlySessionChan <-chan struct{}
if c.use0RTT {
earlySessionChan = c.session.earlySessionReady()
}
select {
case <-ctx.Done():
c.session.shutdown()
return ctx.Err()
case err := <-errorChan:
var recreateErr *errCloseForRecreating
if errors.As(err, &recreateErr) {
c.initialPacketNumber = recreateErr.nextPacketNumber
c.version = recreateErr.nextVersion
c.hasNegotiatedVersion = true
return c.dial(ctx)
}
return err
case <-earlySessionChan:
// ready to send 0-RTT data
return nil
case <-c.session.HandshakeComplete().Done():
// handshake successfully completed
return nil
}
}
quic-go-0.25.0/client_test.go 0000664 0000000 0000000 00000044320 14171454516 0016021 0 ustar 00root root 0000000 0000000 package quic
import (
"context"
"crypto/tls"
"errors"
"net"
"os"
"time"
mocklogging "github.com/lucas-clemente/quic-go/internal/mocks/logging"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/logging"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Client", func() {
var (
cl *client
packetConn *MockPacketConn
addr net.Addr
connID protocol.ConnectionID
mockMultiplexer *MockMultiplexer
origMultiplexer multiplexer
tlsConf *tls.Config
tracer *mocklogging.MockConnectionTracer
config *Config
originalClientSessConstructor func(
conn sendConn,
runner sessionRunner,
destConnID protocol.ConnectionID,
srcConnID protocol.ConnectionID,
conf *Config,
tlsConf *tls.Config,
initialPacketNumber protocol.PacketNumber,
enable0RTT bool,
hasNegotiatedVersion bool,
tracer logging.ConnectionTracer,
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
) quicSession
)
BeforeEach(func() {
tlsConf = &tls.Config{NextProtos: []string{"proto1"}}
connID = protocol.ConnectionID{0, 0, 0, 0, 0, 0, 0x13, 0x37}
originalClientSessConstructor = newClientSession
tracer = mocklogging.NewMockConnectionTracer(mockCtrl)
tr := mocklogging.NewMockTracer(mockCtrl)
tr.EXPECT().TracerForConnection(gomock.Any(), protocol.PerspectiveClient, gomock.Any()).Return(tracer).MaxTimes(1)
config = &Config{Tracer: tr, Versions: []protocol.VersionNumber{protocol.VersionTLS}}
Eventually(areSessionsRunning).Should(BeFalse())
// sess = NewMockQuicSession(mockCtrl)
addr = &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
packetConn = NewMockPacketConn(mockCtrl)
packetConn.EXPECT().LocalAddr().Return(&net.UDPAddr{}).AnyTimes()
cl = &client{
srcConnID: connID,
destConnID: connID,
version: protocol.VersionTLS,
conn: newSendPconn(packetConn, addr),
tracer: tracer,
logger: utils.DefaultLogger,
}
getMultiplexer() // make the sync.Once execute
// replace the clientMuxer. getClientMultiplexer will now return the MockMultiplexer
mockMultiplexer = NewMockMultiplexer(mockCtrl)
origMultiplexer = connMuxer
connMuxer = mockMultiplexer
})
AfterEach(func() {
connMuxer = origMultiplexer
newClientSession = originalClientSessConstructor
})
AfterEach(func() {
if s, ok := cl.session.(*session); ok {
s.shutdown()
}
Eventually(areSessionsRunning).Should(BeFalse())
})
Context("Dialing", func() {
var origGenerateConnectionID func(int) (protocol.ConnectionID, error)
var origGenerateConnectionIDForInitial func() (protocol.ConnectionID, error)
BeforeEach(func() {
origGenerateConnectionID = generateConnectionID
origGenerateConnectionIDForInitial = generateConnectionIDForInitial
generateConnectionID = func(int) (protocol.ConnectionID, error) {
return connID, nil
}
generateConnectionIDForInitial = func() (protocol.ConnectionID, error) {
return connID, nil
}
})
AfterEach(func() {
generateConnectionID = origGenerateConnectionID
generateConnectionIDForInitial = origGenerateConnectionIDForInitial
})
It("resolves the address", func() {
if os.Getenv("APPVEYOR") == "True" {
Skip("This test is flaky on AppVeyor.")
}
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
manager.EXPECT().Destroy()
mockMultiplexer.EXPECT().AddConn(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
remoteAddrChan := make(chan string, 1)
newClientSession = func(
conn sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
remoteAddrChan <- conn.RemoteAddr().String()
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run()
sess.EXPECT().HandshakeComplete().Return(context.Background())
return sess
}
_, err := DialAddr("localhost:17890", tlsConf, &Config{HandshakeIdleTimeout: time.Millisecond})
Expect(err).ToNot(HaveOccurred())
Eventually(remoteAddrChan).Should(Receive(Equal("127.0.0.1:17890")))
})
It("uses the tls.Config.ServerName as the hostname, if present", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
manager.EXPECT().Destroy()
mockMultiplexer.EXPECT().AddConn(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
hostnameChan := make(chan string, 1)
newClientSession = func(
_ sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
tlsConf *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
hostnameChan <- tlsConf.ServerName
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run()
sess.EXPECT().HandshakeComplete().Return(context.Background())
return sess
}
tlsConf.ServerName = "foobar"
_, err := DialAddr("localhost:17890", tlsConf, nil)
Expect(err).ToNot(HaveOccurred())
Eventually(hostnameChan).Should(Receive(Equal("foobar")))
})
It("allows passing host without port as server name", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
hostnameChan := make(chan string, 1)
newClientSession = func(
_ sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
tlsConf *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
hostnameChan <- tlsConf.ServerName
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().HandshakeComplete().Return(context.Background())
sess.EXPECT().run()
return sess
}
tracer.EXPECT().StartedConnection(packetConn.LocalAddr(), addr, gomock.Any(), gomock.Any())
_, err := Dial(
packetConn,
addr,
"test.com",
tlsConf,
config,
)
Expect(err).ToNot(HaveOccurred())
Eventually(hostnameChan).Should(Receive(Equal("test.com")))
})
It("returns after the handshake is complete", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
run := make(chan struct{})
newClientSession = func(
_ sendConn,
runner sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
enable0RTT bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
Expect(enable0RTT).To(BeFalse())
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run().Do(func() { close(run) })
ctx, cancel := context.WithCancel(context.Background())
cancel()
sess.EXPECT().HandshakeComplete().Return(ctx)
return sess
}
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
s, err := Dial(
packetConn,
addr,
"localhost:1337",
tlsConf,
config,
)
Expect(err).ToNot(HaveOccurred())
Expect(s).ToNot(BeNil())
Eventually(run).Should(BeClosed())
})
It("returns early sessions", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
readyChan := make(chan struct{})
done := make(chan struct{})
newClientSession = func(
_ sendConn,
runner sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
enable0RTT bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
Expect(enable0RTT).To(BeTrue())
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run().Do(func() { <-done })
sess.EXPECT().HandshakeComplete().Return(context.Background())
sess.EXPECT().earlySessionReady().Return(readyChan)
return sess
}
go func() {
defer GinkgoRecover()
defer close(done)
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
s, err := DialEarly(
packetConn,
addr,
"localhost:1337",
tlsConf,
config,
)
Expect(err).ToNot(HaveOccurred())
Expect(s).ToNot(BeNil())
}()
Consistently(done).ShouldNot(BeClosed())
close(readyChan)
Eventually(done).Should(BeClosed())
})
It("returns an error that occurs while waiting for the handshake to complete", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
testErr := errors.New("early handshake error")
newClientSession = func(
_ sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run().Return(testErr)
sess.EXPECT().HandshakeComplete().Return(context.Background())
return sess
}
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
_, err := Dial(
packetConn,
addr,
"localhost:1337",
tlsConf,
config,
)
Expect(err).To(MatchError(testErr))
})
It("closes the session when the context is canceled", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
sessionRunning := make(chan struct{})
defer close(sessionRunning)
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run().Do(func() {
<-sessionRunning
})
sess.EXPECT().HandshakeComplete().Return(context.Background())
newClientSession = func(
_ sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
return sess
}
ctx, cancel := context.WithCancel(context.Background())
dialed := make(chan struct{})
go func() {
defer GinkgoRecover()
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
_, err := DialContext(
ctx,
packetConn,
addr,
"localhost:1337",
tlsConf,
config,
)
Expect(err).To(MatchError(context.Canceled))
close(dialed)
}()
Consistently(dialed).ShouldNot(BeClosed())
sess.EXPECT().shutdown()
cancel()
Eventually(dialed).Should(BeClosed())
})
It("closes the connection when it was created by DialAddr", func() {
if os.Getenv("APPVEYOR") == "True" {
Skip("This test is flaky on AppVeyor.")
}
manager := NewMockPacketHandlerManager(mockCtrl)
mockMultiplexer.EXPECT().AddConn(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
var conn sendConn
run := make(chan struct{})
sessionCreated := make(chan struct{})
sess := NewMockQuicSession(mockCtrl)
newClientSession = func(
connP sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
conn = connP
close(sessionCreated)
return sess
}
sess.EXPECT().run().Do(func() {
<-run
})
sess.EXPECT().HandshakeComplete().Return(context.Background())
done := make(chan struct{})
go func() {
defer GinkgoRecover()
_, err := DialAddr("localhost:1337", tlsConf, nil)
Expect(err).ToNot(HaveOccurred())
close(done)
}()
Eventually(sessionCreated).Should(BeClosed())
// check that the connection is not closed
Expect(conn.Write([]byte("foobar"))).To(Succeed())
manager.EXPECT().Destroy()
close(run)
time.Sleep(50 * time.Millisecond)
Eventually(done).Should(BeClosed())
})
Context("quic.Config", func() {
It("setups with the right values", func() {
tokenStore := NewLRUTokenStore(10, 4)
config := &Config{
HandshakeIdleTimeout: 1337 * time.Minute,
MaxIdleTimeout: 42 * time.Hour,
MaxIncomingStreams: 1234,
MaxIncomingUniStreams: 4321,
ConnectionIDLength: 13,
StatelessResetKey: []byte("foobar"),
TokenStore: tokenStore,
EnableDatagrams: true,
}
c := populateClientConfig(config, false)
Expect(c.HandshakeIdleTimeout).To(Equal(1337 * time.Minute))
Expect(c.MaxIdleTimeout).To(Equal(42 * time.Hour))
Expect(c.MaxIncomingStreams).To(BeEquivalentTo(1234))
Expect(c.MaxIncomingUniStreams).To(BeEquivalentTo(4321))
Expect(c.ConnectionIDLength).To(Equal(13))
Expect(c.StatelessResetKey).To(Equal([]byte("foobar")))
Expect(c.TokenStore).To(Equal(tokenStore))
Expect(c.EnableDatagrams).To(BeTrue())
})
It("errors when the Config contains an invalid version", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
version := protocol.VersionNumber(0x1234)
_, err := Dial(packetConn, nil, "localhost:1234", tlsConf, &Config{Versions: []protocol.VersionNumber{version}})
Expect(err).To(MatchError("0x1234 is not a valid QUIC version"))
})
It("disables bidirectional streams", func() {
config := &Config{
MaxIncomingStreams: -1,
MaxIncomingUniStreams: 4321,
}
c := populateClientConfig(config, false)
Expect(c.MaxIncomingStreams).To(BeZero())
Expect(c.MaxIncomingUniStreams).To(BeEquivalentTo(4321))
})
It("disables unidirectional streams", func() {
config := &Config{
MaxIncomingStreams: 1234,
MaxIncomingUniStreams: -1,
}
c := populateClientConfig(config, false)
Expect(c.MaxIncomingStreams).To(BeEquivalentTo(1234))
Expect(c.MaxIncomingUniStreams).To(BeZero())
})
It("uses 0-byte connection IDs when dialing an address", func() {
c := populateClientConfig(&Config{}, true)
Expect(c.ConnectionIDLength).To(BeZero())
})
It("fills in default values if options are not set in the Config", func() {
c := populateClientConfig(&Config{}, false)
Expect(c.Versions).To(Equal(protocol.SupportedVersions))
Expect(c.HandshakeIdleTimeout).To(Equal(protocol.DefaultHandshakeIdleTimeout))
Expect(c.MaxIdleTimeout).To(Equal(protocol.DefaultIdleTimeout))
})
})
It("creates new sessions with the right parameters", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(connID, gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
config := &Config{Versions: []protocol.VersionNumber{protocol.VersionTLS}}
c := make(chan struct{})
var cconn sendConn
var version protocol.VersionNumber
var conf *Config
newClientSession = func(
connP sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
configP *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
versionP protocol.VersionNumber,
) quicSession {
cconn = connP
version = versionP
conf = configP
close(c)
// TODO: check connection IDs?
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run()
sess.EXPECT().HandshakeComplete().Return(context.Background())
return sess
}
_, err := Dial(packetConn, addr, "localhost:1337", tlsConf, config)
Expect(err).ToNot(HaveOccurred())
Eventually(c).Should(BeClosed())
Expect(cconn.(*spconn).PacketConn).To(Equal(packetConn))
Expect(version).To(Equal(config.Versions[0]))
Expect(conf.Versions).To(Equal(config.Versions))
})
It("creates a new session after version negotiation", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(connID, gomock.Any()).Times(2)
manager.EXPECT().Destroy()
mockMultiplexer.EXPECT().AddConn(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manager, nil)
var counter int
newClientSession = func(
_ sendConn,
_ sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
configP *Config,
_ *tls.Config,
pn protocol.PacketNumber,
_ bool,
hasNegotiatedVersion bool,
_ logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
versionP protocol.VersionNumber,
) quicSession {
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().HandshakeComplete().Return(context.Background())
if counter == 0 {
Expect(pn).To(BeZero())
Expect(hasNegotiatedVersion).To(BeFalse())
sess.EXPECT().run().Return(&errCloseForRecreating{
nextPacketNumber: 109,
nextVersion: 789,
})
} else {
Expect(pn).To(Equal(protocol.PacketNumber(109)))
Expect(hasNegotiatedVersion).To(BeTrue())
sess.EXPECT().run()
}
counter++
return sess
}
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
_, err := DialAddr("localhost:7890", tlsConf, config)
Expect(err).ToNot(HaveOccurred())
Expect(counter).To(Equal(2))
})
})
})
quic-go-0.25.0/closed_session.go 0000664 0000000 0000000 00000005651 14171454516 0016524 0 ustar 00root root 0000000 0000000 package quic
import (
"sync"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
)
// A closedLocalSession is a session that we closed locally.
// When receiving packets for such a session, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
// with an exponential backoff.
type closedLocalSession struct {
conn sendConn
connClosePacket []byte
closeOnce sync.Once
closeChan chan struct{} // is closed when the session is closed or destroyed
receivedPackets chan *receivedPacket
counter uint64 // number of packets received
perspective protocol.Perspective
logger utils.Logger
}
var _ packetHandler = &closedLocalSession{}
// newClosedLocalSession creates a new closedLocalSession and runs it.
func newClosedLocalSession(
conn sendConn,
connClosePacket []byte,
perspective protocol.Perspective,
logger utils.Logger,
) packetHandler {
s := &closedLocalSession{
conn: conn,
connClosePacket: connClosePacket,
perspective: perspective,
logger: logger,
closeChan: make(chan struct{}),
receivedPackets: make(chan *receivedPacket, 64),
}
go s.run()
return s
}
func (s *closedLocalSession) run() {
for {
select {
case p := <-s.receivedPackets:
s.handlePacketImpl(p)
case <-s.closeChan:
return
}
}
}
func (s *closedLocalSession) handlePacket(p *receivedPacket) {
select {
case s.receivedPackets <- p:
default:
}
}
func (s *closedLocalSession) handlePacketImpl(_ *receivedPacket) {
s.counter++
// exponential backoff
// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
for n := s.counter; n > 1; n = n / 2 {
if n%2 != 0 {
return
}
}
s.logger.Debugf("Received %d packets after sending CONNECTION_CLOSE. Retransmitting.", s.counter)
if err := s.conn.Write(s.connClosePacket); err != nil {
s.logger.Debugf("Error retransmitting CONNECTION_CLOSE: %s", err)
}
}
func (s *closedLocalSession) shutdown() {
s.destroy(nil)
}
func (s *closedLocalSession) destroy(error) {
s.closeOnce.Do(func() {
close(s.closeChan)
})
}
func (s *closedLocalSession) getPerspective() protocol.Perspective {
return s.perspective
}
// A closedRemoteSession is a session that was closed remotely.
// For such a session, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
// We can just ignore those packets.
type closedRemoteSession struct {
perspective protocol.Perspective
}
var _ packetHandler = &closedRemoteSession{}
func newClosedRemoteSession(pers protocol.Perspective) packetHandler {
return &closedRemoteSession{perspective: pers}
}
func (s *closedRemoteSession) handlePacket(*receivedPacket) {}
func (s *closedRemoteSession) shutdown() {}
func (s *closedRemoteSession) destroy(error) {}
func (s *closedRemoteSession) getPerspective() protocol.Perspective { return s.perspective }
quic-go-0.25.0/closed_session_test.go 0000664 0000000 0000000 00000002711 14171454516 0017555 0 ustar 00root root 0000000 0000000 package quic
import (
"errors"
"time"
"github.com/golang/mock/gomock"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Closed local session", func() {
var (
sess packetHandler
mconn *MockSendConn
)
BeforeEach(func() {
mconn = NewMockSendConn(mockCtrl)
sess = newClosedLocalSession(mconn, []byte("close"), protocol.PerspectiveClient, utils.DefaultLogger)
})
AfterEach(func() {
Eventually(areClosedSessionsRunning).Should(BeFalse())
})
It("tells its perspective", func() {
Expect(sess.getPerspective()).To(Equal(protocol.PerspectiveClient))
// stop the session
sess.shutdown()
})
It("repeats the packet containing the CONNECTION_CLOSE frame", func() {
written := make(chan []byte)
mconn.EXPECT().Write(gomock.Any()).Do(func(p []byte) { written <- p }).AnyTimes()
for i := 1; i <= 20; i++ {
sess.handlePacket(&receivedPacket{})
if i == 1 || i == 2 || i == 4 || i == 8 || i == 16 {
Eventually(written).Should(Receive(Equal([]byte("close")))) // receive the CONNECTION_CLOSE
} else {
Consistently(written, 10*time.Millisecond).Should(HaveLen(0))
}
}
// stop the session
sess.shutdown()
})
It("destroys sessions", func() {
Eventually(areClosedSessionsRunning).Should(BeTrue())
sess.destroy(errors.New("destroy"))
Eventually(areClosedSessionsRunning).Should(BeFalse())
})
})
quic-go-0.25.0/codecov.yml 0000664 0000000 0000000 00000001067 14171454516 0015323 0 ustar 00root root 0000000 0000000 coverage:
round: nearest
ignore:
- streams_map_incoming_bidi.go
- streams_map_incoming_uni.go
- streams_map_outgoing_bidi.go
- streams_map_outgoing_uni.go
- http3/gzip_reader.go
- interop/
- internal/ackhandler/packet_linkedlist.go
- internal/utils/byteinterval_linkedlist.go
- internal/utils/newconnectionid_linkedlist.go
- internal/utils/packetinterval_linkedlist.go
- internal/utils/linkedlist/linkedlist.go
- fuzzing/
- metrics/
status:
project:
default:
threshold: 0.5
patch: false
quic-go-0.25.0/config.go 0000664 0000000 0000000 00000010267 14171454516 0014754 0 ustar 00root root 0000000 0000000 package quic
import (
"errors"
"time"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/protocol"
)
// Clone clones a Config
func (c *Config) Clone() *Config {
copy := *c
return ©
}
func (c *Config) handshakeTimeout() time.Duration {
return utils.MaxDuration(protocol.DefaultHandshakeTimeout, 2*c.HandshakeIdleTimeout)
}
func validateConfig(config *Config) error {
if config == nil {
return nil
}
if config.MaxIncomingStreams > 1<<60 {
return errors.New("invalid value for Config.MaxIncomingStreams")
}
if config.MaxIncomingUniStreams > 1<<60 {
return errors.New("invalid value for Config.MaxIncomingUniStreams")
}
return nil
}
// populateServerConfig populates fields in the quic.Config with their default values, if none are set
// it may be called with nil
func populateServerConfig(config *Config) *Config {
config = populateConfig(config)
if config.ConnectionIDLength == 0 {
config.ConnectionIDLength = protocol.DefaultConnectionIDLength
}
if config.AcceptToken == nil {
config.AcceptToken = defaultAcceptToken
}
return config
}
// populateClientConfig populates fields in the quic.Config with their default values, if none are set
// it may be called with nil
func populateClientConfig(config *Config, createdPacketConn bool) *Config {
config = populateConfig(config)
if config.ConnectionIDLength == 0 && !createdPacketConn {
config.ConnectionIDLength = protocol.DefaultConnectionIDLength
}
return config
}
func populateConfig(config *Config) *Config {
if config == nil {
config = &Config{}
}
versions := config.Versions
if len(versions) == 0 {
versions = protocol.SupportedVersions
}
handshakeIdleTimeout := protocol.DefaultHandshakeIdleTimeout
if config.HandshakeIdleTimeout != 0 {
handshakeIdleTimeout = config.HandshakeIdleTimeout
}
idleTimeout := protocol.DefaultIdleTimeout
if config.MaxIdleTimeout != 0 {
idleTimeout = config.MaxIdleTimeout
}
initialStreamReceiveWindow := config.InitialStreamReceiveWindow
if initialStreamReceiveWindow == 0 {
initialStreamReceiveWindow = protocol.DefaultInitialMaxStreamData
}
maxStreamReceiveWindow := config.MaxStreamReceiveWindow
if maxStreamReceiveWindow == 0 {
maxStreamReceiveWindow = protocol.DefaultMaxReceiveStreamFlowControlWindow
}
initialConnectionReceiveWindow := config.InitialConnectionReceiveWindow
if initialConnectionReceiveWindow == 0 {
initialConnectionReceiveWindow = protocol.DefaultInitialMaxData
}
maxConnectionReceiveWindow := config.MaxConnectionReceiveWindow
if maxConnectionReceiveWindow == 0 {
maxConnectionReceiveWindow = protocol.DefaultMaxReceiveConnectionFlowControlWindow
}
maxIncomingStreams := config.MaxIncomingStreams
if maxIncomingStreams == 0 {
maxIncomingStreams = protocol.DefaultMaxIncomingStreams
} else if maxIncomingStreams < 0 {
maxIncomingStreams = 0
}
maxIncomingUniStreams := config.MaxIncomingUniStreams
if maxIncomingUniStreams == 0 {
maxIncomingUniStreams = protocol.DefaultMaxIncomingUniStreams
} else if maxIncomingUniStreams < 0 {
maxIncomingUniStreams = 0
}
return &Config{
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
AcceptToken: config.AcceptToken,
KeepAlive: config.KeepAlive,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
ConnectionIDLength: config.ConnectionIDLength,
StatelessResetKey: config.StatelessResetKey,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
Tracer: config.Tracer,
}
}
quic-go-0.25.0/config_test.go 0000664 0000000 0000000 00000014344 14171454516 0016013 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"net"
"reflect"
"time"
mocklogging "github.com/lucas-clemente/quic-go/internal/mocks/logging"
"github.com/lucas-clemente/quic-go/internal/protocol"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Config", func() {
Context("validating", func() {
It("validates a nil config", func() {
Expect(validateConfig(nil)).To(Succeed())
})
It("validates a config with normal values", func() {
Expect(validateConfig(populateServerConfig(&Config{}))).To(Succeed())
})
It("errors on too large values for MaxIncomingStreams", func() {
Expect(validateConfig(&Config{MaxIncomingStreams: 1<<60 + 1})).To(MatchError("invalid value for Config.MaxIncomingStreams"))
})
It("errors on too large values for MaxIncomingUniStreams", func() {
Expect(validateConfig(&Config{MaxIncomingUniStreams: 1<<60 + 1})).To(MatchError("invalid value for Config.MaxIncomingUniStreams"))
})
})
configWithNonZeroNonFunctionFields := func() *Config {
c := &Config{}
v := reflect.ValueOf(c).Elem()
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
f := v.Field(i)
if !f.CanSet() {
// unexported field; not cloned.
continue
}
switch fn := typ.Field(i).Name; fn {
case "AcceptToken", "GetLogWriter", "AllowConnectionWindowIncrease":
// Can't compare functions.
case "Versions":
f.Set(reflect.ValueOf([]VersionNumber{1, 2, 3}))
case "ConnectionIDLength":
f.Set(reflect.ValueOf(8))
case "HandshakeIdleTimeout":
f.Set(reflect.ValueOf(time.Second))
case "MaxIdleTimeout":
f.Set(reflect.ValueOf(time.Hour))
case "TokenStore":
f.Set(reflect.ValueOf(NewLRUTokenStore(2, 3)))
case "InitialStreamReceiveWindow":
f.Set(reflect.ValueOf(uint64(1234)))
case "MaxStreamReceiveWindow":
f.Set(reflect.ValueOf(uint64(9)))
case "InitialConnectionReceiveWindow":
f.Set(reflect.ValueOf(uint64(4321)))
case "MaxConnectionReceiveWindow":
f.Set(reflect.ValueOf(uint64(10)))
case "MaxIncomingStreams":
f.Set(reflect.ValueOf(int64(11)))
case "MaxIncomingUniStreams":
f.Set(reflect.ValueOf(int64(12)))
case "StatelessResetKey":
f.Set(reflect.ValueOf([]byte{1, 2, 3, 4}))
case "KeepAlive":
f.Set(reflect.ValueOf(true))
case "EnableDatagrams":
f.Set(reflect.ValueOf(true))
case "DisableVersionNegotiationPackets":
f.Set(reflect.ValueOf(true))
case "DisablePathMTUDiscovery":
f.Set(reflect.ValueOf(true))
case "Tracer":
f.Set(reflect.ValueOf(mocklogging.NewMockTracer(mockCtrl)))
default:
Fail(fmt.Sprintf("all fields must be accounted for, but saw unknown field %q", fn))
}
}
return c
}
It("uses 10s handshake timeout for short handshake idle timeouts", func() {
c := &Config{HandshakeIdleTimeout: time.Second}
Expect(c.handshakeTimeout()).To(Equal(protocol.DefaultHandshakeTimeout))
})
It("uses twice the handshake idle timeouts for the handshake timeout, for long handshake idle timeouts", func() {
c := &Config{HandshakeIdleTimeout: time.Second * 11 / 2}
Expect(c.handshakeTimeout()).To(Equal(11 * time.Second))
})
Context("cloning", func() {
It("clones function fields", func() {
var calledAcceptToken, calledAllowConnectionWindowIncrease bool
c1 := &Config{
AcceptToken: func(_ net.Addr, _ *Token) bool { calledAcceptToken = true; return true },
AllowConnectionWindowIncrease: func(Session, uint64) bool { calledAllowConnectionWindowIncrease = true; return true },
}
c2 := c1.Clone()
c2.AcceptToken(&net.UDPAddr{}, &Token{})
Expect(calledAcceptToken).To(BeTrue())
c2.AllowConnectionWindowIncrease(nil, 1234)
Expect(calledAllowConnectionWindowIncrease).To(BeTrue())
})
It("clones non-function fields", func() {
c := configWithNonZeroNonFunctionFields()
Expect(c.Clone()).To(Equal(c))
})
It("returns a copy", func() {
c1 := &Config{
MaxIncomingStreams: 100,
AcceptToken: func(_ net.Addr, _ *Token) bool { return true },
}
c2 := c1.Clone()
c2.MaxIncomingStreams = 200
c2.AcceptToken = func(_ net.Addr, _ *Token) bool { return false }
Expect(c1.MaxIncomingStreams).To(BeEquivalentTo(100))
Expect(c1.AcceptToken(&net.UDPAddr{}, nil)).To(BeTrue())
})
})
Context("populating", func() {
It("populates function fields", func() {
var calledAcceptToken bool
c1 := &Config{
AcceptToken: func(_ net.Addr, _ *Token) bool { calledAcceptToken = true; return true },
}
c2 := populateConfig(c1)
c2.AcceptToken(&net.UDPAddr{}, &Token{})
Expect(calledAcceptToken).To(BeTrue())
})
It("copies non-function fields", func() {
c := configWithNonZeroNonFunctionFields()
Expect(populateConfig(c)).To(Equal(c))
})
It("populates empty fields with default values", func() {
c := populateConfig(&Config{})
Expect(c.Versions).To(Equal(protocol.SupportedVersions))
Expect(c.HandshakeIdleTimeout).To(Equal(protocol.DefaultHandshakeIdleTimeout))
Expect(c.InitialStreamReceiveWindow).To(BeEquivalentTo(protocol.DefaultInitialMaxStreamData))
Expect(c.MaxStreamReceiveWindow).To(BeEquivalentTo(protocol.DefaultMaxReceiveStreamFlowControlWindow))
Expect(c.InitialConnectionReceiveWindow).To(BeEquivalentTo(protocol.DefaultInitialMaxData))
Expect(c.MaxConnectionReceiveWindow).To(BeEquivalentTo(protocol.DefaultMaxReceiveConnectionFlowControlWindow))
Expect(c.MaxIncomingStreams).To(BeEquivalentTo(protocol.DefaultMaxIncomingStreams))
Expect(c.MaxIncomingUniStreams).To(BeEquivalentTo(protocol.DefaultMaxIncomingUniStreams))
Expect(c.DisableVersionNegotiationPackets).To(BeFalse())
Expect(c.DisablePathMTUDiscovery).To(BeFalse())
})
It("populates empty fields with default values, for the server", func() {
c := populateServerConfig(&Config{})
Expect(c.ConnectionIDLength).To(Equal(protocol.DefaultConnectionIDLength))
Expect(c.AcceptToken).ToNot(BeNil())
})
It("sets a default connection ID length if we didn't create the conn, for the client", func() {
c := populateClientConfig(&Config{}, false)
Expect(c.ConnectionIDLength).To(Equal(protocol.DefaultConnectionIDLength))
})
It("doesn't set a default connection ID length if we created the conn, for the client", func() {
c := populateClientConfig(&Config{}, true)
Expect(c.ConnectionIDLength).To(BeZero())
})
})
})
quic-go-0.25.0/conn.go 0000664 0000000 0000000 00000003470 14171454516 0014442 0 ustar 00root root 0000000 0000000 package quic
import (
"io"
"net"
"syscall"
"time"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
)
type connection interface {
ReadPacket() (*receivedPacket, error)
WritePacket(b []byte, addr net.Addr, oob []byte) (int, error)
LocalAddr() net.Addr
io.Closer
}
// If the PacketConn passed to Dial or Listen satisfies this interface, quic-go will read the ECN bits from the IP header.
// In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets.
type OOBCapablePacketConn interface {
net.PacketConn
SyscallConn() (syscall.RawConn, error)
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}
var _ OOBCapablePacketConn = &net.UDPConn{}
func wrapConn(pc net.PacketConn) (connection, error) {
c, ok := pc.(OOBCapablePacketConn)
if !ok {
utils.DefaultLogger.Infof("PacketConn is not a net.UDPConn. Disabling optimizations possible on UDP connections.")
return &basicConn{PacketConn: pc}, nil
}
return newConn(c)
}
type basicConn struct {
net.PacketConn
}
var _ connection = &basicConn{}
func (c *basicConn) ReadPacket() (*receivedPacket, error) {
buffer := getPacketBuffer()
// The packet size should not exceed protocol.MaxPacketBufferSize bytes
// If it does, we only read a truncated packet, which will then end up undecryptable
buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize]
n, addr, err := c.PacketConn.ReadFrom(buffer.Data)
if err != nil {
return nil, err
}
return &receivedPacket{
remoteAddr: addr,
rcvTime: time.Now(),
data: buffer.Data[:n],
buffer: buffer,
}, nil
}
func (c *basicConn) WritePacket(b []byte, addr net.Addr, _ []byte) (n int, err error) {
return c.PacketConn.WriteTo(b, addr)
}
quic-go-0.25.0/conn_generic.go 0000664 0000000 0000000 00000000571 14171454516 0016135 0 ustar 00root root 0000000 0000000 //go:build !darwin && !linux && !freebsd && !windows
// +build !darwin,!linux,!freebsd,!windows
package quic
import "net"
const disablePathMTUDiscovery = false
func newConn(c net.PacketConn) (connection, error) {
return &basicConn{PacketConn: c}, nil
}
func inspectReadBuffer(interface{}) (int, error) {
return 0, nil
}
func (i *packetInfo) OOB() []byte { return nil }
quic-go-0.25.0/conn_helper_darwin.go 0000664 0000000 0000000 00000000712 14171454516 0017341 0 ustar 00root root 0000000 0000000 //go:build darwin
// +build darwin
package quic
import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_RECVTOS
disablePathMTUDiscovery = false
)
const (
ipv4RECVPKTINFO = unix.IP_RECVPKTINFO
ipv6RECVPKTINFO = 0x3d
)
const (
msgTypeIPv4PKTINFO = unix.IP_PKTINFO
msgTypeIPv6PKTINFO = 0x2e
)
// ReadBatch only returns a single packet on OSX,
// see https://godoc.org/golang.org/x/net/ipv4#PacketConn.ReadBatch.
const batchSize = 1
quic-go-0.25.0/conn_helper_freebsd.go 0000664 0000000 0000000 00000000471 14171454516 0017471 0 ustar 00root root 0000000 0000000 //go:build freebsd
// +build freebsd
package quic
import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_RECVTOS
disablePathMTUDiscovery = false
)
const (
ipv4RECVPKTINFO = 0x7
ipv6RECVPKTINFO = 0x24
)
const (
msgTypeIPv4PKTINFO = 0x7
msgTypeIPv6PKTINFO = 0x2e
)
const batchSize = 8
quic-go-0.25.0/conn_helper_linux.go 0000664 0000000 0000000 00000000703 14171454516 0017214 0 ustar 00root root 0000000 0000000 //go:build linux
// +build linux
package quic
import "golang.org/x/sys/unix"
const (
msgTypeIPTOS = unix.IP_TOS
disablePathMTUDiscovery = false
)
const (
ipv4RECVPKTINFO = unix.IP_PKTINFO
ipv6RECVPKTINFO = unix.IPV6_RECVPKTINFO
)
const (
msgTypeIPv4PKTINFO = unix.IP_PKTINFO
msgTypeIPv6PKTINFO = unix.IPV6_PKTINFO
)
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
quic-go-0.25.0/conn_id_generator.go 0000664 0000000 0000000 00000010644 14171454516 0017165 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/wire"
)
type connIDGenerator struct {
connIDLen int
highestSeq uint64
activeSrcConnIDs map[uint64]protocol.ConnectionID
initialClientDestConnID protocol.ConnectionID
addConnectionID func(protocol.ConnectionID)
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken
removeConnectionID func(protocol.ConnectionID)
retireConnectionID func(protocol.ConnectionID)
replaceWithClosed func(protocol.ConnectionID, packetHandler)
queueControlFrame func(wire.Frame)
version protocol.VersionNumber
}
func newConnIDGenerator(
initialConnectionID protocol.ConnectionID,
initialClientDestConnID protocol.ConnectionID, // nil for the client
addConnectionID func(protocol.ConnectionID),
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken,
removeConnectionID func(protocol.ConnectionID),
retireConnectionID func(protocol.ConnectionID),
replaceWithClosed func(protocol.ConnectionID, packetHandler),
queueControlFrame func(wire.Frame),
version protocol.VersionNumber,
) *connIDGenerator {
m := &connIDGenerator{
connIDLen: initialConnectionID.Len(),
activeSrcConnIDs: make(map[uint64]protocol.ConnectionID),
addConnectionID: addConnectionID,
getStatelessResetToken: getStatelessResetToken,
removeConnectionID: removeConnectionID,
retireConnectionID: retireConnectionID,
replaceWithClosed: replaceWithClosed,
queueControlFrame: queueControlFrame,
version: version,
}
m.activeSrcConnIDs[0] = initialConnectionID
m.initialClientDestConnID = initialClientDestConnID
return m
}
func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
if m.connIDLen == 0 {
return nil
}
// The active_connection_id_limit transport parameter is the number of
// connection IDs the peer will store. This limit includes the connection ID
// used during the handshake, and the one sent in the preferred_address
// transport parameter.
// We currently don't send the preferred_address transport parameter,
// so we can issue (limit - 1) connection IDs.
for i := uint64(len(m.activeSrcConnIDs)); i < utils.MinUint64(limit, protocol.MaxIssuedConnectionIDs); i++ {
if err := m.issueNewConnID(); err != nil {
return err
}
}
return nil
}
func (m *connIDGenerator) Retire(seq uint64, sentWithDestConnID protocol.ConnectionID) error {
if seq > m.highestSeq {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("retired connection ID %d (highest issued: %d)", seq, m.highestSeq),
}
}
connID, ok := m.activeSrcConnIDs[seq]
// We might already have deleted this connection ID, if this is a duplicate frame.
if !ok {
return nil
}
if connID.Equal(sentWithDestConnID) {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("retired connection ID %d (%s), which was used as the Destination Connection ID on this packet", seq, connID),
}
}
m.retireConnectionID(connID)
delete(m.activeSrcConnIDs, seq)
// Don't issue a replacement for the initial connection ID.
if seq == 0 {
return nil
}
return m.issueNewConnID()
}
func (m *connIDGenerator) issueNewConnID() error {
connID, err := protocol.GenerateConnectionID(m.connIDLen)
if err != nil {
return err
}
m.activeSrcConnIDs[m.highestSeq+1] = connID
m.addConnectionID(connID)
m.queueControlFrame(&wire.NewConnectionIDFrame{
SequenceNumber: m.highestSeq + 1,
ConnectionID: connID,
StatelessResetToken: m.getStatelessResetToken(connID),
})
m.highestSeq++
return nil
}
func (m *connIDGenerator) SetHandshakeComplete() {
if m.initialClientDestConnID != nil {
m.retireConnectionID(m.initialClientDestConnID)
m.initialClientDestConnID = nil
}
}
func (m *connIDGenerator) RemoveAll() {
if m.initialClientDestConnID != nil {
m.removeConnectionID(m.initialClientDestConnID)
}
for _, connID := range m.activeSrcConnIDs {
m.removeConnectionID(connID)
}
}
func (m *connIDGenerator) ReplaceWithClosed(handler packetHandler) {
if m.initialClientDestConnID != nil {
m.replaceWithClosed(m.initialClientDestConnID, handler)
}
for _, connID := range m.activeSrcConnIDs {
m.replaceWithClosed(connID, handler)
}
}
quic-go-0.25.0/conn_id_generator_test.go 0000664 0000000 0000000 00000016451 14171454516 0020226 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/wire"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Connection ID Generator", func() {
var (
addedConnIDs []protocol.ConnectionID
retiredConnIDs []protocol.ConnectionID
removedConnIDs []protocol.ConnectionID
replacedWithClosed map[string]packetHandler
queuedFrames []wire.Frame
g *connIDGenerator
)
initialConnID := protocol.ConnectionID{1, 2, 3, 4, 5, 6, 7}
initialClientDestConnID := protocol.ConnectionID{0xa, 0xb, 0xc, 0xd, 0xe}
connIDToToken := func(c protocol.ConnectionID) protocol.StatelessResetToken {
return protocol.StatelessResetToken{c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0], c[0]}
}
BeforeEach(func() {
addedConnIDs = nil
retiredConnIDs = nil
removedConnIDs = nil
queuedFrames = nil
replacedWithClosed = make(map[string]packetHandler)
g = newConnIDGenerator(
initialConnID,
initialClientDestConnID,
func(c protocol.ConnectionID) { addedConnIDs = append(addedConnIDs, c) },
connIDToToken,
func(c protocol.ConnectionID) { removedConnIDs = append(removedConnIDs, c) },
func(c protocol.ConnectionID) { retiredConnIDs = append(retiredConnIDs, c) },
func(c protocol.ConnectionID, h packetHandler) { replacedWithClosed[string(c)] = h },
func(f wire.Frame) { queuedFrames = append(queuedFrames, f) },
protocol.VersionDraft29,
)
})
It("issues new connection IDs", func() {
Expect(g.SetMaxActiveConnIDs(4)).To(Succeed())
Expect(retiredConnIDs).To(BeEmpty())
Expect(addedConnIDs).To(HaveLen(3))
for i := 0; i < len(addedConnIDs)-1; i++ {
Expect(addedConnIDs[i]).ToNot(Equal(addedConnIDs[i+1]))
}
Expect(queuedFrames).To(HaveLen(3))
for i := 0; i < 3; i++ {
f := queuedFrames[i]
Expect(f).To(BeAssignableToTypeOf(&wire.NewConnectionIDFrame{}))
nf := f.(*wire.NewConnectionIDFrame)
Expect(nf.SequenceNumber).To(BeEquivalentTo(i + 1))
Expect(nf.ConnectionID.Len()).To(Equal(7))
Expect(nf.StatelessResetToken).To(Equal(connIDToToken(nf.ConnectionID)))
}
})
It("limits the number of connection IDs that it issues", func() {
Expect(g.SetMaxActiveConnIDs(9999999)).To(Succeed())
Expect(retiredConnIDs).To(BeEmpty())
Expect(addedConnIDs).To(HaveLen(protocol.MaxIssuedConnectionIDs - 1))
Expect(queuedFrames).To(HaveLen(protocol.MaxIssuedConnectionIDs - 1))
})
// SetMaxActiveConnIDs is called twice when we dialing a 0-RTT connection:
// once for the restored from the old connections, once when we receive the transport parameters
Context("dealing with 0-RTT", func() {
It("doesn't issue new connection IDs when SetMaxActiveConnIDs is called with the same value", func() {
Expect(g.SetMaxActiveConnIDs(4)).To(Succeed())
Expect(queuedFrames).To(HaveLen(3))
queuedFrames = nil
Expect(g.SetMaxActiveConnIDs(4)).To(Succeed())
Expect(queuedFrames).To(BeEmpty())
})
It("issues more connection IDs if the server allows a higher limit on the resumed connection", func() {
Expect(g.SetMaxActiveConnIDs(3)).To(Succeed())
Expect(queuedFrames).To(HaveLen(2))
queuedFrames = nil
Expect(g.SetMaxActiveConnIDs(6)).To(Succeed())
Expect(queuedFrames).To(HaveLen(3))
})
It("issues more connection IDs if the server allows a higher limit on the resumed connection, when connection IDs were retired in between", func() {
Expect(g.SetMaxActiveConnIDs(3)).To(Succeed())
Expect(queuedFrames).To(HaveLen(2))
queuedFrames = nil
g.Retire(1, protocol.ConnectionID{})
Expect(queuedFrames).To(HaveLen(1))
queuedFrames = nil
Expect(g.SetMaxActiveConnIDs(6)).To(Succeed())
Expect(queuedFrames).To(HaveLen(3))
})
})
It("errors if the peers tries to retire a connection ID that wasn't yet issued", func() {
Expect(g.Retire(1, protocol.ConnectionID{})).To(MatchError(&qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "retired connection ID 1 (highest issued: 0)",
}))
})
It("errors if the peers tries to retire a connection ID in a packet with that connection ID", func() {
Expect(g.SetMaxActiveConnIDs(4)).To(Succeed())
Expect(queuedFrames).ToNot(BeEmpty())
Expect(queuedFrames[0]).To(BeAssignableToTypeOf(&wire.NewConnectionIDFrame{}))
f := queuedFrames[0].(*wire.NewConnectionIDFrame)
Expect(g.Retire(f.SequenceNumber, f.ConnectionID)).To(MatchError(&qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("retired connection ID %d (%s), which was used as the Destination Connection ID on this packet", f.SequenceNumber, f.ConnectionID),
}))
})
It("issues new connection IDs, when old ones are retired", func() {
Expect(g.SetMaxActiveConnIDs(5)).To(Succeed())
queuedFrames = nil
Expect(retiredConnIDs).To(BeEmpty())
Expect(g.Retire(3, protocol.ConnectionID{})).To(Succeed())
Expect(queuedFrames).To(HaveLen(1))
Expect(queuedFrames[0]).To(BeAssignableToTypeOf(&wire.NewConnectionIDFrame{}))
nf := queuedFrames[0].(*wire.NewConnectionIDFrame)
Expect(nf.SequenceNumber).To(BeEquivalentTo(5))
Expect(nf.ConnectionID.Len()).To(Equal(7))
})
It("retires the initial connection ID", func() {
Expect(g.Retire(0, protocol.ConnectionID{})).To(Succeed())
Expect(removedConnIDs).To(BeEmpty())
Expect(retiredConnIDs).To(HaveLen(1))
Expect(retiredConnIDs[0]).To(Equal(initialConnID))
Expect(addedConnIDs).To(BeEmpty())
})
It("handles duplicate retirements", func() {
Expect(g.SetMaxActiveConnIDs(11)).To(Succeed())
queuedFrames = nil
Expect(retiredConnIDs).To(BeEmpty())
Expect(g.Retire(5, protocol.ConnectionID{})).To(Succeed())
Expect(retiredConnIDs).To(HaveLen(1))
Expect(queuedFrames).To(HaveLen(1))
Expect(g.Retire(5, protocol.ConnectionID{})).To(Succeed())
Expect(retiredConnIDs).To(HaveLen(1))
Expect(queuedFrames).To(HaveLen(1))
})
It("retires the client's initial destination connection ID when the handshake completes", func() {
g.SetHandshakeComplete()
Expect(retiredConnIDs).To(HaveLen(1))
Expect(retiredConnIDs[0]).To(Equal(initialClientDestConnID))
})
It("removes all connection IDs", func() {
Expect(g.SetMaxActiveConnIDs(5)).To(Succeed())
Expect(queuedFrames).To(HaveLen(4))
g.RemoveAll()
Expect(removedConnIDs).To(HaveLen(6)) // initial conn ID, initial client dest conn id, and newly issued ones
Expect(removedConnIDs).To(ContainElement(initialConnID))
Expect(removedConnIDs).To(ContainElement(initialClientDestConnID))
for _, f := range queuedFrames {
nf := f.(*wire.NewConnectionIDFrame)
Expect(removedConnIDs).To(ContainElement(nf.ConnectionID))
}
})
It("replaces with a closed session for all connection IDs", func() {
Expect(g.SetMaxActiveConnIDs(5)).To(Succeed())
Expect(queuedFrames).To(HaveLen(4))
sess := NewMockPacketHandler(mockCtrl)
g.ReplaceWithClosed(sess)
Expect(replacedWithClosed).To(HaveLen(6)) // initial conn ID, initial client dest conn id, and newly issued ones
Expect(replacedWithClosed).To(HaveKeyWithValue(string(initialClientDestConnID), sess))
Expect(replacedWithClosed).To(HaveKeyWithValue(string(initialConnID), sess))
for _, f := range queuedFrames {
nf := f.(*wire.NewConnectionIDFrame)
Expect(replacedWithClosed).To(HaveKeyWithValue(string(nf.ConnectionID), sess))
}
})
})
quic-go-0.25.0/conn_id_manager.go 0000664 0000000 0000000 00000015022 14171454516 0016604 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/wire"
)
type connIDManager struct {
queue utils.NewConnectionIDList
handshakeComplete bool
activeSequenceNumber uint64
highestRetired uint64
activeConnectionID protocol.ConnectionID
activeStatelessResetToken *protocol.StatelessResetToken
// We change the connection ID after sending on average
// protocol.PacketsPerConnectionID packets. The actual value is randomized
// hide the packet loss rate from on-path observers.
rand utils.Rand
packetsSinceLastChange uint32
packetsPerConnectionID uint32
addStatelessResetToken func(protocol.StatelessResetToken)
removeStatelessResetToken func(protocol.StatelessResetToken)
queueControlFrame func(wire.Frame)
}
func newConnIDManager(
initialDestConnID protocol.ConnectionID,
addStatelessResetToken func(protocol.StatelessResetToken),
removeStatelessResetToken func(protocol.StatelessResetToken),
queueControlFrame func(wire.Frame),
) *connIDManager {
return &connIDManager{
activeConnectionID: initialDestConnID,
addStatelessResetToken: addStatelessResetToken,
removeStatelessResetToken: removeStatelessResetToken,
queueControlFrame: queueControlFrame,
}
}
func (h *connIDManager) AddFromPreferredAddress(connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
return h.addConnectionID(1, connID, resetToken)
}
func (h *connIDManager) Add(f *wire.NewConnectionIDFrame) error {
if err := h.add(f); err != nil {
return err
}
if h.queue.Len() >= protocol.MaxActiveConnectionIDs {
return &qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}
}
return nil
}
func (h *connIDManager) add(f *wire.NewConnectionIDFrame) error {
// If the NEW_CONNECTION_ID frame is reordered, such that its sequence number is smaller than the currently active
// connection ID or if it was already retired, send the RETIRE_CONNECTION_ID frame immediately.
if f.SequenceNumber < h.activeSequenceNumber || f.SequenceNumber < h.highestRetired {
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: f.SequenceNumber,
})
return nil
}
// Retire elements in the queue.
// Doesn't retire the active connection ID.
if f.RetirePriorTo > h.highestRetired {
var next *utils.NewConnectionIDElement
for el := h.queue.Front(); el != nil; el = next {
if el.Value.SequenceNumber >= f.RetirePriorTo {
break
}
next = el.Next()
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: el.Value.SequenceNumber,
})
h.queue.Remove(el)
}
h.highestRetired = f.RetirePriorTo
}
if f.SequenceNumber == h.activeSequenceNumber {
return nil
}
if err := h.addConnectionID(f.SequenceNumber, f.ConnectionID, f.StatelessResetToken); err != nil {
return err
}
// Retire the active connection ID, if necessary.
if h.activeSequenceNumber < f.RetirePriorTo {
// The queue is guaranteed to have at least one element at this point.
h.updateConnectionID()
}
return nil
}
func (h *connIDManager) addConnectionID(seq uint64, connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
// insert a new element at the end
if h.queue.Len() == 0 || h.queue.Back().Value.SequenceNumber < seq {
h.queue.PushBack(utils.NewConnectionID{
SequenceNumber: seq,
ConnectionID: connID,
StatelessResetToken: resetToken,
})
return nil
}
// insert a new element somewhere in the middle
for el := h.queue.Front(); el != nil; el = el.Next() {
if el.Value.SequenceNumber == seq {
if !el.Value.ConnectionID.Equal(connID) {
return fmt.Errorf("received conflicting connection IDs for sequence number %d", seq)
}
if el.Value.StatelessResetToken != resetToken {
return fmt.Errorf("received conflicting stateless reset tokens for sequence number %d", seq)
}
break
}
if el.Value.SequenceNumber > seq {
h.queue.InsertBefore(utils.NewConnectionID{
SequenceNumber: seq,
ConnectionID: connID,
StatelessResetToken: resetToken,
}, el)
break
}
}
return nil
}
func (h *connIDManager) updateConnectionID() {
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: h.activeSequenceNumber,
})
h.highestRetired = utils.MaxUint64(h.highestRetired, h.activeSequenceNumber)
if h.activeStatelessResetToken != nil {
h.removeStatelessResetToken(*h.activeStatelessResetToken)
}
front := h.queue.Remove(h.queue.Front())
h.activeSequenceNumber = front.SequenceNumber
h.activeConnectionID = front.ConnectionID
h.activeStatelessResetToken = &front.StatelessResetToken
h.packetsSinceLastChange = 0
h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint32(h.rand.Int31n(protocol.PacketsPerConnectionID))
h.addStatelessResetToken(*h.activeStatelessResetToken)
}
func (h *connIDManager) Close() {
if h.activeStatelessResetToken != nil {
h.removeStatelessResetToken(*h.activeStatelessResetToken)
}
}
// is called when the server performs a Retry
// and when the server changes the connection ID in the first Initial sent
func (h *connIDManager) ChangeInitialConnID(newConnID protocol.ConnectionID) {
if h.activeSequenceNumber != 0 {
panic("expected first connection ID to have sequence number 0")
}
h.activeConnectionID = newConnID
}
// is called when the server provides a stateless reset token in the transport parameters
func (h *connIDManager) SetStatelessResetToken(token protocol.StatelessResetToken) {
if h.activeSequenceNumber != 0 {
panic("expected first connection ID to have sequence number 0")
}
h.activeStatelessResetToken = &token
h.addStatelessResetToken(token)
}
func (h *connIDManager) SentPacket() {
h.packetsSinceLastChange++
}
func (h *connIDManager) shouldUpdateConnID() bool {
if !h.handshakeComplete {
return false
}
// initiate the first change as early as possible (after handshake completion)
if h.queue.Len() > 0 && h.activeSequenceNumber == 0 {
return true
}
// For later changes, only change if
// 1. The queue of connection IDs is filled more than 50%.
// 2. We sent at least PacketsPerConnectionID packets
return 2*h.queue.Len() >= protocol.MaxActiveConnectionIDs &&
h.packetsSinceLastChange >= h.packetsPerConnectionID
}
func (h *connIDManager) Get() protocol.ConnectionID {
if h.shouldUpdateConnID() {
h.updateConnectionID()
}
return h.activeConnectionID
}
func (h *connIDManager) SetHandshakeComplete() {
h.handshakeComplete = true
}
quic-go-0.25.0/conn_id_manager_test.go 0000664 0000000 0000000 00000033734 14171454516 0017655 0 ustar 00root root 0000000 0000000 package quic
import (
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/wire"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Connection ID Manager", func() {
var (
m *connIDManager
frameQueue []wire.Frame
tokenAdded *protocol.StatelessResetToken
removedTokens []protocol.StatelessResetToken
)
initialConnID := protocol.ConnectionID{0, 0, 0, 0}
BeforeEach(func() {
frameQueue = nil
tokenAdded = nil
removedTokens = nil
m = newConnIDManager(
initialConnID,
func(token protocol.StatelessResetToken) { tokenAdded = &token },
func(token protocol.StatelessResetToken) { removedTokens = append(removedTokens, token) },
func(f wire.Frame,
) {
frameQueue = append(frameQueue, f)
})
})
get := func() (protocol.ConnectionID, protocol.StatelessResetToken) {
if m.queue.Len() == 0 {
return nil, protocol.StatelessResetToken{}
}
val := m.queue.Remove(m.queue.Front())
return val.ConnectionID, val.StatelessResetToken
}
It("returns the initial connection ID", func() {
Expect(m.Get()).To(Equal(initialConnID))
})
It("changes the initial connection ID", func() {
m.ChangeInitialConnID(protocol.ConnectionID{1, 2, 3, 4, 5})
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 2, 3, 4, 5}))
})
It("sets the token for the first connection ID", func() {
token := protocol.StatelessResetToken{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
m.SetStatelessResetToken(token)
Expect(*m.activeStatelessResetToken).To(Equal(token))
Expect(*tokenAdded).To(Equal(token))
})
It("adds and gets connection IDs", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 10,
ConnectionID: protocol.ConnectionID{2, 3, 4, 5},
StatelessResetToken: protocol.StatelessResetToken{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0},
})).To(Succeed())
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 4,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe},
})).To(Succeed())
c1, rt1 := get()
Expect(c1).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
Expect(rt1).To(Equal(protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe}))
c2, rt2 := get()
Expect(c2).To(Equal(protocol.ConnectionID{2, 3, 4, 5}))
Expect(rt2).To(Equal(protocol.StatelessResetToken{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}))
c3, _ := get()
Expect(c3).To(BeNil())
})
It("accepts duplicates", func() {
f1 := &wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe},
}
f2 := &wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe},
}
Expect(m.Add(f1)).To(Succeed())
Expect(m.Add(f2)).To(Succeed())
c1, rt1 := get()
Expect(c1).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
Expect(rt1).To(Equal(protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe}))
c2, _ := get()
Expect(c2).To(BeNil())
})
It("ignores duplicates for the currently used connection ID", func() {
f := &wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe},
}
m.SetHandshakeComplete()
Expect(m.Add(f)).To(Succeed())
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
c, _ := get()
Expect(c).To(BeNil())
// Now send the same connection ID again. It should not be queued.
Expect(m.Add(f)).To(Succeed())
c, _ = get()
Expect(c).To(BeNil())
})
It("rejects duplicates with different connection IDs", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 42,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
})).To(Succeed())
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 42,
ConnectionID: protocol.ConnectionID{2, 3, 4, 5},
})).To(MatchError("received conflicting connection IDs for sequence number 42"))
})
It("rejects duplicates with different connection IDs", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 42,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe},
})).To(Succeed())
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 42,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0},
})).To(MatchError("received conflicting stateless reset tokens for sequence number 42"))
})
It("retires connection IDs", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 10,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
})).To(Succeed())
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 13,
ConnectionID: protocol.ConnectionID{2, 3, 4, 5},
})).To(Succeed())
Expect(frameQueue).To(BeEmpty())
Expect(m.Add(&wire.NewConnectionIDFrame{
RetirePriorTo: 14,
SequenceNumber: 17,
ConnectionID: protocol.ConnectionID{3, 4, 5, 6},
})).To(Succeed())
Expect(frameQueue).To(HaveLen(3))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeEquivalentTo(10))
Expect(frameQueue[1].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeEquivalentTo(13))
Expect(frameQueue[2].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeZero())
Expect(m.Get()).To(Equal(protocol.ConnectionID{3, 4, 5, 6}))
})
It("ignores reordered connection IDs, if their sequence number was already retired", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 10,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
RetirePriorTo: 5,
})).To(Succeed())
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeZero())
frameQueue = nil
// If this NEW_CONNECTION_ID frame hadn't been reordered, we would have retired it before.
// Make sure it gets retired immediately now.
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 4,
ConnectionID: protocol.ConnectionID{4, 3, 2, 1},
})).To(Succeed())
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeEquivalentTo(4))
})
It("ignores reordered connection IDs, if their sequence number was already retired or less than active", func() {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 10,
ConnectionID: protocol.ConnectionID{0xde, 0xad, 0xbe, 0xef},
RetirePriorTo: 5,
})).To(Succeed())
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeZero())
frameQueue = nil
Expect(m.Get()).To(Equal(protocol.ConnectionID{0xde, 0xad, 0xbe, 0xef}))
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 9,
ConnectionID: protocol.ConnectionID{0xde, 0xca, 0xfb, 0xad},
RetirePriorTo: 5,
})).To(Succeed())
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeEquivalentTo(9))
})
It("accepts retransmissions for the connection ID that is in use", func() {
connID := protocol.ConnectionID{1, 2, 3, 4}
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: connID,
})).To(Succeed())
m.SetHandshakeComplete()
Expect(frameQueue).To(BeEmpty())
Expect(m.Get()).To(Equal(connID))
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0]).To(BeAssignableToTypeOf(&wire.RetireConnectionIDFrame{}))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeZero())
frameQueue = nil
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: connID,
})).To(Succeed())
Expect(frameQueue).To(BeEmpty())
})
It("errors when the peer sends too connection IDs", func() {
for i := uint8(1); i < protocol.MaxActiveConnectionIDs; i++ {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(i),
ConnectionID: protocol.ConnectionID{i, i, i, i},
StatelessResetToken: protocol.StatelessResetToken{i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i},
})).To(Succeed())
}
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(9999),
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
})).To(MatchError(&qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}))
})
It("initiates the first connection ID update as soon as possible", func() {
Expect(m.Get()).To(Equal(initialConnID))
m.SetHandshakeComplete()
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
})).To(Succeed())
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
})
It("waits until handshake completion before initiating a connection ID update", func() {
Expect(m.Get()).To(Equal(initialConnID))
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
})).To(Succeed())
Expect(m.Get()).To(Equal(initialConnID))
m.SetHandshakeComplete()
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
})
It("initiates subsequent updates when enough packets are sent", func() {
var s uint8
for s = uint8(1); s < protocol.MaxActiveConnectionIDs; s++ {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(s),
ConnectionID: protocol.ConnectionID{s, s, s, s},
StatelessResetToken: protocol.StatelessResetToken{s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s},
})).To(Succeed())
}
m.SetHandshakeComplete()
lastConnID := m.Get()
Expect(lastConnID).To(Equal(protocol.ConnectionID{1, 1, 1, 1}))
var counter int
for i := 0; i < 50*protocol.PacketsPerConnectionID; i++ {
m.SentPacket()
connID := m.Get()
if !connID.Equal(lastConnID) {
counter++
lastConnID = connID
Expect(removedTokens).To(HaveLen(1))
removedTokens = nil
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(s),
ConnectionID: protocol.ConnectionID{s, s, s, s},
StatelessResetToken: protocol.StatelessResetToken{s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s},
})).To(Succeed())
s++
}
}
Expect(counter).To(BeNumerically("~", 50, 10))
})
It("retires delayed connection IDs that arrive after a higher connection ID was already retired", func() {
for s := uint8(10); s <= 10+protocol.MaxActiveConnectionIDs/2; s++ {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(s),
ConnectionID: protocol.ConnectionID{s, s, s, s},
StatelessResetToken: protocol.StatelessResetToken{s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s},
})).To(Succeed())
}
m.SetHandshakeComplete()
Expect(m.Get()).To(Equal(protocol.ConnectionID{10, 10, 10, 10}))
for {
m.SentPacket()
if m.Get().Equal(protocol.ConnectionID{11, 11, 11, 11}) {
break
}
}
// The active conn ID is now {11, 11, 11, 11}
Expect(m.queue.Front().Value.ConnectionID).To(Equal(protocol.ConnectionID{12, 12, 12, 12}))
// Add a delayed connection ID. It should just be ignored now.
frameQueue = nil
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(5),
ConnectionID: protocol.ConnectionID{5, 5, 5, 5},
StatelessResetToken: protocol.StatelessResetToken{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
})).To(Succeed())
Expect(m.queue.Front().Value.ConnectionID).To(Equal(protocol.ConnectionID{12, 12, 12, 12}))
Expect(frameQueue).To(HaveLen(1))
Expect(frameQueue[0].(*wire.RetireConnectionIDFrame).SequenceNumber).To(BeEquivalentTo(5))
})
It("only initiates subsequent updates when enough if enough connection IDs are queued", func() {
for i := uint8(1); i <= protocol.MaxActiveConnectionIDs/2; i++ {
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: uint64(i),
ConnectionID: protocol.ConnectionID{i, i, i, i},
StatelessResetToken: protocol.StatelessResetToken{i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i},
})).To(Succeed())
}
m.SetHandshakeComplete()
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 1, 1, 1}))
for i := 0; i < 2*protocol.PacketsPerConnectionID; i++ {
m.SentPacket()
}
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 1, 1, 1}))
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1337,
ConnectionID: protocol.ConnectionID{1, 3, 3, 7},
})).To(Succeed())
Expect(m.Get()).To(Equal(protocol.ConnectionID{2, 2, 2, 2}))
Expect(removedTokens).To(HaveLen(1))
Expect(removedTokens[0]).To(Equal(protocol.StatelessResetToken{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}))
})
It("removes the currently active stateless reset token when it is closed", func() {
m.Close()
Expect(removedTokens).To(BeEmpty())
Expect(m.Add(&wire.NewConnectionIDFrame{
SequenceNumber: 1,
ConnectionID: protocol.ConnectionID{1, 2, 3, 4},
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
})).To(Succeed())
m.SetHandshakeComplete()
Expect(m.Get()).To(Equal(protocol.ConnectionID{1, 2, 3, 4}))
m.Close()
Expect(removedTokens).To(HaveLen(1))
Expect(removedTokens[0]).To(Equal(protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}))
})
})
quic-go-0.25.0/conn_oob.go 0000664 0000000 0000000 00000016764 14171454516 0015313 0 ustar 00root root 0000000 0000000 //go:build darwin || linux || freebsd
// +build darwin linux freebsd
package quic
import (
"encoding/binary"
"errors"
"fmt"
"net"
"syscall"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
)
const (
ecnMask = 0x3
oobBufferSize = 128
)
// Contrary to what the naming suggests, the ipv{4,6}.Message is not dependent on the IP version.
// They're both just aliases for x/net/internal/socket.Message.
// This means we can use this struct to read from a socket that receives both IPv4 and IPv6 messages.
var _ ipv4.Message = ipv6.Message{}
type batchConn interface {
ReadBatch(ms []ipv4.Message, flags int) (int, error)
}
func inspectReadBuffer(c interface{}) (int, error) {
conn, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
})
if !ok {
return 0, errors.New("doesn't have a SyscallConn")
}
rawConn, err := conn.SyscallConn()
if err != nil {
return 0, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
var size int
var serr error
if err := rawConn.Control(func(fd uintptr) {
size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF)
}); err != nil {
return 0, err
}
return size, serr
}
type oobConn struct {
OOBCapablePacketConn
batchConn batchConn
readPos uint8
// Packets received from the kernel, but not yet returned by ReadPacket().
messages []ipv4.Message
buffers [batchSize]*packetBuffer
}
var _ connection = &oobConn{}
func newConn(c OOBCapablePacketConn) (*oobConn, error) {
rawConn, err := c.SyscallConn()
if err != nil {
return nil, err
}
needsPacketInfo := false
if udpAddr, ok := c.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP.IsUnspecified() {
needsPacketInfo = true
}
// We don't know if this a IPv4-only, IPv6-only or a IPv4-and-IPv6 connection.
// Try enabling receiving of ECN and packet info for both IP versions.
// We expect at least one of those syscalls to succeed.
var errECNIPv4, errECNIPv6, errPIIPv4, errPIIPv6 error
if err := rawConn.Control(func(fd uintptr) {
errECNIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
errECNIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)
if needsPacketInfo {
errPIIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, ipv4RECVPKTINFO, 1)
errPIIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, ipv6RECVPKTINFO, 1)
}
}); err != nil {
return nil, err
}
switch {
case errECNIPv4 == nil && errECNIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4 and IPv6.")
case errECNIPv4 == nil && errECNIPv6 != nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4.")
case errECNIPv4 != nil && errECNIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv6.")
case errECNIPv4 != nil && errECNIPv6 != nil:
return nil, errors.New("activating ECN failed for both IPv4 and IPv6")
}
if needsPacketInfo {
switch {
case errPIIPv4 == nil && errPIIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of packet info for IPv4 and IPv6.")
case errPIIPv4 == nil && errPIIPv6 != nil:
utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv4.")
case errPIIPv4 != nil && errPIIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv6.")
case errPIIPv4 != nil && errPIIPv6 != nil:
return nil, errors.New("activating packet info failed for both IPv4 and IPv6")
}
}
// Allows callers to pass in a connection that already satisfies batchConn interface
// to make use of the optimisation. Otherwise, ipv4.NewPacketConn would unwrap the file descriptor
// via SyscallConn(), and read it that way, which might not be what the caller wants.
var bc batchConn
if ibc, ok := c.(batchConn); ok {
bc = ibc
} else {
bc = ipv4.NewPacketConn(c)
}
oobConn := &oobConn{
OOBCapablePacketConn: c,
batchConn: bc,
messages: make([]ipv4.Message, batchSize),
readPos: batchSize,
}
for i := 0; i < batchSize; i++ {
oobConn.messages[i].OOB = make([]byte, oobBufferSize)
}
return oobConn, nil
}
func (c *oobConn) ReadPacket() (*receivedPacket, error) {
if len(c.messages) == int(c.readPos) { // all messages read. Read the next batch of messages.
c.messages = c.messages[:batchSize]
// replace buffers data buffers up to the packet that has been consumed during the last ReadBatch call
for i := uint8(0); i < c.readPos; i++ {
buffer := getPacketBuffer()
buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize]
c.buffers[i] = buffer
c.messages[i].Buffers = [][]byte{c.buffers[i].Data}
}
c.readPos = 0
n, err := c.batchConn.ReadBatch(c.messages, 0)
if n == 0 || err != nil {
return nil, err
}
c.messages = c.messages[:n]
}
msg := c.messages[c.readPos]
buffer := c.buffers[c.readPos]
c.readPos++
ctrlMsgs, err := unix.ParseSocketControlMessage(msg.OOB[:msg.NN])
if err != nil {
return nil, err
}
var ecn protocol.ECN
var destIP net.IP
var ifIndex uint32
for _, ctrlMsg := range ctrlMsgs {
if ctrlMsg.Header.Level == unix.IPPROTO_IP {
switch ctrlMsg.Header.Type {
case msgTypeIPTOS:
ecn = protocol.ECN(ctrlMsg.Data[0] & ecnMask)
case msgTypeIPv4PKTINFO:
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination
// address */
// };
ip := make([]byte, 4)
if len(ctrlMsg.Data) == 12 {
ifIndex = binary.LittleEndian.Uint32(ctrlMsg.Data)
copy(ip, ctrlMsg.Data[8:12])
} else if len(ctrlMsg.Data) == 4 {
// FreeBSD
copy(ip, ctrlMsg.Data)
}
destIP = net.IP(ip)
}
}
if ctrlMsg.Header.Level == unix.IPPROTO_IPV6 {
switch ctrlMsg.Header.Type {
case unix.IPV6_TCLASS:
ecn = protocol.ECN(ctrlMsg.Data[0] & ecnMask)
case msgTypeIPv6PKTINFO:
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
if len(ctrlMsg.Data) == 20 {
ip := make([]byte, 16)
copy(ip, ctrlMsg.Data[:16])
destIP = net.IP(ip)
ifIndex = binary.LittleEndian.Uint32(ctrlMsg.Data[16:])
}
}
}
}
var info *packetInfo
if destIP != nil {
info = &packetInfo{
addr: destIP,
ifIndex: ifIndex,
}
}
return &receivedPacket{
remoteAddr: msg.Addr,
rcvTime: time.Now(),
data: msg.Buffers[0][:msg.N],
ecn: ecn,
info: info,
buffer: buffer,
}, nil
}
func (c *oobConn) WritePacket(b []byte, addr net.Addr, oob []byte) (n int, err error) {
n, _, err = c.OOBCapablePacketConn.WriteMsgUDP(b, oob, addr.(*net.UDPAddr))
return n, err
}
func (info *packetInfo) OOB() []byte {
if info == nil {
return nil
}
if ip4 := info.addr.To4(); ip4 != nil {
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
cm := ipv4.ControlMessage{
Src: ip4,
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
} else if len(info.addr) == 16 {
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
cm := ipv6.ControlMessage{
Src: info.addr,
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
}
return nil
}
quic-go-0.25.0/conn_oob_test.go 0000664 0000000 0000000 00000016161 14171454516 0016341 0 ustar 00root root 0000000 0000000 //go:build !windows
// +build !windows
package quic
import (
"fmt"
"net"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/sys/unix"
"github.com/golang/mock/gomock"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("OOB Conn Test", func() {
runServer := func(network, address string) (*net.UDPConn, <-chan *receivedPacket) {
addr, err := net.ResolveUDPAddr(network, address)
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP(network, addr)
Expect(err).ToNot(HaveOccurred())
oobConn, err := newConn(udpConn)
Expect(err).ToNot(HaveOccurred())
packetChan := make(chan *receivedPacket)
go func() {
defer GinkgoRecover()
for {
p, err := oobConn.ReadPacket()
if err != nil {
return
}
packetChan <- p
}
}()
return udpConn, packetChan
}
Context("ECN conn", func() {
sendPacketWithECN := func(network string, addr *net.UDPAddr, setECN func(uintptr)) net.Addr {
conn, err := net.DialUDP(network, nil, addr)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
rawConn, err := conn.SyscallConn()
ExpectWithOffset(1, err).ToNot(HaveOccurred())
ExpectWithOffset(1, rawConn.Control(func(fd uintptr) {
setECN(fd)
})).To(Succeed())
_, err = conn.Write([]byte("foobar"))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return conn.LocalAddr()
}
It("reads ECN flags on IPv4", func() {
conn, packetChan := runServer("udp4", "localhost:0")
defer conn.Close()
sentFrom := sendPacketWithECN(
"udp4",
conn.LocalAddr().(*net.UDPAddr),
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 2)).To(Succeed())
},
)
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.ecn).To(Equal(protocol.ECT0))
})
It("reads ECN flags on IPv6", func() {
conn, packetChan := runServer("udp6", "[::]:0")
defer conn.Close()
sentFrom := sendPacketWithECN(
"udp6",
conn.LocalAddr().(*net.UDPAddr),
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 3)).To(Succeed())
},
)
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.ecn).To(Equal(protocol.ECNCE))
})
It("reads ECN flags on a connection that supports both IPv4 and IPv6", func() {
conn, packetChan := runServer("udp", "0.0.0.0:0")
defer conn.Close()
port := conn.LocalAddr().(*net.UDPAddr).Port
// IPv4
sendPacketWithECN(
"udp4",
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port},
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 3)).To(Succeed())
},
)
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(utils.IsIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeTrue())
Expect(p.ecn).To(Equal(protocol.ECNCE))
// IPv6
sendPacketWithECN(
"udp6",
&net.UDPAddr{IP: net.IPv6loopback, Port: port},
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 1)).To(Succeed())
},
)
Eventually(packetChan).Should(Receive(&p))
Expect(utils.IsIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeFalse())
Expect(p.ecn).To(Equal(protocol.ECT1))
})
})
Context("Packet Info conn", func() {
sendPacket := func(network string, addr *net.UDPAddr) net.Addr {
conn, err := net.DialUDP(network, nil, addr)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
_, err = conn.Write([]byte("foobar"))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return conn.LocalAddr()
}
It("reads packet info on IPv4", func() {
conn, packetChan := runServer("udp4", ":0")
defer conn.Close()
addr := conn.LocalAddr().(*net.UDPAddr)
ip := net.ParseIP("127.0.0.1").To4()
addr.IP = ip
sentFrom := sendPacket("udp4", addr)
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.info).To(Not(BeNil()))
Expect(p.info.addr.To4()).To(Equal(ip))
})
It("reads packet info on IPv6", func() {
conn, packetChan := runServer("udp6", ":0")
defer conn.Close()
addr := conn.LocalAddr().(*net.UDPAddr)
ip := net.ParseIP("::1")
addr.IP = ip
sentFrom := sendPacket("udp6", addr)
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.info).To(Not(BeNil()))
Expect(p.info.addr).To(Equal(ip))
})
It("reads packet info on a connection that supports both IPv4 and IPv6", func() {
conn, packetChan := runServer("udp", ":0")
defer conn.Close()
port := conn.LocalAddr().(*net.UDPAddr).Port
// IPv4
ip4 := net.ParseIP("127.0.0.1").To4()
sendPacket("udp4", &net.UDPAddr{IP: ip4, Port: port})
var p *receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(utils.IsIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeTrue())
Expect(p.info).To(Not(BeNil()))
Expect(p.info.addr.To4()).To(Equal(ip4))
// IPv6
ip6 := net.ParseIP("::1")
sendPacket("udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: port})
Eventually(packetChan).Should(Receive(&p))
Expect(utils.IsIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeFalse())
Expect(p.info).To(Not(BeNil()))
Expect(p.info.addr).To(Equal(ip6))
})
})
Context("Batch Reading", func() {
var batchConn *MockBatchConn
BeforeEach(func() {
batchConn = NewMockBatchConn(mockCtrl)
})
It("reads multiple messages in one batch", func() {
const numMsgRead = batchSize/2 + 1
var counter int
batchConn.EXPECT().ReadBatch(gomock.Any(), gomock.Any()).DoAndReturn(func(ms []ipv4.Message, flags int) (int, error) {
Expect(ms).To(HaveLen(batchSize))
for i := 0; i < numMsgRead; i++ {
Expect(ms[i].Buffers).To(HaveLen(1))
Expect(ms[i].Buffers[0]).To(HaveLen(int(protocol.MaxPacketBufferSize)))
data := []byte(fmt.Sprintf("message %d", counter))
counter++
ms[i].Buffers[0] = data
ms[i].N = len(data)
}
return numMsgRead, nil
}).Times(2)
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", addr)
Expect(err).ToNot(HaveOccurred())
oobConn, err := newConn(udpConn)
Expect(err).ToNot(HaveOccurred())
oobConn.batchConn = batchConn
for i := 0; i < batchSize+1; i++ {
p, err := oobConn.ReadPacket()
Expect(err).ToNot(HaveOccurred())
Expect(string(p.data)).To(Equal(fmt.Sprintf("message %d", i)))
}
})
})
})
quic-go-0.25.0/conn_test.go 0000664 0000000 0000000 00000001570 14171454516 0015500 0 ustar 00root root 0000000 0000000 package quic
import (
"net"
"time"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Basic Conn Test", func() {
It("reads a packet", func() {
c := NewMockPacketConn(mockCtrl)
addr := &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 1234}
c.EXPECT().ReadFrom(gomock.Any()).DoAndReturn(func(b []byte) (int, net.Addr, error) {
data := []byte("foobar")
Expect(b).To(HaveLen(int(protocol.MaxPacketBufferSize)))
return copy(b, data), addr, nil
})
conn, err := wrapConn(c)
Expect(err).ToNot(HaveOccurred())
p, err := conn.ReadPacket()
Expect(err).ToNot(HaveOccurred())
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(100*time.Millisecond)))
Expect(p.remoteAddr).To(Equal(addr))
})
})
quic-go-0.25.0/conn_windows.go 0000664 0000000 0000000 00000002500 14171454516 0016205 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package quic
import (
"errors"
"fmt"
"net"
"syscall"
"golang.org/x/sys/windows"
)
const (
disablePathMTUDiscovery = true
IP_DONTFRAGMENT = 14
)
func newConn(c OOBCapablePacketConn) (connection, error) {
rawConn, err := c.SyscallConn()
if err != nil {
return nil, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
if err := rawConn.Control(func(fd uintptr) {
// This should succeed if the connection is a IPv4 or a dual-stack connection.
// It will fail for IPv6 connections.
// TODO: properly handle error.
_ = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_DONTFRAGMENT, 1)
}); err != nil {
return nil, err
}
return &basicConn{PacketConn: c}, nil
}
func inspectReadBuffer(c net.PacketConn) (int, error) {
conn, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
})
if !ok {
return 0, errors.New("doesn't have a SyscallConn")
}
rawConn, err := conn.SyscallConn()
if err != nil {
return 0, fmt.Errorf("couldn't get syscall.RawConn: %w", err)
}
var size int
var serr error
if err := rawConn.Control(func(fd uintptr) {
size, serr = windows.GetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_RCVBUF)
}); err != nil {
return 0, err
}
return size, serr
}
func (i *packetInfo) OOB() []byte { return nil }
quic-go-0.25.0/conn_windows_test.go 0000664 0000000 0000000 00000001443 14171454516 0017251 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package quic
import (
"net"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Windows Conn Test", func() {
It("works on IPv4", func() {
addr, err := net.ResolveUDPAddr("udp4", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp4", addr)
Expect(err).ToNot(HaveOccurred())
conn, err := newConn(udpConn)
Expect(err).ToNot(HaveOccurred())
Expect(conn.Close()).To(Succeed())
})
It("works on IPv6", func() {
addr, err := net.ResolveUDPAddr("udp6", "[::1]:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp6", addr)
Expect(err).ToNot(HaveOccurred())
conn, err := newConn(udpConn)
Expect(err).ToNot(HaveOccurred())
Expect(conn.Close()).To(Succeed())
})
})
quic-go-0.25.0/crypto_stream.go 0000664 0000000 0000000 00000006015 14171454516 0016376 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"io"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/wire"
)
type cryptoStream interface {
// for receiving data
HandleCryptoFrame(*wire.CryptoFrame) error
GetCryptoData() []byte
Finish() error
// for sending data
io.Writer
HasData() bool
PopCryptoFrame(protocol.ByteCount) *wire.CryptoFrame
}
type cryptoStreamImpl struct {
queue *frameSorter
msgBuf []byte
highestOffset protocol.ByteCount
finished bool
writeOffset protocol.ByteCount
writeBuf []byte
}
func newCryptoStream() cryptoStream {
return &cryptoStreamImpl{queue: newFrameSorter()}
}
func (s *cryptoStreamImpl) HandleCryptoFrame(f *wire.CryptoFrame) error {
highestOffset := f.Offset + protocol.ByteCount(len(f.Data))
if maxOffset := highestOffset; maxOffset > protocol.MaxCryptoStreamOffset {
return &qerr.TransportError{
ErrorCode: qerr.CryptoBufferExceeded,
ErrorMessage: fmt.Sprintf("received invalid offset %d on crypto stream, maximum allowed %d", maxOffset, protocol.MaxCryptoStreamOffset),
}
}
if s.finished {
if highestOffset > s.highestOffset {
// reject crypto data received after this stream was already finished
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received crypto data after change of encryption level",
}
}
// ignore data with a smaller offset than the highest received
// could e.g. be a retransmission
return nil
}
s.highestOffset = utils.MaxByteCount(s.highestOffset, highestOffset)
if err := s.queue.Push(f.Data, f.Offset, nil); err != nil {
return err
}
for {
_, data, _ := s.queue.Pop()
if data == nil {
return nil
}
s.msgBuf = append(s.msgBuf, data...)
}
}
// GetCryptoData retrieves data that was received in CRYPTO frames
func (s *cryptoStreamImpl) GetCryptoData() []byte {
if len(s.msgBuf) < 4 {
return nil
}
msgLen := 4 + int(s.msgBuf[1])<<16 + int(s.msgBuf[2])<<8 + int(s.msgBuf[3])
if len(s.msgBuf) < msgLen {
return nil
}
msg := make([]byte, msgLen)
copy(msg, s.msgBuf[:msgLen])
s.msgBuf = s.msgBuf[msgLen:]
return msg
}
func (s *cryptoStreamImpl) Finish() error {
if s.queue.HasMoreData() {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "encryption level changed, but crypto stream has more data to read",
}
}
s.finished = true
return nil
}
// Writes writes data that should be sent out in CRYPTO frames
func (s *cryptoStreamImpl) Write(p []byte) (int, error) {
s.writeBuf = append(s.writeBuf, p...)
return len(p), nil
}
func (s *cryptoStreamImpl) HasData() bool {
return len(s.writeBuf) > 0
}
func (s *cryptoStreamImpl) PopCryptoFrame(maxLen protocol.ByteCount) *wire.CryptoFrame {
f := &wire.CryptoFrame{Offset: s.writeOffset}
n := utils.MinByteCount(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
f.Data = s.writeBuf[:n]
s.writeBuf = s.writeBuf[n:]
s.writeOffset += n
return f
}
quic-go-0.25.0/crypto_stream_manager.go 0000664 0000000 0000000 00000003056 14171454516 0020072 0 ustar 00root root 0000000 0000000 package quic
import (
"fmt"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/wire"
)
type cryptoDataHandler interface {
HandleMessage([]byte, protocol.EncryptionLevel) bool
}
type cryptoStreamManager struct {
cryptoHandler cryptoDataHandler
initialStream cryptoStream
handshakeStream cryptoStream
oneRTTStream cryptoStream
}
func newCryptoStreamManager(
cryptoHandler cryptoDataHandler,
initialStream cryptoStream,
handshakeStream cryptoStream,
oneRTTStream cryptoStream,
) *cryptoStreamManager {
return &cryptoStreamManager{
cryptoHandler: cryptoHandler,
initialStream: initialStream,
handshakeStream: handshakeStream,
oneRTTStream: oneRTTStream,
}
}
func (m *cryptoStreamManager) HandleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) (bool /* encryption level changed */, error) {
var str cryptoStream
//nolint:exhaustive // CRYPTO frames cannot be sent in 0-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
str = m.initialStream
case protocol.EncryptionHandshake:
str = m.handshakeStream
case protocol.Encryption1RTT:
str = m.oneRTTStream
default:
return false, fmt.Errorf("received CRYPTO frame with unexpected encryption level: %s", encLevel)
}
if err := str.HandleCryptoFrame(frame); err != nil {
return false, err
}
for {
data := str.GetCryptoData()
if data == nil {
return false, nil
}
if encLevelFinished := m.cryptoHandler.HandleMessage(data, encLevel); encLevelFinished {
return true, str.Finish()
}
}
}
quic-go-0.25.0/crypto_stream_manager_test.go 0000664 0000000 0000000 00000011134 14171454516 0021125 0 ustar 00root root 0000000 0000000 package quic
import (
"errors"
"github.com/golang/mock/gomock"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/wire"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Crypto Stream Manager", func() {
var (
csm *cryptoStreamManager
cs *MockCryptoDataHandler
initialStream *MockCryptoStream
handshakeStream *MockCryptoStream
oneRTTStream *MockCryptoStream
)
BeforeEach(func() {
initialStream = NewMockCryptoStream(mockCtrl)
handshakeStream = NewMockCryptoStream(mockCtrl)
oneRTTStream = NewMockCryptoStream(mockCtrl)
cs = NewMockCryptoDataHandler(mockCtrl)
csm = newCryptoStreamManager(cs, initialStream, handshakeStream, oneRTTStream)
})
It("passes messages to the initial stream", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
initialStream.EXPECT().HandleCryptoFrame(cf)
initialStream.EXPECT().GetCryptoData().Return([]byte("foobar"))
initialStream.EXPECT().GetCryptoData()
cs.EXPECT().HandleMessage([]byte("foobar"), protocol.EncryptionInitial)
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.EncryptionInitial)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeFalse())
})
It("passes messages to the handshake stream", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
handshakeStream.EXPECT().HandleCryptoFrame(cf)
handshakeStream.EXPECT().GetCryptoData().Return([]byte("foobar"))
handshakeStream.EXPECT().GetCryptoData()
cs.EXPECT().HandleMessage([]byte("foobar"), protocol.EncryptionHandshake)
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.EncryptionHandshake)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeFalse())
})
It("passes messages to the 1-RTT stream", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
oneRTTStream.EXPECT().HandleCryptoFrame(cf)
oneRTTStream.EXPECT().GetCryptoData().Return([]byte("foobar"))
oneRTTStream.EXPECT().GetCryptoData()
cs.EXPECT().HandleMessage([]byte("foobar"), protocol.Encryption1RTT)
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.Encryption1RTT)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeFalse())
})
It("doesn't call the message handler, if there's no message", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
handshakeStream.EXPECT().HandleCryptoFrame(cf)
handshakeStream.EXPECT().GetCryptoData() // don't return any data to handle
// don't EXPECT any calls to HandleMessage()
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.EncryptionHandshake)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeFalse())
})
It("processes all messages", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
handshakeStream.EXPECT().HandleCryptoFrame(cf)
handshakeStream.EXPECT().GetCryptoData().Return([]byte("foo"))
handshakeStream.EXPECT().GetCryptoData().Return([]byte("bar"))
handshakeStream.EXPECT().GetCryptoData()
cs.EXPECT().HandleMessage([]byte("foo"), protocol.EncryptionHandshake)
cs.EXPECT().HandleMessage([]byte("bar"), protocol.EncryptionHandshake)
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.EncryptionHandshake)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeFalse())
})
It("finishes the crypto stream, when the crypto setup is done with this encryption level", func() {
cf := &wire.CryptoFrame{Data: []byte("foobar")}
gomock.InOrder(
handshakeStream.EXPECT().HandleCryptoFrame(cf),
handshakeStream.EXPECT().GetCryptoData().Return([]byte("foobar")),
cs.EXPECT().HandleMessage([]byte("foobar"), protocol.EncryptionHandshake).Return(true),
handshakeStream.EXPECT().Finish(),
)
encLevelChanged, err := csm.HandleCryptoFrame(cf, protocol.EncryptionHandshake)
Expect(err).ToNot(HaveOccurred())
Expect(encLevelChanged).To(BeTrue())
})
It("returns errors that occur when finishing a stream", func() {
testErr := errors.New("test error")
cf := &wire.CryptoFrame{Data: []byte("foobar")}
gomock.InOrder(
handshakeStream.EXPECT().HandleCryptoFrame(cf),
handshakeStream.EXPECT().GetCryptoData().Return([]byte("foobar")),
cs.EXPECT().HandleMessage([]byte("foobar"), protocol.EncryptionHandshake).Return(true),
handshakeStream.EXPECT().Finish().Return(testErr),
)
_, err := csm.HandleCryptoFrame(cf, protocol.EncryptionHandshake)
Expect(err).To(MatchError(err))
})
It("errors for unknown encryption levels", func() {
_, err := csm.HandleCryptoFrame(&wire.CryptoFrame{}, 42)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("received CRYPTO frame with unexpected encryption level"))
})
})
quic-go-0.25.0/crypto_stream_test.go 0000664 0000000 0000000 00000013243 14171454516 0017436 0 ustar 00root root 0000000 0000000 package quic
import (
"crypto/rand"
"fmt"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/qerr"
"github.com/lucas-clemente/quic-go/internal/wire"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func createHandshakeMessage(len int) []byte {
msg := make([]byte, 4+len)
rand.Read(msg[:1]) // random message type
msg[1] = uint8(len >> 16)
msg[2] = uint8(len >> 8)
msg[3] = uint8(len)
rand.Read(msg[4:])
return msg
}
var _ = Describe("Crypto Stream", func() {
var str cryptoStream
BeforeEach(func() {
str = newCryptoStream()
})
Context("handling incoming data", func() {
It("handles in-order CRYPTO frames", func() {
msg := createHandshakeMessage(6)
err := str.HandleCryptoFrame(&wire.CryptoFrame{Data: msg})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(Equal(msg))
Expect(str.GetCryptoData()).To(BeNil())
})
It("handles multiple messages in one CRYPTO frame", func() {
msg1 := createHandshakeMessage(6)
msg2 := createHandshakeMessage(10)
msg := append(append([]byte{}, msg1...), msg2...)
err := str.HandleCryptoFrame(&wire.CryptoFrame{Data: msg})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(Equal(msg1))
Expect(str.GetCryptoData()).To(Equal(msg2))
Expect(str.GetCryptoData()).To(BeNil())
})
It("errors if the frame exceeds the maximum offset", func() {
Expect(str.HandleCryptoFrame(&wire.CryptoFrame{
Offset: protocol.MaxCryptoStreamOffset - 5,
Data: []byte("foobar"),
})).To(MatchError(&qerr.TransportError{
ErrorCode: qerr.CryptoBufferExceeded,
ErrorMessage: fmt.Sprintf("received invalid offset %d on crypto stream, maximum allowed %d", protocol.MaxCryptoStreamOffset+1, protocol.MaxCryptoStreamOffset),
}))
})
It("handles messages split over multiple CRYPTO frames", func() {
msg := createHandshakeMessage(6)
err := str.HandleCryptoFrame(&wire.CryptoFrame{
Data: msg[:4],
})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(BeNil())
err = str.HandleCryptoFrame(&wire.CryptoFrame{
Offset: 4,
Data: msg[4:],
})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(Equal(msg))
Expect(str.GetCryptoData()).To(BeNil())
})
It("handles out-of-order CRYPTO frames", func() {
msg := createHandshakeMessage(6)
err := str.HandleCryptoFrame(&wire.CryptoFrame{
Offset: 4,
Data: msg[4:],
})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(BeNil())
err = str.HandleCryptoFrame(&wire.CryptoFrame{
Data: msg[:4],
})
Expect(err).ToNot(HaveOccurred())
Expect(str.GetCryptoData()).To(Equal(msg))
Expect(str.GetCryptoData()).To(BeNil())
})
Context("finishing", func() {
It("errors if there's still data to read after finishing", func() {
Expect(str.HandleCryptoFrame(&wire.CryptoFrame{
Data: createHandshakeMessage(5),
Offset: 10,
})).To(Succeed())
Expect(str.Finish()).To(MatchError(&qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "encryption level changed, but crypto stream has more data to read",
}))
})
It("works with reordered data", func() {
f1 := &wire.CryptoFrame{
Data: []byte("foo"),
}
f2 := &wire.CryptoFrame{
Offset: 3,
Data: []byte("bar"),
}
Expect(str.HandleCryptoFrame(f2)).To(Succeed())
Expect(str.HandleCryptoFrame(f1)).To(Succeed())
Expect(str.Finish()).To(Succeed())
Expect(str.HandleCryptoFrame(f2)).To(Succeed())
})
It("rejects new crypto data after finishing", func() {
Expect(str.Finish()).To(Succeed())
Expect(str.HandleCryptoFrame(&wire.CryptoFrame{
Data: createHandshakeMessage(5),
})).To(MatchError(&qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received crypto data after change of encryption level",
}))
})
It("ignores crypto data below the maximum offset received before finishing", func() {
msg := createHandshakeMessage(15)
Expect(str.HandleCryptoFrame(&wire.CryptoFrame{
Data: msg,
})).To(Succeed())
Expect(str.GetCryptoData()).To(Equal(msg))
Expect(str.Finish()).To(Succeed())
Expect(str.HandleCryptoFrame(&wire.CryptoFrame{
Offset: protocol.ByteCount(len(msg) - 6),
Data: []byte("foobar"),
})).To(Succeed())
})
})
})
Context("writing data", func() {
It("says if it has data", func() {
Expect(str.HasData()).To(BeFalse())
_, err := str.Write([]byte("foobar"))
Expect(err).ToNot(HaveOccurred())
Expect(str.HasData()).To(BeTrue())
})
It("pops crypto frames", func() {
_, err := str.Write([]byte("foobar"))
Expect(err).ToNot(HaveOccurred())
f := str.PopCryptoFrame(1000)
Expect(f).ToNot(BeNil())
Expect(f.Offset).To(BeZero())
Expect(f.Data).To(Equal([]byte("foobar")))
})
It("coalesces multiple writes", func() {
_, err := str.Write([]byte("foo"))
Expect(err).ToNot(HaveOccurred())
_, err = str.Write([]byte("bar"))
Expect(err).ToNot(HaveOccurred())
f := str.PopCryptoFrame(1000)
Expect(f).ToNot(BeNil())
Expect(f.Offset).To(BeZero())
Expect(f.Data).To(Equal([]byte("foobar")))
})
It("respects the maximum size", func() {
frameHeaderLen := (&wire.CryptoFrame{}).Length(protocol.VersionWhatever)
_, err := str.Write([]byte("foobar"))
Expect(err).ToNot(HaveOccurred())
f := str.PopCryptoFrame(frameHeaderLen + 3)
Expect(f).ToNot(BeNil())
Expect(f.Offset).To(BeZero())
Expect(f.Data).To(Equal([]byte("foo")))
f = str.PopCryptoFrame(frameHeaderLen + 3)
Expect(f).ToNot(BeNil())
Expect(f.Offset).To(Equal(protocol.ByteCount(3)))
Expect(f.Data).To(Equal([]byte("bar")))
})
})
})
quic-go-0.25.0/datagram_queue.go 0000664 0000000 0000000 00000003474 14171454516 0016475 0 ustar 00root root 0000000 0000000 package quic
import (
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/wire"
)
type datagramQueue struct {
sendQueue chan *wire.DatagramFrame
rcvQueue chan []byte
closeErr error
closed chan struct{}
hasData func()
dequeued chan struct{}
logger utils.Logger
}
func newDatagramQueue(hasData func(), logger utils.Logger) *datagramQueue {
return &datagramQueue{
hasData: hasData,
sendQueue: make(chan *wire.DatagramFrame, 1),
rcvQueue: make(chan []byte, protocol.DatagramRcvQueueLen),
dequeued: make(chan struct{}),
closed: make(chan struct{}),
logger: logger,
}
}
// AddAndWait queues a new DATAGRAM frame for sending.
// It blocks until the frame has been dequeued.
func (h *datagramQueue) AddAndWait(f *wire.DatagramFrame) error {
select {
case h.sendQueue <- f:
h.hasData()
case <-h.closed:
return h.closeErr
}
select {
case <-h.dequeued:
return nil
case <-h.closed:
return h.closeErr
}
}
// Get dequeues a DATAGRAM frame for sending.
func (h *datagramQueue) Get() *wire.DatagramFrame {
select {
case f := <-h.sendQueue:
h.dequeued <- struct{}{}
return f
default:
return nil
}
}
// HandleDatagramFrame handles a received DATAGRAM frame.
func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
data := make([]byte, len(f.Data))
copy(data, f.Data)
select {
case h.rcvQueue <- data:
default:
h.logger.Debugf("Discarding DATAGRAM frame (%d bytes payload)", len(f.Data))
}
}
// Receive gets a received DATAGRAM frame.
func (h *datagramQueue) Receive() ([]byte, error) {
select {
case data := <-h.rcvQueue:
return data, nil
case <-h.closed:
return nil, h.closeErr
}
}
func (h *datagramQueue) CloseWithError(e error) {
h.closeErr = e
close(h.closed)
}
quic-go-0.25.0/datagram_queue_test.go 0000664 0000000 0000000 00000005015 14171454516 0017525 0 ustar 00root root 0000000 0000000 package quic
import (
"errors"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/internal/wire"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Datagram Queue", func() {
var queue *datagramQueue
var queued chan struct{}
BeforeEach(func() {
queued = make(chan struct{}, 100)
queue = newDatagramQueue(func() {
queued <- struct{}{}
}, utils.DefaultLogger)
})
Context("sending", func() {
It("returns nil when there's no datagram to send", func() {
Expect(queue.Get()).To(BeNil())
})
It("queues a datagram", func() {
done := make(chan struct{})
go func() {
defer GinkgoRecover()
defer close(done)
Expect(queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foobar")})).To(Succeed())
}()
Eventually(queued).Should(HaveLen(1))
Consistently(done).ShouldNot(BeClosed())
f := queue.Get()
Expect(f).ToNot(BeNil())
Expect(f.Data).To(Equal([]byte("foobar")))
Eventually(done).Should(BeClosed())
Expect(queue.Get()).To(BeNil())
})
It("closes", func() {
errChan := make(chan error, 1)
go func() {
defer GinkgoRecover()
errChan <- queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foobar")})
}()
Consistently(errChan).ShouldNot(Receive())
queue.CloseWithError(errors.New("test error"))
Eventually(errChan).Should(Receive(MatchError("test error")))
})
})
Context("receiving", func() {
It("receives DATAGRAM frames", func() {
queue.HandleDatagramFrame(&wire.DatagramFrame{Data: []byte("foo")})
queue.HandleDatagramFrame(&wire.DatagramFrame{Data: []byte("bar")})
data, err := queue.Receive()
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal([]byte("foo")))
data, err = queue.Receive()
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal([]byte("bar")))
})
It("blocks until a frame is received", func() {
c := make(chan []byte, 1)
go func() {
defer GinkgoRecover()
data, err := queue.Receive()
Expect(err).ToNot(HaveOccurred())
c <- data
}()
Consistently(c).ShouldNot(Receive())
queue.HandleDatagramFrame(&wire.DatagramFrame{Data: []byte("foobar")})
Eventually(c).Should(Receive(Equal([]byte("foobar"))))
})
It("closes", func() {
errChan := make(chan error, 1)
go func() {
defer GinkgoRecover()
_, err := queue.Receive()
errChan <- err
}()
Consistently(errChan).ShouldNot(Receive())
queue.CloseWithError(errors.New("test error"))
Eventually(errChan).Should(Receive(MatchError("test error")))
})
})
})
quic-go-0.25.0/docs/ 0000775 0000000 0000000 00000000000 14171454516 0014102 5 ustar 00root root 0000000 0000000 quic-go-0.25.0/docs/quic.png 0000664 0000000 0000000 00000041623 14171454516 0015557 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR ^ ø =ûè\ CZIDATxìÖÌØPFáÙ¶mÛ¶mÛ¶mÛ¶Íh¶mÛ6¾ÙûmïIž¨nïmëE)¥”RJ)¥”RJ9.¿È…šè‚q‚æ(…¸pfJ)¥”RÊJc>Ãpí?§| ºa"Ö`?Öc&ú¡‚â?J)¥”R¥pæ¯ÐþñßGi0·aŽðkQ>áÌT@DG2dAaTB´GO´A=”GdD"Do¸eJ)¥TBl†ý(xìx·DYKY¿™eéÚÏÒ·éb‰«Ô²¨Ùs›þ~ZP‘ADÄE*¤C|„G ¸uá0æ§PHyG\”B,Ã9¼…¹À+Æ,´A>D€k¤”RJåÃ#Øg~C„´ÔMÚXù5›Þ±+¿UkßiË9p”…O•îÇo×[4„kéQ}°ûq7ñÌ ßÕÛ8ÝXŽî(ð
çÖ ` 0\„?«Yº°ãgZ©EkðÔ–£ßpKÕ¸•…IšÂ¼xõú«sÝ„(ø”J†®Ø‰g0wt«QááÔ”RJ©zx
ƒyóéË’Õj`5vŸ0~¬œ$gÿ‘æ7xˆ¿UmáÔ¼"!c2¶â6Ì<ÄfŒ@uDpä9„@ÌüE¬ø¼•ŽºU6î³ô;›Ÿ ïØ;è(’î‹7– $DÐww‚»/îîn«ÖqY÷Å}qwwwww[ÇÞ¿ÞœjþõõŽôÔtÍDÞïœûI¦3ЗªW÷ç>Ÿ¶L$cªÉoÆ
&ˆ%zÍÍßp>œê
‚ ‚è%¾K²FB›5ÛÑH«ë¾Ó±T9ã;j˜æš¦®|vçÄ2äïØüv½aj18w>h