Compare commits

...

264 commits

Author SHA1 Message Date
Mingye Chen
a157e2d9de Fix panic when using QUICRandomFrames 2025-01-17 19:06:48 -07:00
Mingye Chen
bbc1fe07d7 Add panic test 2025-01-17 19:06:48 -07:00
phoenix6936
98f5a5debc
Updates dependencies (#43)
* build(deps): bump the go_modules group across 2 directories with 1 update

Bumps the go_modules group with 1 update in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto).
Bumps the go_modules group with 1 update in the /integrationtests/gomodvendor directory: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.23.0 to 0.31.0
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.31.0)

Updates `golang.org/x/crypto` from 0.22.0 to 0.31.0
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  dependency-group: go_modules
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update dependabot.yml

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

* Update dependabot.yml

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

* build(deps): bump github.com/onsi/gomega from 1.33.1 to 1.36.1

Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.33.1 to 1.36.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.1...v1.36.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump github.com/refraction-networking/utls

Bumps [github.com/refraction-networking/utls](https://github.com/refraction-networking/utls) from 1.6.6 to 1.6.7.
- [Release notes](https://github.com/refraction-networking/utls/releases)
- [Commits](https://github.com/refraction-networking/utls/compare/v1.6.6...v1.6.7)

---
updated-dependencies:
- dependency-name: github.com/refraction-networking/utls
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump go.uber.org/mock from 0.4.0 to 0.5.0

Bumps [go.uber.org/mock](https://github.com/uber/mock) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/uber/mock/releases)
- [Changelog](https://github.com/uber-go/mock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uber/mock/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: go.uber.org/mock
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.22.0

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.17.2 to 2.22.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.22.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump golang.org/x/net from 0.25.0 to 0.32.0

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.32.0.
- [Commits](https://github.com/golang/net/compare/v0.25.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump github.com/quic-go/qpack from 0.4.0 to 0.5.1

Bumps [github.com/quic-go/qpack](https://github.com/quic-go/qpack) from 0.4.0 to 0.5.1.
- [Release notes](https://github.com/quic-go/qpack/releases)
- [Commits](https://github.com/quic-go/qpack/compare/v0.4.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/qpack
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump golang.org/x/time from 0.5.0 to 0.8.0

Bumps [golang.org/x/time](https://github.com/golang/time) from 0.5.0 to 0.8.0.
- [Commits](https://github.com/golang/time/compare/v0.5.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/time
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run go mod tidy, rm toolchain

* Downgrade back to 1.21

* build(deps): bump github.com/quic-go/qpack from 0.4.0 to 0.5.1

Bumps [github.com/quic-go/qpack](https://github.com/quic-go/qpack) from 0.4.0 to 0.5.1.
- [Release notes](https://github.com/quic-go/qpack/releases)
- [Commits](https://github.com/quic-go/qpack/compare/v0.4.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/qpack
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump go.uber.org/mock from 0.4.0 to 0.5.0

Bumps [go.uber.org/mock](https://github.com/uber/mock) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/uber/mock/releases)
- [Changelog](https://github.com/uber-go/mock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uber/mock/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: go.uber.org/mock
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.22.0

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.17.2 to 2.22.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.22.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump github.com/onsi/gomega from 1.33.1 to 1.36.1

Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.33.1 to 1.36.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.1...v1.36.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update go.mod

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

* Update go.sum

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

* Update go.sum

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

* Update go.sum

Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: phoenix6936 <189024923+phoenix6936@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mingye Chen <mingye.chen@colorado.edu>
2024-12-15 19:01:17 -07:00
Mingye Chen
cc7f02d9b9 Support non-zero lowest frame offset
The lowest offset of CRYPTO frames in a QUIC packet does not necessarily start at zero, such as the second packet of a connection using Kyber key in the client hello.
Also updates clienthellod to new repo.
2024-11-22 15:12:57 -07:00
Gaukas Wang
cb2c7f1296
deps: update to latest dependencies (#37)
Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-05-06 10:31:38 -06:00
Gaukas Wang
9178bdb6a5
fix: MaybePackProbePacket also use QUIC spec (#34)
Patch MaybePackProbePacket to also generate the initial packet based on the QUIC spec if set. This fixes the incorrect behavior observed on automatic retry on timeout (sending probe packet), where uquic was inccorectly sending the default frames (PADDING, CRYPTO) instead of specified frames by QUIC spec.

Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-05-02 19:09:00 -06:00
Gaukas Wang
164729a701
fix: cipherSuitesTLS13 linked to crypto/tls (#35)
This linkage issue caused the testing to fail, since we are modifying the available Cipher Suites from the TLS provider (crypto/tls -> utls) to manipulate the selection results.

Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-05-02 11:57:44 -06:00
dependabot[bot]
b24875057d
build(deps): bump actions/checkout from 3 to 4 (#28)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 00:40:27 -06:00
dependabot[bot]
8a76d80568
build(deps): bump actions/setup-go from 4 to 5 (#29)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 00:39:34 -06:00
dependabot[bot]
5e504393a9
build(deps): bump golang.org/x/net in /integrationtests/gomodvendor (#30)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 00:39:26 -06:00
dependabot[bot]
bd4a3dff02
build(deps): bump golang.org/x/crypto in /integrationtests/gomodvendor (#31)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.4.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.4.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 00:39:16 -06:00
Gaukas Wang
0018ce1ff4
sync: merge refraction-networking/sync-upstream-0-42-0 (#27)
sync: upstream quic-go v0.42.0

Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-04-23 22:33:05 -07:00
Gaukas Wang
05c12945b8
fix: build error after sync
Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-04-23 23:20:14 -06:00
Gaukas Wang
b66e1ed5f5
sync: 0.42.0 merge commit
Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-04-23 22:38:58 -06:00
Gaukas Wang
4973374ea5
sync: quic-go 0.42.0
Signed-off-by: Gaukas Wang <i@gaukas.wang>
2024-04-23 22:34:55 -06:00
dependabot[bot]
d40dde9b9b
build(deps): bump golang.org/x/net from 0.17.0 to 0.23.0 (#26) 2024-04-19 12:30:10 -06:00
dependabot[bot]
b5b59e42e9
build(deps): bump github.com/quic-go/quic-go from 0.39.4 to 0.42.0 (#25)
* build(deps): bump github.com/quic-go/quic-go from 0.39.4 to 0.42.0

Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.39.4 to 0.42.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.39.4...v0.42.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* bump: go minimum version 1.21

Signed-off-by: Gaukas Wang <i@gaukas.wang>

* update: github action scripts Go version

Signed-off-by: Gaukas Wang <i@gaukas.wang>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Gaukas Wang <i@gaukas.wang>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gaukas Wang <i@gaukas.wang>
2024-04-04 22:52:55 -06:00
Marten Seemann
4a99b816ae
close connection when an abnormally large number of frames are queued (#4369)
Under normal circumstances, we should be able to send out control frames
right away, so we don't expect any queue to build up. To defend against
resource exhaustion attacks, we limit the control frame queue to 16384
elements.
2024-03-17 17:29:00 -07:00
Marten Seemann
9971fedd42
use Transport.VerifySourceAddress to control the Retry Mechanism (#4362)
* use Transport.VerifySourceAddress to control the Retry Mechanism

This can be used to rate-limit handshakes originating from unverified
source addresses. Rate-limiting for handshakes can be implemented using
the GetConfigForClient callback on the Config.

* pass the remote address to Transport.VerifySourceAddress
2024-03-14 17:35:52 -07:00
Marten Seemann
497d3f58a5
http3: add a RoundTripOpt to check the server's SETTINGS frame (#4355)
For some requests, the client is required to check the server's HTTP/3
SETTINGS. For example, a client is only allowed to send HTTP/3 datagrams
if the server explicitly enabled support.

SETTINGS are sent asynchronously on a control stream (usually the first
unidirectional stream). This means that the SETTINGS might not be
available at the beginning of the connection. This is not expected to be
the common case, since the server can send the SETTINGS in 0.5-RTT data,
but we have to be able to deal with arbitrary delays.

For WebTransport, there are even more SETTINGS values that the client
needs to check. By making CheckSettings a callback on the RoundTripOpt,
this entire validation logic can live at the WebTransport layer.
2024-03-12 01:03:00 -07:00
Marten Seemann
ca787d6f00
add an AddrVerified field to the ClientHelloInfo (#4360)
* add an AddressVerified field to the ClientHelloInfo

* rename ClientHelloInfo.AddressVerififed to ClientHelloInfo.AddrVerififed
2024-03-11 05:00:25 -07:00
Marten Seemann
f1476390f2
update gomock to v0.4.0 (#4361) 2024-03-10 18:07:20 -07:00
Marten Seemann
06b421411d
remove unused ReceiveStream.CloseRemote method (#4357) 2024-03-09 02:29:43 -08:00
Marten Seemann
5fd5d7770d
Merge pull request #4305 from quic-go/qlog-tracer
add a qlog tracer for events outside of QUIC connections
2024-03-09 19:59:14 +09:30
Marten Seemann
30e01b9524 use the transport tracer in integration tests 2024-03-09 19:32:15 +09:30
Marten Seemann
55c05aceed qlog: log sent packets outside of a QUIC connection 2024-03-09 19:32:15 +09:30
Marten Seemann
aff90a6ffa qlog: log sent Version Negotiation packets 2024-03-09 19:32:15 +09:30
Marten Seemann
3a7a53fdb9 qlog: log packet drops outside of a QUIC connection 2024-03-09 19:32:15 +09:30
Marten Seemann
2abbd41806 qlog: introduce a basic tracer for non-connection events 2024-03-09 19:32:15 +09:30
the harder the luckier
ac1268911e
improve API documentation for OpenStreamSync (#4352)
* docs: improve API documentation for OpenStreamSync

Both `OpenStream` and `OpenStreamSync` themselves only create steam objects locally, but `OpenStreamSync` does not add document descriptions, which will cause ambiguity.

* additional description
2024-03-06 16:37:35 -08:00
Thijs van Dien
a70419b49f
unmap IPv4-mapped IPv6 addresses (#4309) 2024-03-05 01:45:53 -08:00
Marten Seemann
71f5ae5ecb
handshake: optimize AEAD handling for long header sealers and openers (#4323) 2024-03-03 04:33:10 -08:00
Marten Seemann
f856163f1e
handshake: embed the mask as an array into the aesHeaderProtector (#4324) 2024-03-03 04:32:32 -08:00
dependabot[bot]
067e7db750
ci: bump docker/setup-buildx-action from 2 to 3 (#4349)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 04:30:28 -08:00
dependabot[bot]
dbbb6ca736
ci: bump docker/setup-qemu-action from 2 to 3 (#4345)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 03:45:33 -08:00
dependabot[bot]
5a70d18c77
ci: bump docker/login-action from 2 to 3 (#4348)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 03:45:01 -08:00
dependabot[bot]
b21bd58281
ci: bump docker/build-push-action from 4 to 5 (#4347)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 03:37:06 -08:00
dependabot[bot]
60b4a9c630
ci: bump actions/upload-artifact from 3 to 4 (#4346)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 03:36:31 -08:00
Sukun
d6269b71af
fix deadlock when concurrently closing server and transport (#4332)
* server: fix deadlock when closing concurrently with transport

* add test for checking no deadlock
2024-03-03 03:00:28 -08:00
Marten Seemann
ba1fbbe964
ci: enable Dependabot for GitHub Actions (#4343) 2024-03-03 00:15:49 -08:00
Marten Seemann
d41c0b68cd
http3: reject duplicate control streams opened by the client (#4344) 2024-03-02 23:52:52 -08:00
Marten Seemann
c5f7096f00
http3: reject duplicate control streams opened by the server (#4342) 2024-03-02 23:28:24 -08:00
Marten Seemann
9813766373
http3: send SETTINGS_ENABLE_CONNECT_PROTOCOL (for Extended CONNECT) (#4341) 2024-03-02 23:15:59 -08:00
Marten Seemann
0405634108
http3: don't automatically set RoundTripper.QuicConfig.EnableDatagrams (#4340)
If the user provides a quic.Config, we shouldn't modify it. Instead, we
should return an error if the user enables HTTP Datagrams but fails to
enable datagrams on the QUIC layer.
2024-03-02 22:39:21 -08:00
Marten Seemann
c786a46f42
remove unused perspective arg from packetHandlerMap.ReplaceWithClosed (#4330) 2024-02-10 19:27:51 -08:00
Marten Seemann
7b8ceaa264
remove unused getPerspective function from quicConn interface (#4329) 2024-02-10 19:05:27 -08:00
Marten Seemann
69fe37885f
reenable previously disabled server unit test (#4328) 2024-02-10 18:44:43 -08:00
Marten Seemann
07a17ffffb
remove unused GetVersion function from quicConn interface (#4327) 2024-02-10 18:38:38 -08:00
Marten Seemann
0a7823c991
ci: update golangci-lint to v1.56.1 and golangci-lint action to v4 (#4326) 2024-02-10 17:46:27 -08:00
Marten Seemann
284996e13c
qtls: protect the tls.ClientSessionCache implementation with a mutex (#4319)
This prevents a race condition when the underlying ClientSessionCache
provided by the application returns the same session ticket for multiple
connections. Reusing session tickets is explicitly recommended against
by both RFC 8446 and RFC 9001, but it's not forbidden. This fix only
benefits applications that compromise their users' privacy by reusing
session tickets.
2024-02-09 07:47:28 -08:00
Marten Seemann
4790797b58
only check for stateless resets if packet doesn't belong to a connection (#4322)
This technically violates the stateless reset handling logic described
in RFC 9000 section 10.3.1 (see comment), but it saves one map lookup in
the hot path.
2024-02-09 00:15:58 -08:00
Marten Seemann
02e4506c3b
handshake: add benchmarks for the Initial AEAD (#4320) 2024-02-08 23:56:46 -08:00
Marten Seemann
f54a32ec28
don't preallocate the slice for STREAM frames when composing a packet (#4314)
The slice will be allocated when STREAM frames are appended. By not
preallocating it is made sure that the slice is only created in cases
where STREAM frames are actually sent in this packet.
2024-02-08 20:53:24 -08:00
Marten Seemann
229ff4fa4c
ci: update Codecov action to v4 (#4321) 2024-02-08 19:55:24 -08:00
Marten Seemann
8e93770dd3
avoid lock contention when accepting new connections (#4313)
* avoid lock contention when accepting new connections

The server used to hold the packet handler map's lock while creating the
connection struct for a newly accepted connection. This was intended to
make sure that no two connections with the same Destination Connection
ID could be created.

This is a corner case: it can only happen if two Initial packets with
the same Destination Connection ID are received at the same time. If
the second one is received after the first one has already been
processed, it would be routed to the first connection. We don't need to
optimized for this corner case. It's ok to create a new connection in
that case, and immediately close it if this collision is detected.

* only pass 0-RTT to the connection if it was actually accepted
2024-02-08 19:34:42 -08:00
Marten Seemann
013949cda3
ci: update to Go 1.22.0 (#4312) 2024-02-07 18:15:56 -08:00
bt90
43baf2db7a
README: add frp to list of projects (#4316) 2024-02-07 18:06:12 -08:00
Marten Seemann
ab17a5df6a
qlog: rename generation to key_phase on key_updated and key_discarded (#4315) 2024-02-07 18:04:23 -08:00
Marten Seemann
c22a3c8e6f
handshake: validate HKDF-Expand-Label against crypto/tls implementation (#4311)
* handshake: validate HKDF-Expand-Label against crypto/tls implementation

* handshake: add a benchmark for HKDF-Expand-Label
2024-02-04 21:27:21 -08:00
Marten Seemann
dc49f5673b
fix flaky 0-RTT packet loss recovery test (#4306) 2024-02-02 22:19:33 -08:00
Marten Seemann
198de32ef6
don't enqueue stream for sending on reordered MAX_STREAM_DATA frames (#4269) 2024-02-02 22:02:13 -08:00
Marten Seemann
07ec3245bd
logging: add a Close function to the Tracer (#4298)
* logging: add a Close function to the Tracer

* close the Tracer when the Transport is closed
2024-02-02 21:12:15 -08:00
Marten Seemann
b675e34254
logging: add a Debug function to the Tracer (#4297) 2024-02-02 20:21:27 -08:00
Marten Seemann
225d2a3926
qlog: disentangle the ConnectionTracer from the qlog writer (#4300)
The qlog writer simply records events, puts them into a channel, and
consumes these events in a separate Go routine (by serializing them).

The ConnectionTracer is the one generating those events.
2024-02-02 02:00:15 -08:00
Marten Seemann
0344401de5
qlog: rename qlog.go to connection_tracer.go (#4301) 2024-02-02 01:45:51 -08:00
Marten Seemann
f3c4be6b01
qlog: remove unneeded mutex from the ConnectionTracer (#4299)
Events are appended to a channel, which is able to handle concurrect writes.
2024-02-01 15:57:07 -08:00
Marten Seemann
2fbe713bb6
protocol: don't capitalize Perspective.String (#4296) 2024-02-01 15:16:36 -08:00
Marten Seemann
0582e931a5
wire: optimize generation of Version Negotiation packets (#4278)
* wire: optimize generation of Version Negotiation packets

* protocol: optimize adding greased version numbers
2024-01-31 23:54:04 -08:00
Marten Seemann
fbaa941ea1
protocol: rename VersionNumber to Version (#4295) 2024-01-31 21:57:33 -08:00
Marten Seemann
69ba7acb9f
ackhandler: don't delay ACKs for Initial and Handshake packets (#4288)
* ackhandler: don't delay ACKs for Initial and Handshake packets

* ackhandler: embed the receivedPacketHistory
2024-01-31 19:13:53 -08:00
Marten Seemann
72c79dbdf5
testutils: expose aliases for all frame types (#4293) 2024-01-30 21:47:03 -08:00
Marten Seemann
86441f1fdc
ci: make Codecov ignore testutils and testdata (#4292) 2024-01-30 21:22:40 -08:00
Marten Seemann
e65e99f1d6
ci: remove unused depguard check for qtls (#4291)
Fortunately, qtls is a thing of the past.
2024-01-30 19:30:11 -08:00
Marten Seemann
da25787a3d
testutils: make the package public (#4290)
This package can be useful outside of quic-go. We're not making any API
guarantees at this point.
2024-01-30 19:00:08 -08:00
Marten Seemann
d330d2e30d
remove unused RTTStats from the received packet handler (#4287) 2024-01-30 01:02:30 -08:00
Marten Seemann
be4838bd64
wire: remove FrameParser interface, expose FrameParser struct (#4284)
Instead, expose the FrameParser struct. This allows us to embed it
directly into the connection struct, avoiding a pointer indirection.
2024-01-28 22:34:28 -08:00
Marten Seemann
34c4d89e8b
fix flaky outgoing streams map test (#4283) 2024-01-28 21:52:32 -08:00
Marten Seemann
03ba124241
testutils: add a perspective function parameter to ComposeInitialPacket (#4276)
Currently not used, but this is useful when crafting Initial packets
sent from the client. No functional change expected.
2024-01-28 21:30:23 -08:00
Marten Seemann
940feef063
only log the discarding of Handshake keys once (#4274) 2024-01-28 20:51:22 -08:00
Marten Seemann
9b83ac230b
fix flaky handshake limiting test (#4281) 2024-01-28 20:26:03 -08:00
Marten Seemann
c82c37a31c
fix flaky accept queue test (#4280) 2024-01-28 20:14:02 -08:00
taoso
808f849ca2
http3: only use :protocol pseudo-header for Extended CONNECT (#4261)
* Fix protocol

The default value should be "HTTP/3.0".

* Reject normal request with :protocol header

The :protocol pseudo header is only defined for
Extended Connect requests (RFC 9220).

* save one branch check

* Fix review issue
2024-01-25 19:07:35 -08:00
Marten Seemann
d3974e1674
fix flaky handshake limiting test (#4270) 2024-01-25 00:53:20 -08:00
Marten Seemann
baeec0f41c
ci: update to Go 1.22rc2 (#4267) 2024-01-24 19:16:17 -08:00
Marten Seemann
2a7a11f4c0
remove unneeded nil check for new connections in the server (#4260) 2024-01-23 21:45:03 -08:00
Marten Seemann
a968e254a1
fix incorrect statement about connection ID lengths in the Transport (#4247) 2024-01-23 21:22:28 -08:00
Marten Seemann
a2cf43d75c
remove the RequireAddressValidation callback from the Config (#4253) 2024-01-22 21:24:07 -08:00
Marten Seemann
892851eb8c
add Transport config options to limit the number of handshakes (#4248)
* add Transport config options to limit the number of handshakes

* fix accounting for failed handshakes

* increase handshake limits, improve documentation
2024-01-22 21:04:25 -08:00
putyWang
bda5b7e6dc
handshake: fix documentation for updatableAEAD.SetWriteKey (#4256) 2024-01-22 19:30:50 -08:00
dependabot[bot]
4684e2dde1
build(deps): bump github.com/quic-go/quic-go from 0.39.2 to 0.39.4 (#23)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.39.2 to 0.39.4.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.39.2...v0.39.4)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 00:50:12 -07:00
Marten Seemann
4407c60f04
handshake: unexport Set{Read,Write}Key methods on the cryptoSetup (#4254)
No functional change expected.
These methods were exported since they were passed to the old qtls API.
2024-01-19 20:44:09 -08:00
Marten Seemann
594440b04c
don't remove closed connections from the server's accept queue (#4245) 2024-01-18 22:45:38 -08:00
Marten Seemann
cb1775a08a
send out the CONNECTION_REFUSED error when refusing a connection (#4250)
So far, we used Connection.destroy, which destroys a connection without
sending out a CONNECTION_CLOSE frame. This is useful (for example) when
receiving a stateless reset, but it's not what we want when the server
refuses an incoming connection. In this case, we want to send out a
packet with a CONNECTION_CLOSE frame to inform the client that the
connection attempt is being rejected.
2024-01-18 22:29:22 -08:00
Marten Seemann
b3eb375bc1
remove shutdown method on the Connection (#4249)
There's no need to have a dedicated shutdown method, as the use case
(shutting down an outgoing connection attempt on context cancellation)
can be achieved by using Connection.destroy.
2024-01-18 22:06:04 -08:00
Marten Seemann
d3c2020ecd
http3: add a basic README (#4246) 2024-01-16 19:34:10 -08:00
Marten Seemann
3449ace5a6
example: remove -qlog flag in favor of QLOGDIR (#4243) 2024-01-13 18:30:59 -08:00
Marten Seemann
13327521a5
example: remove -v flag and custom logger configuration (#4242)
Users can adjust the log level using the QUIC_GO_LOG_LEVEL environment
variable. This is more representative of how quic-go would actually be
used, since the logger is part of an internal package.
2024-01-13 01:59:58 -08:00
Benedikt Spies
2cd9ed38f1
qlog: add a default tracer that writes to QLOGDIR (#4233)
* add qlog default tracer which writes to QLOGDIR

* gofumpt

* add qlog default tracer which writes to QLOGDIR

* fix flaky tests

* Update README.md

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* Update README.md

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* Update README.md

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* Update README.md

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2024-01-12 03:11:53 -08:00
Marten Seemann
1e874896cd
wire: improve logging of connection ID retirements (#4241) 2024-01-10 21:53:25 -08:00
dependabot[bot]
fa047fddb7
build(deps): bump github.com/cloudflare/circl from 1.3.5 to 1.3.7 (#21)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.5...v1.3.7)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 10:06:36 -07:00
Marten Seemann
0a922b4e7d
example: add config flag for TLS key and cert for the server (#4237) 2024-01-05 02:21:42 -08:00
Robin Thellend
3ff50295ce
http3: add ConnContext to the server (#4230)
* Add ConnContext to http3.Server

ConnContext can be used to modify the context used by a new http
Request.

* Make linter happy

* Add nil check and integration test

* Add the ServerContextKey check to the ConnContext func

* Update integrationtests/self/http_test.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* Update http3/server.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2024-01-04 19:13:53 -08:00
Marten Seemann
f1b3bdbcb0
fix race condition when dropping Initial packet with short connection ID (#4236) 2024-01-04 18:59:39 -08:00
Marten Seemann
54d6f7dc51
ackhandler: refactor ACK queueing logic (#4225)
Once an ACK has been queued, there's no need to check futher conditions that
would lead to queueing of an ACK.
2024-01-03 18:39:09 -08:00
Marten Seemann
8cad3d2ea5
wire: use netip.AddrPort to encode the IPs in the Preferred Address (#4232) 2024-01-02 21:56:25 -08:00
Marten Seemann
59ed51704a
README: add RoadRunner to list of projects (#4226) 2024-01-02 21:32:58 -08:00
Marten Seemann
1083d1fb8f
handshake: remove unneeded mutex in cryptoSetup (#4227) 2024-01-01 23:52:08 -08:00
Marten Seemann
22b7f7744e
use a ring buffer for the datagram queue (#4223) 2023-12-31 20:50:26 -08:00
Marten Seemann
1fce81f8bb
queue up to 32 DATAGRAM frames to send (#4222) 2023-12-31 19:58:41 -08:00
Marten Seemann
7f080dd54b
discard DATAGRAM frames that don't fit into packets without an ACK (#4221)
If the packet doesn't contain an ACK (i.e. the payload is empty),
there's no point retrying to pack it later. It's extremely unlikely that
the available packet size will suddenly increase.
2023-12-31 19:17:25 -08:00
Marten Seemann
d6e3f3229f
qtls: remove unneeded type alias for the tls.QUICEncryptionLevel (#4220)
* qtls: remove unneeded type alias for the tls.QUICEncryptionLevel

* handshake: make cryptoSetup.WriteRecord private
2023-12-28 18:59:56 -08:00
Vladimir Varankin
a937959fbf
http3: fix channel size in ListenAndServe (#4219)
* http3: typo in ListenAndServe docs

Also, partially prevent a goroutine leak on an error from one of the
listeners.

* http3: improve documentation for ListenAndServe

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-12-28 18:35:46 -08:00
Marten Seemann
22411e16d5
utils: switch to standard library min and max functions (#4218)
These functions were added in Go 1.21.
2023-12-27 21:19:13 -08:00
Marten Seemann
18c591c75a
utils: use time.Duration.Abs (#4217)
This function was added in Go 1.19, and covers some corner cases that
our custom implementation didn't.
2023-12-27 20:49:47 -08:00
Marten Seemann
d795250479
drop support for Go 1.20, build on Go 1.22rc1 on CI (#4195)
* drop support for Go 1.20

* ci: udpate CircleCI to Go 1.21

* qtls: remove unnecessary type aliases

* ci: build using Go 1.22rc1
2023-12-27 20:31:58 -08:00
Benedikt Spies
31a677cacd
qlog: add support for alpn_information event (#4216)
* qlog chosen alpn

* qlog chosen alpn

* qlog: fix capitalization of ALPN

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-12-25 21:02:47 -08:00
Marten Seemann
2243fdefbf
http3: return the context cancellation error from RoundTrip (#4203) 2023-12-20 20:16:30 -08:00
dependabot[bot]
1c8dd085f9
build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#20)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 10:15:01 -07:00
Constantine Shablia
d3c5f389d4
http3: improve debug message when determining the listener port fails (#4214)
Fixes: #4212
2023-12-18 21:03:28 -08:00
Constantine Shablia
5d6bf7e206
http3: don't use error string as a format string in debug log message (#4211)
Fixes: #4209
2023-12-18 19:27:07 -08:00
WeidiDeng
e0bf13be01
http3: reset stream when a handler panics (#4181)
* interrupt the stream when a panick happened

* move the declaration of errPanicked

* check what's read is a prefix of what's written

* check errPanicked

* use MatchError instead of Equal

* use channel to notify the response has been received
2023-12-15 19:39:49 -08:00
Dominik Roos
06c6a8449b
http3: add remote address to request context (#4208)
* http3: add remote address to request context

Add the remote address of the underlying packet connection to the
HTTP request context. This is useful for applications that need access
to the actual remote address (wrapped in a net.Addr) rather than just
its string representation.

Fixes #4198

* add an integration test to the self test suite.

I was not sure how deep we want to go to assure the right value is set.
For now, it asserts that a net.Addr is present in the context.

Due to the dynamic nature of the requests, it is a bit harder to know
exactly how the remote address will look like. IPv4 vs IPv6, random high
port. I think it is fine to only assert that the value is present.
2023-12-15 19:29:41 -08:00
Marten Seemann
6ffb9054a2
fuzzing: add frame validation logic (#4206) 2023-12-13 23:09:02 -08:00
Marten Seemann
048940927c
ci: update golangci-lint to v1.55.2 (#4204) 2023-12-13 04:02:06 -08:00
Marten Seemann
ff6d575ee3
don't retransmit PATH_CHALLENGE and PATH_RESPONSE frames (#4200) 2023-12-12 20:22:13 -08:00
Marten Seemann
574dc84c0c
limit the number of queued PATH_RESPONSE frames to 256 (#4199) 2023-12-12 20:17:09 -08:00
Marten Seemann
a7a66f6437
integrationtests: remove leftover code for Go 1.19 (#4193) 2023-12-10 03:00:26 -08:00
Marten Seemann
659da8ca08
fuzzing: update Go version used on OSS-Fuzz to 1.21 (#4192) 2023-12-09 09:20:09 -08:00
fengyun.rui
f162b948db
examples: close listener, connection and stream in echo client and server (#4188)
* fix: add close func in example

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* fix: add close func in example

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* fix: add close func in example

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

---------

Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-12-09 09:05:31 -08:00
Marten Seemann
d234d62d52
qtls: only attempt 0-RTT resumption for 0-RTT enabled session tickets (#4183) 2023-12-09 06:17:47 -08:00
Charles Xu
38eafe4ad8
README: add gost project (#4154)
* chore: add project

* fix name

* revert unrelated whitespace fix

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-12-06 19:18:33 -08:00
Marten Seemann
9b40c50a73
http3: use the AdditionalSettings for requests (#4156) 2023-12-06 06:22:23 -08:00
Benedikt Spies
45922f76d6
reduce calls to time.Now() calls in connection (#4191) 2023-12-06 06:00:58 -08:00
Marten Seemann
87ef8ec48d
fuzzing: add transport parameter validation logic (#4175) 2023-12-03 05:53:36 -08:00
chungthuang
7b9d21fbe6
send large max_datagram_frame size, introduce a DatagramTooLargeError error (#4143)
The size can be overwritten to a lower value for testing.
2023-12-02 06:27:15 -08:00
Marten Seemann
2d7ea37672
wire: reject NEW_CONNECTION_ID frames with zero-length conneciton IDs (#4180) 2023-11-23 02:41:12 -08:00
Marten Seemann
771d136fa9
interop: update Go version to 1.21.4 (#4179) 2023-11-22 20:30:26 -08:00
Marten Seemann
3bf2e19d0d
logging: pass the packet number to ConnectionTracer.DroppedPacket (#4171)
In most cases the packet number is not known when a packet is dropped,
but it's useful to log the packet number when dropping a duplicate
packet.
2023-11-17 04:11:16 -08:00
Marten Seemann
96ab48eb7d
fix serialization of connection ID in filenames of qlog files (#4170) 2023-11-16 22:06:42 -08:00
Marten Seemann
427f53328b
fix flaky server test (#4167) 2023-11-14 02:00:42 -08:00
Anders Pitman
740119b144
README: fix typo (#4166) 2023-11-14 01:09:47 -08:00
Gaukas Wang
a14017fc59
fix: link to quic-go in README.md (#19)
Fix unintended changes that break links to the upstream.
2023-11-13 19:55:57 -07:00
Marten Seemann
9414ea4910
ackhandler: immediately acknowledge ECN-CE marked packets (#4147)
* ackhandler: immediately acknowledge ECN-CE marked packets

* shorter debug statements
2023-11-03 08:28:16 -07:00
Marten Seemann
d4ab27de1f
don't set the TLS version in the transport (#4135)
This is already done in the crypto setup.
2023-10-31 22:27:13 -07:00
Marten Seemann
f23da7da47
congestion: don't use floating point math when calculating pacing times (#4148) 2023-10-31 02:21:42 -07:00
Marten Seemann
a3603549ee
document what happens to established connections on Listener.Close (#4138) 2023-10-27 23:40:50 -07:00
Marten Seemann
6eb0caca1a
fix race condition in multiplex integration test (#4136) 2023-10-27 22:08:49 -07:00
Marten Seemann
5311f8178c
README: link to webtransport-go repo (#4117) 2023-10-27 02:21:26 -07:00
Marten Seemann
dda63b90eb
don't close established connections on Listener.Close, when using a Transport (#4072)
* don't close established connections on Listener.Close

* only close once
2023-10-26 23:10:13 -07:00
Gaukas Wang
e3be11b1e8
upstream: sync to quic-go v0.39.1
sync to upstream quic-go v0.39.1
2023-10-26 23:56:31 -06:00
Marten Seemann
ef800d6f71
handshake: set MinVersion on the Config returned by GetConfigForClient (#4134) 2023-10-26 22:35:07 -07:00
Gaukas Wang
92311cd441
fix: go 1.20 failing test 2023-10-26 23:26:51 -06:00
Marten Seemann
d309060cde
handshake: clone the tls.Config returned by GetConfigForClient (#4133)
We modify this tls.Config, so we should clone it first. Otherwise, this could
cause conflicts with how the application is using that config.
2023-10-26 22:22:20 -07:00
Gaukas Wang
77691ccce8
fix: error after sync 2023-10-26 22:48:59 -06:00
Gaukas Wang
7c77243b04
upstream: sync to 0.39.1 2023-10-26 22:47:22 -06:00
Marten Seemann
e2622bfad8
reject ClientHellos that offer TLS versions older than 1.3 (for Go 1.20) (#4130) 2023-10-25 09:49:53 -07:00
Marten Seemann
746290b78a
never allow 0-RTT when using Dial, even if the session ticket allows it (#4125)
When resuming a TLS session using Dial (and not DialEarly), 0-RTT should
be disabled at the TLS layer, even if the session ticket allows for
0-RTT resumption.

This bug is not critical, since Dial doesn't return an EarlyConnection,
so the client wouldn't be able to actually send 0-RTT data in practice.
2023-10-25 08:20:23 -07:00
Marten Seemann
1bcec70978
ci: run linter on all supported Go versions (#4126) 2023-10-24 22:03:41 -07:00
Marten Seemann
30f9c0139f
use typed atomics in integration tests (#4120)
* use typed atomic in integration tests

* use an atomic.Bool in hotswap test
2023-10-24 21:46:29 -07:00
Marten Seemann
6239effc7a
fix IPv4 ECN control message length on Linux (#4127) 2023-10-24 21:18:43 -07:00
Marten Seemann
1c631cf9cb
rename Connection.{Send,Receive}Message to {Send,Receive}Datagram (#4116)
This is more consistent with both the RFC and the rest of the API. For
example, the option in the Config is already name EnableDatagrams, and
the property in the ConnectionState is named SupportsDatagrams.
2023-10-24 21:18:09 -07:00
Marten Seemann
7884f87f82
ci: use bash on all platforms (#4122) 2023-10-22 23:36:45 -07:00
Marten Seemann
4c357c8f76
ci: create separate artifact archives per workflow run (#4121) 2023-10-22 22:53:10 -07:00
Marten Seemann
5314d90b9f
fix logging of connection IDs in tracer test (#4118) 2023-10-22 22:46:27 -07:00
Glonee
36f7fe7d07
http3: discard body from responses to HEAD requests (#4115)
* http3: HEAD method should not have a body

* add tests

* Update http3/server.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* ruduce the size of responseWriter

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-10-22 19:31:24 -07:00
Marten Seemann
a263164d9f
use new gomock feature to generate type-safe methods in mocks (#4057) 2023-10-20 22:55:33 -07:00
Marten Seemann
b344940f06
catch EPERM sendmsg errors for the very first packet on Linux (#4111) 2023-10-17 01:23:33 -07:00
Marten Seemann
2b290742f6 fix IPv4 ECN control message length on FreeBSD (#4110) 2023-10-17 12:51:24 +07:00
Marten Seemann
262cf0a592
fix IPv4 ECN control message length on FreeBSD (#4110) 2023-10-16 22:50:40 -07:00
Marten Seemann
f49944b737
README: add qlog to list of supported RFCs, add an example (#4102) 2023-10-13 00:05:47 -07:00
Gaukas Wang
6b99ab6f6e
Merge pull request #17 from refraction-networking/dependabot/go_modules/golang.org/x/net-0.17.0
build(deps): bump golang.org/x/net from 0.14.0 to 0.17.0
2023-10-11 17:07:49 -06:00
dependabot[bot]
1c92f0caab
build(deps): bump golang.org/x/net from 0.14.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-11 23:04:29 +00:00
Marten Seemann
49e588a6a9
don't spawn a new Go routine to send a Retry packet (#4092) 2023-09-25 04:21:51 -07:00
Marten Seemann
ae2ef95fa3
don't spawn a new Go routine to send a CONNECTION_REFUSED packet (#4091) 2023-09-25 09:31:27 +00:00
Marten Seemann
348042ee4c
simplify sending of INVALID_TOKEN errors (#4090) 2023-09-25 02:14:07 -07:00
Marten Seemann
9a397abc17
update gomock to v0.3.0 (#4087) 2023-09-24 04:38:28 -07:00
Toby
4bdff39ff0
README: add Hysteria (#4085)
* chore: add my project

* fix order
2023-09-24 04:01:03 -07:00
Marten Seemann
4a046185b7
ackhandler: fix ECN mangling detection when packets are lost (#4089)
Some of the 10 testing packets are might be lost, while others might be
CE-marked. We need to detect mangling if all testing packets are either
lost are CE-marked.
2023-09-17 22:08:33 -07:00
Marten Seemann
c12f425803
ackhandler: don't fail ECN validation if less than 10 testing packets are lost (#4088)
* ackhandler: don't fail ECN validation less than 10 testing packets lost

* ackhandler: simplify checks for mangling and loss of all testing packets
2023-09-17 23:00:05 +04:00
Marten Seemann
9010cfd2bb
remove unused unknownPacketHandler interface (#4093) 2023-09-17 05:20:13 -07:00
Marten Seemann
22fb59ee6f
create FUNDING.yml 2023-09-17 05:18:43 -07:00
Marten Seemann
55eebd49ff
return the cancellation cause for cancelled dials (#4078) 2023-09-16 05:37:58 -07:00
Marten Seemann
1affe38703
move MaxTokenAge configuration option to the Transport (#4084) 2023-09-16 05:10:20 -07:00
Marten Seemann
9b82196578
make the logging.Tracer and logging.ConnectionTracer a struct (#4082) 2023-09-16 04:58:51 -07:00
Marten Seemann
d8cc4cb3ef
http3: introduce an HTTP/3 error type (#4039)
* http3: introduce an HTTP/3 error type

* http3: use a pointer receiver for the Error
2023-09-16 04:57:50 -07:00
Marten Seemann
ab1c1be9a9
basic ClusterFuzzLite integration (#4034) 2023-09-15 09:57:13 -07:00
Marten Seemann
22eac50276
ci: combine the go generate workflow with the linting workflow (#4053)
* ci: combine the go generate workflow with the linting workflow

* reorder
2023-09-15 09:56:20 -07:00
Marten Seemann
5b25d8b5be
ci: fail if any Go files contain an ignore directive (#4055) 2023-09-15 18:35:53 +07:00
Marten Seemann
c1ce4a8e92
http09: increase the startup timeout in tests (#4071) 2023-09-15 18:35:09 +07:00
Marten Seemann
862e64c7b9
add a Transport config option for the key used to encrypt tokens (#4066)
* add a Transport config option for the key used to encrypt tokens

* handshake: remove unused error return values
2023-09-15 18:33:57 +07:00
Benedikt Spies
37a3c417a7
expose GSO usage through ConnectionState (#4083) 2023-09-13 23:55:49 -07:00
Marten Seemann
7599f81faf
ci: clean up Codecov ignore list (#4081) 2023-09-12 04:37:12 -07:00
Marten Seemann
d52e9f35bc
ackhandler: detect ECN mangling (#4080)
* ackhandler: detect ECN mangling

Mangling means that a path is re-marking all ECN-marked packets as CE.

* ackhandler: only detect ECN mangling once all testing packets were sent
2023-09-11 23:18:33 -07:00
Marten Seemann
2a8dc12a53
remove duplicate mocks for the Tracer and the ConnectionTracer (#4076)
They were introduced to avoid an import loop in the tests in the logging
package, but the same can be achieved by having a dedicated package for
the tests (logging_test).
2023-09-11 23:12:13 -07:00
Ameagari
d1f6ea997c
save the RTT in non-0-RTT session tickets (#4042)
* also send session ticket when 0-RTT is disabled for go1.21

* allow session ticket without transport parameters

* do not include transport parameters for non-0RTT session ticket

* remove the test assertion because it is not supported for go1.20

* Update internal/handshake/session_ticket.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* add a 0-RTT argument to unmarshaling session tickets

* bump sessionTicketRevision to 4

* check if non-0-RTT session ticket has expected length

* change parameter order

* add test checks

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-09-11 08:05:31 -07:00
Marten Seemann
1f25153884
Merge pull request #4059 from quic-go/ecn
add ECN support
2023-09-11 22:04:31 +07:00
Marten Seemann
d6ac6300a4 feed ECN feedback into the congestion controller 2023-09-11 21:17:31 +07:00
Marten Seemann
797e275293 congestion: rename OnPacketLost to OnCongestionEvent 2023-09-11 21:14:53 +07:00
Marten Seemann
b6ce91bfe7 stop appending to a GSO batch when the ECN marking changes 2023-09-11 21:14:53 +07:00
Marten Seemann
f9cfa248da implement ECN path validation logic, send ECN-marked 1-RTT packets 2023-09-11 21:14:52 +07:00
Marten Seemann
ffe6546833 add tracing and qlogging of state transitions for ECN validation 2023-09-11 20:31:50 +07:00
Marten Seemann
ad63e2a40a trace and qlog the ECN marking on sent and received packets 2023-09-11 20:31:50 +07:00
Marten Seemann
bed8ebbd4c distinguish coalesced and 1-RTT packets when determining ECN mode 2023-09-11 20:31:50 +07:00
Marten Seemann
8df7624c07 add a QUIC_GO_DISABLE_ECN env to disable ECN support 2023-09-11 20:31:50 +07:00
Marten Seemann
b73a4de7ea only add an ECN control message if ECN is supported 2023-09-11 20:31:50 +07:00
Marten Seemann
5dd6d91c11 send and track packets with ECN markings 2023-09-11 20:31:50 +07:00
Marten Seemann
f919473598 add support for writing the ECN control message (Linux, macOS) 2023-09-11 20:31:49 +07:00
Marten Seemann
a7f807856c
randomize the serialization order of control frames (#4069)
* randomize the serialization order of control frames

* add comment for packetPacker.appendPacketPayload
2023-09-10 21:49:29 -07:00
Marten Seemann
abfe1ef548
remove Config.MaxRetryTokenAge, set it to the handshake timeout (#4064)
There is no good reason to manually set the validity period for Retry
tokens. Retry tokens are only valid on a single connection during the
handshake, so it makes sense to limit their validity to the configured
handshake timeout.
2023-09-10 13:53:12 +07:00
Marten Seemann
e1fcac3e46
set the handshake timeout to twice the handshake idle timeout (#4063) 2023-09-09 06:12:37 -07:00
Marten Seemann
54b76ceb3e
ackhandler: use the receive time of the Retry packet for RTT estimation (#4070) 2023-09-09 06:12:19 -07:00
Marten Seemann
dc0369cad4
remove TLS post-handshake message reassembly logic (#4073)
Go 1.21.1 was released, which fixed the bug that made this workaround
necessary.
2023-09-06 21:27:03 -07:00
Marten Seemann
6cac231f6a
update qtls-go1-20 to v0.3.4 (#4068) 2023-09-06 09:02:33 -07:00
Marten Seemann
591d864e5e
ci: update GitHub checkout and setup-go actions to v4 (#4067) 2023-09-05 03:47:05 -07:00
Marten Seemann
96b1943cf5
ackhandler: rename variables to follow RFC 9002 terminology (#4062) 2023-09-03 21:45:41 -07:00
Ameagari
6cde43785f
integration tests: fix connection timeout in 0-RTT test (#4060) 2023-09-01 19:40:35 -07:00
Marten Seemann
090e505aa9
move GSO control message handling to the oobConn (#4056)
* move GSO control message handling to the oobConn

* disable OOB test on Windows

* improve GSO tests

* update ooConn.WritePacket comment
2023-08-31 00:49:27 -07:00
Marten Seemann
d7334c16e7
move the DisableVersionNegotiationPackets flag to the Transport (#4047)
* move the DisableVersionNegotiationPackets flag to the Transport

* add an integration test for DisableVersionNegotiationPackets
2023-08-30 23:33:40 -07:00
Gaukas Wang
66db44d7c8
sync: upstream quic-go v0.38.1
This is a merge commit.
2023-08-28 16:59:30 -06:00
Gaukas Wang
9b03bc282c
fix: uquic sync error
...so that uquic build/test shall pass and examples shall work again.
2023-08-28 16:53:19 -06:00
Gaukas Wang
856bc02b8f
Merge branch 'upstream' into sync-upstream 2023-08-28 14:12:03 -06:00
Gaukas Wang
3d09353796
Update README.md
fix the incorrect link to quic-go
2023-08-28 11:19:19 -06:00
Marten Seemann
2797f85fc0
switch from unmaintained golang/mock to go.uber.org/mock (#4050) 2023-08-28 02:23:55 -07:00
Marten Seemann
8f34488c76
fix flaky version negotiation connection unit test (#4052) 2023-08-26 21:53:07 -07:00
Marten Seemann
8963306987
ci: fix syntax error in integration test workflow (#4048) 2023-08-26 18:23:26 +07:00
Marten Seemann
e058f56643
ci: fix integration test running with and without GSO (#4043) 2023-08-24 18:23:44 -07:00
Marten Seemann
d22854641a
remove the port from the hostname used for tls.Config.ServerName (#4046) 2023-08-24 17:53:02 -07:00
Marten Seemann
f633dca488
update qtls to v0.3.3 (#4044) 2023-08-22 20:36:33 -07:00
Jean-Francois Giorgi
8d91ad9fcd
move QUIC_GO_DISABLE_GSO check out of init (#4041)
* move QUIC_GO_DISABLE_GSO test out of init().

* Update sys_conn_helper_linux.go

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-08-22 20:12:08 -07:00
WeidiDeng
824fd8a2f2
http3: automatically add content-length for small responses (#3989)
* response writer: add content-length automatically when response is small enough and doesn't call Flush

* fix comment

* add integration test

* Update http3/response_writer.go

* Update integrationtests/self/http_test.go

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-08-20 20:31:22 -07:00
Marten Seemann
ced65c0ddc
wire: always set the QUIC bit for Version Negotiation packets (#3991)
* wire: always set the QUIC bit for Version Negotiation packets

* Update internal/wire/version_negotiation_test.go
2023-08-20 19:55:57 -07:00
Marten Seemann
f689a5d023
ci: build interop Docker image for pushes to master, and for releases (#4035)
* ci: build interop Docker image for pushes to master, and tag releases

* use self-hosted runner to build Docker image

* Apply suggestions from code review

Co-authored-by: Piotr Galar <piotr.galar@gmail.com>

* Update .github/workflows/build-interop-docker.yml

Co-authored-by: Piotr Galar <piotr.galar@gmail.com>

* build the correct commit

* Update .github/workflows/build-interop-docker.yml

---------

Co-authored-by: Piotr Galar <piotr.galar@gmail.com>
2023-08-20 19:48:03 -07:00
Marten Seemann
fe3c4f271d
add a method to retrieve non-QUIC packets from the Transport (#3992) 2023-08-19 01:19:17 -07:00
Ameagari
6880f88089
save the max_datagram_frame_size transport parameter in the session ticket (#4013)
* Add MaxDatagramFrameSize parameter in session ticket

* fix gofumpt issues

* Update integrationtests/self/zero_rtt_test.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* fix: correct comparsion of max_datagram_frame_size

* test: use constant MaxDatagramFrameSize for session ticket test

* fix grammar

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
2023-08-18 19:16:16 -07:00
Marten Seemann
443c6148b6
protocol: add string representation for ECN values (#4008) 2023-08-18 17:17:37 -07:00
Marten Seemann
5c5db8cc59
reassemble post-handshake TLS messages before passing them to crypto/tls (#4038) 2023-08-18 17:16:57 -07:00
Marten Seemann
501cc21c4b
expose crypto/tls errors on the TransportError (#4015) 2023-08-18 03:01:49 -07:00
Marten Seemann
f7f4872bb9
Merge pull request #4005 from quic-go/gso-detection
enable GSO, disable if sending fails for a particular address
2023-08-18 13:01:23 +07:00
Marten Seemann
3822dae9bb
handshake fuzzer: fix setting of cipher suites (#4037) 2023-08-16 22:26:36 -07:00
Marten Seemann
5200f27dcf add QUIC_GO_DISABLE_GSO env to disable GSO 2023-08-16 22:09:30 +07:00
Marten Seemann
3a3169551b detect kernel GSO support 2023-08-16 22:09:30 +07:00
Marten Seemann
4122eb7a7d disable GSO if sending fails for a particular remote address 2023-08-16 22:09:29 +07:00
Marten Seemann
83c00a574d
ci: also run integration tests on Windows and macOS (#3987) 2023-08-16 07:21:48 -07:00
Marten Seemann
51d257d608
handshake fuzzer: fix TLS handshake sequence (#4033)
There were two problems with the existing code:
1. The transport parameters were rejected due to an invalid value for
   ActiveConnectionIDLimit, causing the handshake to fail.
2. Handshake messages were passed in at the wrong encryption level,
   leading to consistent handshake failures.
2023-08-16 06:55:44 -07:00
Marten Seemann
ca3842d6c8
automatically set the tls.Config.ServerName if unset (#4032) 2023-08-16 06:54:42 -07:00
Marten Seemann
4f696569a2
store the server port as an int, not a string, in HTTP tests (#3959) 2023-08-16 04:59:11 -07:00
Marten Seemann
2e7ea9119c
add OSS-Fuzz badge to README (#3942) 2023-08-16 04:58:07 -07:00
Gaukas Wang
2cf9a8b684
Update README.md 2023-08-15 23:00:48 -06:00
Gaukas Wang
d55cdaf54d
Add files via upload 2023-08-15 22:56:50 -06:00
Gaukas Wang
7cc5b8918f
new: uQUIC logo 2023-08-15 22:50:10 -06:00
Marten Seemann
bda01bc489
handshake: use the correct hash function for TLS_AES_256_GCM_SHA384 (#4031) 2023-08-15 20:09:01 -07:00
Marten Seemann
1d848392bc
ignore QUICConn.SendSessionTicket error if session tickets are disabled (#4030) 2023-08-15 19:53:41 -07:00
Marten Seemann
70f3f44a09
http3: remove leftover ALPN constant for draft-29 (#4027) 2023-08-13 19:36:01 -07:00
Egon Elbre
b65ed61fea
integrationtests: fix proxy test on Windows (#4023) 2023-08-13 01:43:28 -07:00
Gaukas Wang
6d525c0e31
fix: broken links to issues
some links are pointing to gaukas/uquic, which was once the root repository but moved here. Since gaukas/uquic is now a fork, URL redirects are no more and we need to manually update the links.
2023-08-11 22:29:00 -06:00
Gaukas Wang
edf00ef67a deps: bump up utls version
Bump up uTLS version to not rely on the retracted v1.4.1. Update go.mod/go.sum.
2023-08-11 12:17:12 -06:00
Ondrej Kokes
05db808f72
http3: change code point for HTTP datagrams to RFC 9297 (#3588)
* HTTP/3 Datagrams are now RFC 9297

* Use datatracker htmlized docs rather than rfc-editor (to be consistent)
2023-08-09 06:30:46 -07:00
Gokul PM
10d1114962
README: fix invocation of Go routine in example (#4019) 2023-08-09 05:26:39 -07:00
elagergren-spideroak
571d3adef4
fix compatibility with API breaking change in Go 1.21 (#4020)
* add Go 1.21 compatibility

Signed-off-by: Eric Lagergren <elagergren@spideroak-inc.com>

* refactor for Go 1.20

Signed-off-by: Eric Lagergren <elagergren@spideroak-inc.com>

---------

Signed-off-by: Eric Lagergren <elagergren@spideroak-inc.com>
2023-08-09 05:22:30 -07:00
Marten Seemann
aab4d4e410 fix handling of ACK frames serialized after CRYPTO frames (#4018) 2023-08-05 18:48:53 -04:00
Marten Seemann
95ab7bdc9a
add tls.ClientHelloInfo.Conn for recursive GetConfigForClient calls (#4016) 2023-08-05 13:00:11 -07:00
Marten Seemann
26c6fcc549
add error handling when confirming handshake on HANDSHAKE_DONE frames (#4017) 2023-08-05 07:02:24 -07:00
Marten Seemann
18d3846d4f
set a net.Conn for tls.ClientHelloInfo.Conn used by GetCertificate (#4014) 2023-08-03 20:33:19 -04:00
Marten Seemann
f9f6b9df6e
update qtls to restrict RSA keys in certificates to <= 8192 bits (#4012) 2023-08-03 08:20:32 -07:00
327 changed files with 18289 additions and 7926 deletions

View file

@ -0,0 +1,21 @@
FROM gcr.io/oss-fuzz-base/base-builder-go:v1
ARG TARGETPLATFORM
RUN echo "TARGETPLATFORM: ${TARGETPLATFORM}"
ENV GOVERSION=1.22.0
RUN platform=$(echo ${TARGETPLATFORM} | tr '/' '-') && \
filename="go${GOVERSION}.${platform}.tar.gz" && \
wget https://dl.google.com/go/${filename} && \
mkdir temp-go && \
rm -rf /root/.go/* && \
tar -C temp-go/ -xzf ${filename} && \
mv temp-go/go/* /root/.go/ && \
rm -r ${filename} temp-go
RUN apt-get update && apt-get install -y make autoconf automake libtool
COPY . $SRC/quic-go
WORKDIR quic-go
COPY .clusterfuzzlite/build.sh $SRC/

9
.clusterfuzzlite/build.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash -eu
export CXX="${CXX} -lresolv" # required by Go 1.20
compile_go_fuzzer github.com/refraction-networking/uquic/fuzzing/frames Fuzz frame_fuzzer
compile_go_fuzzer github.com/refraction-networking/uquic/fuzzing/header Fuzz header_fuzzer
compile_go_fuzzer github.com/refraction-networking/uquic/fuzzing/transportparameters Fuzz transportparameter_fuzzer
compile_go_fuzzer github.com/refraction-networking/uquic/fuzzing/tokens Fuzz token_fuzzer
compile_go_fuzzer github.com/refraction-networking/uquic/fuzzing/handshake Fuzz handshake_fuzzer

View file

@ -0,0 +1 @@
language: go

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [marten-seemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

15
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View file

@ -14,29 +14,45 @@ jobs:
fail-fast: false
matrix:
# os: [ "ubuntu-latest", "windows-latest", "macos-latest" ]
go: [ "1.20.x", "1.21.0-rc.4" ]
go: [ "1.21.x", "1.22.x" ]
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- run: go version
- name: Run tests
- name: Run unit tests
env:
TIMESCALE_FACTOR: 10
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -cover -randomize-all -randomize-suites -trace -skip-package integrationtests
# - name: Run tests (32 bit)
# - name: Run unit tests (32 bit)
# if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.
# env:
# TIMESCALE_FACTOR: 10
# GOARCH: 386
# run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -cover -coverprofile coverage.txt -output-dir . -randomize-all -randomize-suites -trace -skip-package integrationtests
# - name: Run tests with race detector
# - name: Run unit tests with race detector
# if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow
# env:
# TIMESCALE_FACTOR: 20
# run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -race -randomize-all -randomize-suites -trace -skip-package integrationtests
- name: Run other tests
run: |
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace -skip-package self,versionnegotiation integrationtests
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/versionnegotiation
- name: Run self tests, using QUIC v1
if: success() || failure() # run this step even if the previous one failed
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=1
- name: Run self tests, using QUIC v2
if: success() || failure() # run this step even if the previous one failed
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=2
- name: Run self tests, with GSO disabled
if: ${{ matrix.os == 'ubuntu' && (success() || failure()) }} # run this step even if the previous one failed
env:
QUIC_GO_DISABLE_GSO: true
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=1

View file

@ -3,7 +3,8 @@
name: "Go Build"
on: [push, pull_request]
# on: [push, pull_request]
on: push # no need to double-run on PR if we are running on all pushes already
jobs:
build:
@ -11,11 +12,11 @@ jobs:
fail-fast: false
matrix:
os: [ "ubuntu-latest", "windows-latest", "macos-latest" ]
go: [ "1.20.x", "1.21.0-rc.4" ]
go: [ "1.21.x", "1.22.x" ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- run: go version

View file

@ -2,16 +2,6 @@ run:
skip-files:
- internal/handshake/cipher_suite.go
linters-settings:
depguard:
type: blacklist
packages:
- github.com/marten-seemann/qtls
- github.com/quic-go/qtls-go1-19
- github.com/quic-go/qtls-go1-20
packages-with-error-message:
- github.com/marten-seemann/qtls: "importing qtls only allowed in internal/qtls"
- github.com/quic-go/qtls-go1-19: "importing qtls only allowed in internal/qtls"
- github.com/quic-go/qtls-go1-20: "importing qtls only allowed in internal/qtls"
misspell:
ignore-words:
- ect
@ -20,7 +10,6 @@ linters:
disable-all: true
enable:
- asciicheck
- depguard
- exhaustive
- exportloopref
- goimports

View file

@ -1,9 +1,12 @@
# ![uTLS](docs/utls_logo_small.png) <img src="docs/quic.png" alt="drawing" width="200"/> uQUIC
# <div> <img style="vertical-align:middle" src="docs/uQUIC_nopadding.png" alt="drawing" width="300"/> <span style="vertical-align:middle">uQUIC</span> </div>
[![Go Build Status](https://github.com/refraction-networking/uquic/actions/workflows/go_build.yml/badge.svg?branch=master)](https://github.com/refraction-networking/uquic/actions/workflows/go_build.yml)
[![Ginkgo Test Status](https://github.com/refraction-networking/uquic/actions/workflows/ginkgo_test.yml/badge.svg?branch=master)](https://github.com/refraction-networking/uquic/actions/workflows/ginkgo_test.yml)
[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/refraction-networking/uquic)
---
uQUIC is a fork of [quic-go](https://github.com/refraction-networking/uquic), which provides Initial Packet fingerprinting resistance and other features. While the handshake is still performed by quic-go, this library provides interface to customize the unencrypted Initial Packet which may reveal fingerprint-able information.
uQUIC is a fork of [quic-go](https://github.com/quic-go/quic-go), which provides Initial Packet fingerprinting resistance and other features. While the handshake is still performed by quic-go, this library provides interface to customize the unencrypted Initial Packet which may reveal fingerprint-able information.
Golang 1.20+ is required.
@ -12,43 +15,50 @@ If you have any questions, bug reports or contributions, you are welcome to publ
Development is still in progress and we welcome any contributions adding new features or fixing extant bugs.
# Disclaimer
This repository belongs to a large research project on how to fingerprint QUIC clients and how to mitigate such fingerprinting. We do not encourage any malicious use of this project's output, including this repository, [uTLS](https://github.com/refraction-networking/utls), and [clienthellod](https://github.com/gaukas/clienthellod).
Our research paper is still yet to be published and therefore this repository is neither ready for production use nor peer-reviewed. And the scope of our research is limited that such mimicry backed by this library MAY NOT be realisticly indistinguishable from the real QUIC clients being mimicked, and some misuses of this library MAY lead to easier fingerprinting against the mimic. We welcome any contributions to improve the realism of the mimicry, as well as expanding the scope of this project.
This repository belongs to a large research project on how to fingerprint QUIC clients and how to mitigate such fingerprinting. We do not encourage any malicious use of this project's output, including this repository, [uTLS](https://github.com/refraction-networking/utls), and [clienthellod](https://github.com/refraction-networking/clienthellod).
For anyone intending to use this library for censorship circumvention, please be sure to understand the risks and limitations of this library.
Our research paper is still yet to be published and therefore this repository is neither ready for production use nor peer-reviewed. And the scope of our research is limited that such mimicry backed by this library MAY NOT be realisticly indistinguishable from the real QUIC clients being mimicked, and some misuses of this library MAY lead to easier fingerprinting against the mimic. We welcome any contributions to improve the realism of the mimicry, as well as expanding the scope of this project.
For anyone intending to use this library for censorship circumvention, please be sure to understand the risks and limitations of this library.
If you are interested in our research, please stay tuned for our paper.
# Development in Progress
## Development Roadmap
- [ ] Customize Initial Packet
- [x] QUIC Header
- [x] QUIC Frame (~~[#3](https://github.com/gaukas/uquic/issues/3)~~)
- [x] QUIC Crypto Frame
- [x] QUIC Padding Frame
- [x] QUIC Ping Frame
- [ ] QUIC ACK Frame (on hold)
- [x] TLS ClientHello Message (by [uTLS](https://github.com/refraction-networking/utls))
- [x] QUIC Transport Parameters (in a uTLS extension)
- [ ] Customize Initial ACK behavior ([#1](https://github.com/gaukas/uquic/issues/1), [quic-go#4007](https://github.com/quic-go/quic-go/issues/4007))
- [ ] Customize Initial Retry behavior ([#2](https://github.com/gaukas/uquic/issues/2))
- [ ] Customize Initial Packet
- [x] QUIC Header
- [x] QUIC Frame (~~[#3](https://github.com/refraction-networking/uquic/issues/3)~~)
- [x] QUIC Crypto Frame
- [x] QUIC Padding Frame
- [x] QUIC Ping Frame
- [ ] QUIC ACK Frame (on hold)
- [x] TLS ClientHello Message (by [uTLS](https://github.com/refraction-networking/utls))
- [x] QUIC Transport Parameters (in a uTLS extension)
- [ ] Customize Initial ACK behavior ([#1](https://github.com/refraction-networking/uquic/issues/1), [quic-go#4007](https://github.com/quic-go/quic-go/issues/4007))
- [ ] Customize Initial Retry behavior ([#2](https://github.com/refraction-networking/uquic/issues/2))
- [ ] Add preset QUIC parrots
- [x] Google Chrome parrot (call for parrots w/ `Token/PSK`)
- [x] Mozilla Firefox parrot (call for parrots w/ `Token/PSK`)
- [ ] Apple Safari parrot
- [ ] Microsoft Edge parrot
- [x] Google Chrome parrot (call for parrots w/ `Token/PSK`)
- [x] Mozilla Firefox parrot (call for parrots w/ `Token/PSK`)
- [ ] Apple Safari parrot
- [ ] Microsoft Edge parrot
# Features
## Initial Packet fingerprinting resistance
uQUIC provides a mechanism to customize the Initial Packet, which is unencrypted and is almost unique to every QUIC client implementation. We provide an interface to customize the Initial Packet and makes the fingerprinting of QUIC clients harder.
### Build a QUIC Spec
A QUIC Spec sets parameters and policies for uQUIC in establishing a QUIC connection.
A QUIC Spec sets parameters and policies for uQUIC in establishing a QUIC connection.
See `u_parrot.go` for examples of building a QUIC Spec (parrot).
### Use a preset QUIC Spec
We provide a few preset QUIC Specs (parrots) for popular QUIC clients in `u_parrot.go`.
To use one, simple invoke `QUICID2Spec(id)`. See below for a complete example of using a preset QUIC Spec in an HTTP3 client.
@ -108,4 +118,4 @@ func main() {
}
fmt.Printf("Response Body: %s", body.Bytes())
}
```
```

View file

@ -29,13 +29,13 @@ type client struct {
initialPacketNumber protocol.PacketNumber
hasNegotiatedVersion bool
version protocol.VersionNumber
version protocol.Version
handshakeChan chan struct{}
conn quicConn
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
tracingID uint64
logger utils.Logger
}
@ -57,11 +57,11 @@ func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Confi
if err != nil {
return nil, err
}
dl, err := setupTransport(udpConn, tlsConf, true)
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
return dl.Dial(ctx, udpAddr, tlsConf, conf)
return tr.dial(ctx, udpAddr, addr, tlsConf, conf, false)
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
@ -75,13 +75,13 @@ func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *
if err != nil {
return nil, err
}
dl, err := setupTransport(udpConn, tlsConf, true)
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
conn, err := dl.DialEarly(ctx, udpAddr, tlsConf, conf)
conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, true)
if err != nil {
dl.Close()
tr.Close()
return nil, err
}
return conn, nil
@ -155,7 +155,7 @@ func dial(
if c.config.Tracer != nil {
c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
}
if c.tracer != nil {
if c.tracer != nil && c.tracer.StartedConnection != nil {
c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
}
@ -166,12 +166,6 @@ func dial(
}
func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
if tlsConf == nil {
tlsConf = &tls.Config{}
} else {
tlsConf = tlsConf.Clone()
}
srcConnID, err := connIDGenerator.GenerateConnectionID()
if err != nil {
return nil, err
@ -243,8 +237,8 @@ func (c *client) dial(ctx context.Context) error {
select {
case <-ctx.Done():
c.conn.shutdown()
return ctx.Err()
c.conn.destroy(nil)
return context.Cause(ctx)
case err := <-errorChan:
return err
case recreateErr := <-recreateChan:

View file

@ -13,10 +13,9 @@ import (
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/logging"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
type nullMultiplexer struct{}
@ -45,10 +44,10 @@ var _ = Describe("Client", func() {
initialPacketNumber protocol.PacketNumber,
enable0RTT bool,
hasNegotiatedVersion bool,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
v protocol.Version,
) quicConn
)
@ -56,12 +55,13 @@ var _ = Describe("Client", func() {
tlsConf = &tls.Config{NextProtos: []string{"proto1"}}
connID = protocol.ParseConnectionID([]byte{0, 0, 0, 0, 0, 0, 0x13, 0x37})
originalClientConnConstructor = newClientConnection
tracer = mocklogging.NewMockConnectionTracer(mockCtrl)
var tr *logging.ConnectionTracer
tr, tracer = mocklogging.NewMockConnectionTracer(mockCtrl)
config = &Config{
Tracer: func(ctx context.Context, perspective logging.Perspective, id ConnectionID) logging.ConnectionTracer {
return tracer
Tracer: func(ctx context.Context, perspective logging.Perspective, id ConnectionID) *logging.ConnectionTracer {
return tr
},
Versions: []protocol.VersionNumber{protocol.Version1},
Versions: []protocol.Version{protocol.Version1},
}
Eventually(areConnsRunning).Should(BeFalse())
packetConn = NewMockSendConn(mockCtrl)
@ -72,7 +72,7 @@ var _ = Describe("Client", func() {
destConnID: connID,
version: protocol.Version1,
sendConn: packetConn,
tracer: tracer,
tracer: tr,
logger: utils.DefaultLogger,
}
getMultiplexer() // make the sync.Once execute
@ -88,7 +88,7 @@ var _ = Describe("Client", func() {
AfterEach(func() {
if s, ok := cl.conn.(*connection); ok {
s.shutdown()
s.destroy(nil)
}
Eventually(areConnsRunning).Should(BeFalse())
})
@ -123,14 +123,14 @@ var _ = Describe("Client", func() {
_ protocol.PacketNumber,
enable0RTT bool,
_ bool,
_ logging.ConnectionTracer,
_ *logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
_ protocol.Version,
) quicConn {
Expect(enable0RTT).To(BeFalse())
conn := NewMockQUICConn(mockCtrl)
conn.EXPECT().run().Do(func() { close(run) })
conn.EXPECT().run().Do(func() error { close(run); return nil })
c := make(chan struct{})
close(c)
conn.EXPECT().HandshakeComplete().Return(c)
@ -160,14 +160,14 @@ var _ = Describe("Client", func() {
_ protocol.PacketNumber,
enable0RTT bool,
_ bool,
_ logging.ConnectionTracer,
_ *logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
_ protocol.Version,
) quicConn {
Expect(enable0RTT).To(BeTrue())
conn := NewMockQUICConn(mockCtrl)
conn.EXPECT().run().Do(func() { close(done) })
conn.EXPECT().run().Do(func() error { close(done); return nil })
conn.EXPECT().HandshakeComplete().Return(make(chan struct{}))
conn.EXPECT().earlyConnReady().Return(readyChan)
return conn
@ -197,10 +197,10 @@ var _ = Describe("Client", func() {
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ *logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
_ protocol.VersionNumber,
_ protocol.Version,
) quicConn {
conn := NewMockQUICConn(mockCtrl)
conn.EXPECT().run().Return(testErr)
@ -266,9 +266,9 @@ var _ = Describe("Client", func() {
})
It("creates new connections with the right parameters", func() {
config := &Config{Versions: []protocol.VersionNumber{protocol.Version1}}
config := &Config{Versions: []protocol.Version{protocol.Version1}}
c := make(chan struct{})
var version protocol.VersionNumber
var version protocol.Version
var conf *Config
done := make(chan struct{})
newClientConnection = func(
@ -282,10 +282,10 @@ var _ = Describe("Client", func() {
_ protocol.PacketNumber,
_ bool,
_ bool,
_ logging.ConnectionTracer,
_ *logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
versionP protocol.VersionNumber,
versionP protocol.Version,
) quicConn {
version = versionP
conf = configP
@ -325,10 +325,10 @@ var _ = Describe("Client", func() {
pn protocol.PacketNumber,
_ bool,
hasNegotiatedVersion bool,
_ logging.ConnectionTracer,
_ *logging.ConnectionTracer,
_ uint64,
_ utils.Logger,
versionP protocol.VersionNumber,
versionP protocol.Version,
) quicConn {
conn := NewMockQUICConn(mockCtrl)
conn.EXPECT().HandshakeComplete().Return(make(chan struct{}))
@ -352,7 +352,7 @@ var _ = Describe("Client", func() {
return conn
}
config := &Config{Tracer: config.Tracer, Versions: []protocol.VersionNumber{protocol.Version1}}
config := &Config{Tracer: config.Tracer, Versions: []protocol.Version{protocol.Version1}}
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
_, err := DialAddr(context.Background(), "localhost:7890", tlsConf, config)
Expect(err).ToNot(HaveOccurred())

View file

@ -4,7 +4,6 @@ import (
"math/bits"
"net"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/utils"
)
@ -12,9 +11,8 @@ import (
// When receiving packets for such a connection, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
// with an exponential backoff.
type closedLocalConn struct {
counter uint32
perspective protocol.Perspective
logger utils.Logger
counter uint32
logger utils.Logger
sendPacket func(net.Addr, packetInfo)
}
@ -22,11 +20,10 @@ type closedLocalConn struct {
var _ packetHandler = &closedLocalConn{}
// newClosedLocalConn creates a new closedLocalConn and runs it.
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), pers protocol.Perspective, logger utils.Logger) packetHandler {
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), logger utils.Logger) packetHandler {
return &closedLocalConn{
sendPacket: sendPacket,
perspective: pers,
logger: logger,
sendPacket: sendPacket,
logger: logger,
}
}
@ -41,24 +38,20 @@ func (c *closedLocalConn) handlePacket(p receivedPacket) {
c.sendPacket(p.remoteAddr, p.info)
}
func (c *closedLocalConn) shutdown() {}
func (c *closedLocalConn) destroy(error) {}
func (c *closedLocalConn) getPerspective() protocol.Perspective { return c.perspective }
func (c *closedLocalConn) destroy(error) {}
func (c *closedLocalConn) closeWithTransportError(TransportErrorCode) {}
// A closedRemoteConn is a connection that was closed remotely.
// For such a connection, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
// We can just ignore those packets.
type closedRemoteConn struct {
perspective protocol.Perspective
}
type closedRemoteConn struct{}
var _ packetHandler = &closedRemoteConn{}
func newClosedRemoteConn(pers protocol.Perspective) packetHandler {
return &closedRemoteConn{perspective: pers}
func newClosedRemoteConn() packetHandler {
return &closedRemoteConn{}
}
func (s *closedRemoteConn) handlePacket(receivedPacket) {}
func (s *closedRemoteConn) shutdown() {}
func (s *closedRemoteConn) destroy(error) {}
func (s *closedRemoteConn) getPerspective() protocol.Perspective { return s.perspective }
func (c *closedRemoteConn) handlePacket(receivedPacket) {}
func (c *closedRemoteConn) destroy(error) {}
func (c *closedRemoteConn) closeWithTransportError(TransportErrorCode) {}

View file

@ -3,7 +3,6 @@ package quic
import (
"net"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/utils"
. "github.com/onsi/ginkgo/v2"
@ -11,20 +10,9 @@ import (
)
var _ = Describe("Closed local connection", func() {
It("tells its perspective", func() {
conn := newClosedLocalConn(nil, protocol.PerspectiveClient, utils.DefaultLogger)
Expect(conn.getPerspective()).To(Equal(protocol.PerspectiveClient))
// stop the connection
conn.shutdown()
})
It("repeats the packet containing the CONNECTION_CLOSE frame", func() {
written := make(chan net.Addr, 1)
conn := newClosedLocalConn(
func(addr net.Addr, _ packetInfo) { written <- addr },
protocol.PerspectiveClient,
utils.DefaultLogger,
)
conn := newClosedLocalConn(func(addr net.Addr, _ packetInfo) { written <- addr }, utils.DefaultLogger)
addr := &net.UDPAddr{IP: net.IPv4(127, 1, 2, 3), Port: 1337}
for i := 1; i <= 20; i++ {
conn.handlePacket(receivedPacket{remoteAddr: addr})

View file

@ -1,19 +1,12 @@
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/handshake/cipher_suite.go
- internal/utils/byteinterval_linkedlist.go
- internal/utils/newconnectionid_linkedlist.go
- internal/utils/packetinterval_linkedlist.go
- internal/utils/linkedlist/linkedlist.go
- logging/null_tracer.go
- internal/testdata
- testutils/
- fuzzing/
- metrics/
status:

View file

@ -2,11 +2,9 @@ package quic
import (
"fmt"
"net"
"time"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/quicvarint"
)
@ -17,7 +15,11 @@ func (c *Config) Clone() *Config {
}
func (c *Config) handshakeTimeout() time.Duration {
return utils.Max(protocol.DefaultHandshakeTimeout, 2*c.HandshakeIdleTimeout)
return 2 * c.HandshakeIdleTimeout
}
func (c *Config) maxRetryTokenAge() time.Duration {
return c.handshakeTimeout()
}
func validateConfig(config *Config) error {
@ -46,22 +48,6 @@ func validateConfig(config *Config) error {
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.MaxTokenAge == 0 {
config.MaxTokenAge = protocol.TokenValidity
}
if config.MaxRetryTokenAge == 0 {
config.MaxRetryTokenAge = protocol.RetryTokenValidity
}
if config.RequireAddressValidation == nil {
config.RequireAddressValidation = func(net.Addr) bool { return false }
}
return config
}
// populateConfig populates fields in the quic.Config with their default values, if none are set
// it may be called with nil
func populateConfig(config *Config) *Config {
@ -110,27 +96,23 @@ func populateConfig(config *Config) *Config {
}
return &Config{
GetConfigForClient: config.GetConfigForClient,
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
MaxTokenAge: config.MaxTokenAge,
MaxRetryTokenAge: config.MaxRetryTokenAge,
RequireAddressValidation: config.RequireAddressValidation,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
Allow0RTT: config.Allow0RTT,
Tracer: config.Tracer,
GetConfigForClient: config.GetConfigForClient,
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
Allow0RTT: config.Allow0RTT,
Tracer: config.Tracer,
}
}

View file

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"net"
"reflect"
"time"
@ -23,7 +22,7 @@ var _ = Describe("Config", func() {
})
It("validates a config with normal values", func() {
conf := populateServerConfig(&Config{
conf := populateConfig(&Config{
MaxIncomingStreams: 5,
MaxStreamReceiveWindow: 10,
})
@ -69,7 +68,7 @@ var _ = Describe("Config", func() {
case "GetConfigForClient", "RequireAddressValidation", "GetLogWriter", "AllowConnectionWindowIncrease", "Tracer":
// Can't compare functions.
case "Versions":
f.Set(reflect.ValueOf([]VersionNumber{1, 2, 3}))
f.Set(reflect.ValueOf([]Version{1, 2, 3}))
case "ConnectionIDLength":
f.Set(reflect.ValueOf(8))
case "ConnectionIDGenerator":
@ -78,10 +77,6 @@ var _ = Describe("Config", func() {
f.Set(reflect.ValueOf(time.Second))
case "MaxIdleTimeout":
f.Set(reflect.ValueOf(time.Hour))
case "MaxTokenAge":
f.Set(reflect.ValueOf(2 * time.Hour))
case "MaxRetryTokenAge":
f.Set(reflect.ValueOf(2 * time.Minute))
case "TokenStore":
f.Set(reflect.ValueOf(NewLRUTokenStore(2, 3)))
case "InitialStreamReceiveWindow":
@ -115,31 +110,23 @@ var _ = Describe("Config", func() {
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() {
It("uses twice the handshake idle timeouts for the handshake timeout", 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 calledAddrValidation, calledAllowConnectionWindowIncrease, calledTracer bool
var calledAllowConnectionWindowIncrease, calledTracer bool
c1 := &Config{
GetConfigForClient: func(info *ClientHelloInfo) (*Config, error) { return nil, errors.New("nope") },
AllowConnectionWindowIncrease: func(Connection, uint64) bool { calledAllowConnectionWindowIncrease = true; return true },
RequireAddressValidation: func(net.Addr) bool { calledAddrValidation = true; return true },
Tracer: func(context.Context, logging.Perspective, ConnectionID) logging.ConnectionTracer {
Tracer: func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer {
calledTracer = true
return nil
},
}
c2 := c1.Clone()
c2.RequireAddressValidation(&net.UDPAddr{})
Expect(calledAddrValidation).To(BeTrue())
c2.AllowConnectionWindowIncrease(nil, 1234)
Expect(calledAllowConnectionWindowIncrease).To(BeTrue())
_, err := c2.GetConfigForClient(&ClientHelloInfo{})
@ -154,29 +141,15 @@ var _ = Describe("Config", func() {
})
It("returns a copy", func() {
c1 := &Config{
MaxIncomingStreams: 100,
RequireAddressValidation: func(net.Addr) bool { return true },
}
c1 := &Config{MaxIncomingStreams: 100}
c2 := c1.Clone()
c2.MaxIncomingStreams = 200
c2.RequireAddressValidation = func(net.Addr) bool { return false }
Expect(c1.MaxIncomingStreams).To(BeEquivalentTo(100))
Expect(c1.RequireAddressValidation(&net.UDPAddr{})).To(BeTrue())
})
})
Context("populating", func() {
It("populates function fields", func() {
var calledAddrValidation bool
c1 := &Config{}
c1.RequireAddressValidation = func(net.Addr) bool { calledAddrValidation = true; return true }
c2 := populateConfig(c1)
c2.RequireAddressValidation(&net.UDPAddr{})
Expect(calledAddrValidation).To(BeTrue())
})
It("copies non-function fields", func() {
c := configWithNonZeroNonFunctionFields()
Expect(populateConfig(c)).To(Equal(c))
@ -192,14 +165,8 @@ var _ = Describe("Config", func() {
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())
Expect(c.GetConfigForClient).To(BeNil())
})
It("populates empty fields with default values, for the server", func() {
c := populateServerConfig(&Config{})
Expect(c.RequireAddressValidation).ToNot(BeNil())
})
})
})

View file

@ -5,7 +5,6 @@ import (
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qerr"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/internal/wire"
)
@ -20,7 +19,7 @@ type connIDGenerator struct {
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken
removeConnectionID func(protocol.ConnectionID)
retireConnectionID func(protocol.ConnectionID)
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte)
replaceWithClosed func([]protocol.ConnectionID, []byte)
queueControlFrame func(wire.Frame)
}
@ -31,7 +30,7 @@ func newConnIDGenerator(
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken,
removeConnectionID func(protocol.ConnectionID),
retireConnectionID func(protocol.ConnectionID),
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte),
replaceWithClosed func([]protocol.ConnectionID, []byte),
queueControlFrame func(wire.Frame),
generator ConnectionIDGenerator,
) *connIDGenerator {
@ -60,7 +59,7 @@ func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
// 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.Min(limit, protocol.MaxIssuedConnectionIDs); i++ {
for i := uint64(len(m.activeSrcConnIDs)); i < min(limit, protocol.MaxIssuedConnectionIDs); i++ {
if err := m.issueNewConnID(); err != nil {
return err
}
@ -127,7 +126,7 @@ func (m *connIDGenerator) RemoveAll() {
}
}
func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose []byte) {
func (m *connIDGenerator) ReplaceWithClosed(connClose []byte) {
connIDs := make([]protocol.ConnectionID, 0, len(m.activeSrcConnIDs)+1)
if m.initialClientDestConnID != nil {
connIDs = append(connIDs, *m.initialClientDestConnID)
@ -135,5 +134,5 @@ func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose
for _, connID := range m.activeSrcConnIDs {
connIDs = append(connIDs, connID)
}
m.replaceWithClosed(connIDs, pers, connClose)
m.replaceWithClosed(connIDs, connClose)
}

View file

@ -41,9 +41,7 @@ var _ = Describe("Connection ID Generator", func() {
connIDToToken,
func(c protocol.ConnectionID) { removedConnIDs = append(removedConnIDs, c) },
func(c protocol.ConnectionID) { retiredConnIDs = append(retiredConnIDs, c) },
func(cs []protocol.ConnectionID, _ protocol.Perspective, _ []byte) {
replacedWithClosed = append(replacedWithClosed, cs...)
},
func(cs []protocol.ConnectionID, _ []byte) { replacedWithClosed = append(replacedWithClosed, cs...) },
func(f wire.Frame) { queuedFrames = append(queuedFrames, f) },
&protocol.DefaultConnectionIDGenerator{ConnLen: initialConnID.Len()},
)
@ -177,7 +175,7 @@ var _ = Describe("Connection ID Generator", func() {
It("replaces with a closed connection for all connection IDs", func() {
Expect(g.SetMaxActiveConnIDs(5)).To(Succeed())
Expect(queuedFrames).To(HaveLen(4))
g.ReplaceWithClosed(protocol.PerspectiveClient, []byte("foobar"))
g.ReplaceWithClosed([]byte("foobar"))
Expect(replacedWithClosed).To(HaveLen(6)) // initial conn ID, initial client dest conn id, and newly issued ones
Expect(replacedWithClosed).To(ContainElement(initialClientDestConnID))
Expect(replacedWithClosed).To(ContainElement(initialConnID))

View file

@ -155,7 +155,7 @@ func (h *connIDManager) updateConnectionID() {
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: h.activeSequenceNumber,
})
h.highestRetired = utils.Max(h.highestRetired, h.activeSequenceNumber)
h.highestRetired = max(h.highestRetired, h.activeSequenceNumber)
if h.activeStatelessResetToken != nil {
h.removeStatelessResetToken(*h.activeStatelessResetToken)
}

View file

@ -26,7 +26,7 @@ import (
)
type unpacker interface {
UnpackLongHeader(hdr *wire.Header, rcvTime time.Time, data []byte, v protocol.VersionNumber) (*unpackedPacket, error)
UnpackLongHeader(hdr *wire.Header, rcvTime time.Time, data []byte, v protocol.Version) (*unpackedPacket, error)
UnpackShortHeader(rcvTime time.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error)
}
@ -94,7 +94,7 @@ type connRunner interface {
GetStatelessResetToken(protocol.ConnectionID) protocol.StatelessResetToken
Retire(protocol.ConnectionID)
Remove(protocol.ConnectionID)
ReplaceWithClosed([]protocol.ConnectionID, protocol.Perspective, []byte)
ReplaceWithClosed([]protocol.ConnectionID, []byte)
AddResetToken(protocol.StatelessResetToken, packetHandler)
RemoveResetToken(protocol.StatelessResetToken)
}
@ -107,7 +107,7 @@ type closeError struct {
type errCloseForRecreating struct {
nextPacketNumber protocol.PacketNumber
nextVersion protocol.VersionNumber
nextVersion protocol.Version
}
func (e *errCloseForRecreating) Error() string {
@ -129,7 +129,7 @@ type connection struct {
srcConnIDLen int
perspective protocol.Perspective
version protocol.VersionNumber
version protocol.Version
config *Config
conn sendConn
@ -178,6 +178,7 @@ type connection struct {
earlyConnReadyChan chan struct{}
sentFirstPacket bool
droppedInitialKeys bool
handshakeComplete bool
handshakeConfirmed bool
@ -209,7 +210,7 @@ type connection struct {
connState ConnectionState
logID string
tracer logging.ConnectionTracer
tracer *logging.ConnectionTracer
logger utils.Logger
}
@ -233,10 +234,10 @@ var newConnection = func(
tlsConf *tls.Config,
tokenGenerator *handshake.TokenGenerator,
clientAddressValidated bool,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
v protocol.Version,
) quicConn {
s := &connection{
conn: conn,
@ -279,6 +280,7 @@ var newConnection = func(
getMaxPacketSize(s.conn.RemoteAddr()),
s.rttStats,
clientAddressValidated,
s.conn.capabilities().ECN,
s.perspective,
s.tracer,
s.logger,
@ -301,17 +303,17 @@ var newConnection = func(
// different from protocol.DefaultActiveConnectionIDLimit.
// If set to the default value, it will be omitted from the transport parameters, which will make
// old quic-go versions interpret it as 0, instead of the default value of 2.
// See https://github.com/quic-go/quic-go/pull/3806.
// See https://github.com/refraction-networking/uquic/pull/3806.
ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
InitialSourceConnectionID: srcConnID,
RetrySourceConnectionID: retrySrcConnID,
}
if s.config.EnableDatagrams {
params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
params.MaxDatagramFrameSize = wire.MaxDatagramSize
} else {
params.MaxDatagramFrameSize = protocol.InvalidByteCount
}
if s.tracer != nil {
if s.tracer != nil && s.tracer.SentTransportParameters != nil {
s.tracer.SentTransportParameters(params)
}
cs := handshake.NewCryptoSetupServer(
@ -345,10 +347,10 @@ var newClientConnection = func(
initialPacketNumber protocol.PacketNumber,
enable0RTT bool,
hasNegotiatedVersion bool,
tracer logging.ConnectionTracer,
tracer *logging.ConnectionTracer,
tracingID uint64,
logger utils.Logger,
v protocol.VersionNumber,
v protocol.Version,
) quicConn {
s := &connection{
conn: conn,
@ -387,7 +389,8 @@ var newClientConnection = func(
initialPacketNumber,
getMaxPacketSize(s.conn.RemoteAddr()),
s.rttStats,
false, /* has no effect */
false, // has no effect
s.conn.capabilities().ECN,
s.perspective,
s.tracer,
s.logger,
@ -395,7 +398,6 @@ var newClientConnection = func(
s.mtuDiscoverer = newMTUDiscoverer(s.rttStats, getMaxPacketSize(s.conn.RemoteAddr()), s.sentPacketHandler.SetMaxDatagramSize)
oneRTTStream := newCryptoStream()
params := &wire.TransportParameters{
InitialMaxStreamDataBidiRemote: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxStreamDataBidiLocal: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
@ -411,17 +413,16 @@ var newClientConnection = func(
// different from protocol.DefaultActiveConnectionIDLimit.
// If set to the default value, it will be omitted from the transport parameters, which will make
// old quic-go versions interpret it as 0, instead of the default value of 2.
// See https://github.com/quic-go/quic-go/pull/3806.
// See https://github.com/refraction-networking/uquic/pull/3806.
ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
InitialSourceConnectionID: srcConnID,
}
if s.config.EnableDatagrams {
params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
params.MaxDatagramFrameSize = wire.MaxDatagramSize
} else {
params.MaxDatagramFrameSize = protocol.InvalidByteCount
}
if s.tracer != nil {
if s.tracer != nil && s.tracer.SentTransportParameters != nil {
s.tracer.SentTransportParameters(params)
}
cs := handshake.NewCryptoSetupClient(
@ -457,7 +458,7 @@ func (s *connection) preSetup() {
s.handshakeStream = newCryptoStream()
s.sendQueue = newSendQueue(s.conn)
s.retransmissionQueue = newRetransmissionQueue()
s.frameParser = wire.NewFrameParser(s.config.EnableDatagrams)
s.frameParser = *wire.NewFrameParser(s.config.EnableDatagrams)
s.rttStats = &utils.RTTStats{}
s.connFlowController = flowcontrol.NewConnectionFlowController(
protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
@ -524,6 +525,9 @@ func (s *connection) run() error {
runLoop:
for {
if s.framer.QueuedTooManyControlFrames() {
s.closeLocal(&qerr.TransportError{ErrorCode: InternalError})
}
// Close immediately if requested
select {
case closeErr = <-s.closeChan:
@ -633,7 +637,7 @@ runLoop:
sendQueueAvailable = s.sendQueue.Available()
continue
}
if err := s.triggerSending(); err != nil {
if err := s.triggerSending(now); err != nil {
s.closeLocal(err)
}
if s.sendQueue.WouldBlock() {
@ -646,8 +650,10 @@ runLoop:
s.cryptoStreamHandler.Close()
s.sendQueue.Close() // close the send queue before sending the CONNECTION_CLOSE
s.handleCloseError(&closeErr)
if e := (&errCloseForRecreating{}); !errors.As(closeErr.err, &e) && s.tracer != nil {
s.tracer.Close()
if s.tracer != nil && s.tracer.Close != nil {
if e := (&errCloseForRecreating{}); !errors.As(closeErr.err, &e) {
s.tracer.Close()
}
}
s.logger.Infof("Connection %s closed.", s.logID)
s.timer.Stop()
@ -677,12 +683,13 @@ func (s *connection) ConnectionState() ConnectionState {
cs := s.cryptoStreamHandler.ConnectionState()
s.connState.TLS = cs.ConnectionState
s.connState.Used0RTT = cs.Used0RTT
s.connState.GSO = s.conn.capabilities().GSO
return s.connState
}
// Time when the connection should time out
func (s *connection) nextIdleTimeoutTime() time.Time {
idleTimeout := utils.Max(s.idleTimeout, s.rttStats.PTO(true)*3)
idleTimeout := max(s.idleTimeout, s.rttStats.PTO(true)*3)
return s.idleTimeoutStartTime().Add(idleTimeout)
}
@ -692,7 +699,7 @@ func (s *connection) nextKeepAliveTime() time.Time {
if s.config.KeepAlivePeriod == 0 || s.keepAlivePingSent || !s.firstAckElicitingPacketAfterIdleSentTime.IsZero() {
return time.Time{}
}
keepAliveInterval := utils.Max(s.keepAliveInterval, s.rttStats.PTO(true)*3/2)
keepAliveInterval := max(s.keepAliveInterval, s.rttStats.PTO(true)*3/2)
return s.lastPacketReceivedTime.Add(keepAliveInterval)
}
@ -732,6 +739,10 @@ func (s *connection) handleHandshakeComplete() error {
s.connIDManager.SetHandshakeComplete()
s.connIDGenerator.SetHandshakeComplete()
if s.tracer != nil && s.tracer.ChoseALPN != nil {
s.tracer.ChoseALPN(s.cryptoStreamHandler.ConnectionState().NegotiatedProtocol)
}
// The server applies transport parameters right away, but the client side has to wait for handshake completion.
// During a 0-RTT connection, the client is only allowed to use the new transport parameters for 1-RTT packets.
if s.perspective == protocol.PerspectiveClient {
@ -777,7 +788,7 @@ func (s *connection) handleHandshakeConfirmed() error {
if maxPacketSize == 0 {
maxPacketSize = protocol.MaxByteCount
}
s.mtuDiscoverer.Start(utils.Min(maxPacketSize, protocol.MaxPacketBufferSize))
s.mtuDiscoverer.Start(min(maxPacketSize, protocol.MaxPacketBufferSize))
}
return nil
}
@ -804,15 +815,15 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
var err error
destConnID, err = wire.ParseConnectionID(p.data, s.srcConnIDLen)
if err != nil {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), logging.PacketDropHeaderParseError)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropHeaderParseError)
}
s.logger.Debugf("error parsing packet, couldn't parse connection ID: %s", err)
break
}
if destConnID != lastConnID {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), logging.PacketDropUnknownConnectionID)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnknownConnectionID)
}
s.logger.Debugf("coalesced packet has different destination connection ID: %s, expected %s", destConnID, lastConnID)
break
@ -822,12 +833,12 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
if wire.IsLongHeaderPacket(p.data[0]) {
hdr, packetData, rest, err := wire.ParsePacket(p.data)
if err != nil {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedPacket != nil {
dropReason := logging.PacketDropHeaderParseError
if err == wire.ErrUnsupportedVersion {
dropReason = logging.PacketDropUnsupportedVersion
}
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), dropReason)
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), dropReason)
}
s.logger.Debugf("error parsing packet: %s", err)
break
@ -835,8 +846,8 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
lastConnID = hdr.DestConnectionID
if hdr.Version != s.version {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), protocol.ByteCount(len(data)), logging.PacketDropUnexpectedVersion)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedVersion)
}
s.logger.Debugf("Dropping packet with version %x. Expected %x.", hdr.Version, s.version)
break
@ -894,14 +905,14 @@ func (s *connection) handleShortHeaderPacket(p receivedPacket, destConnID protoc
if s.receivedPacketHandler.IsPotentiallyDuplicate(pn, protocol.Encryption1RTT) {
s.logger.Debugf("Dropping (potentially) duplicate packet.")
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketType1RTT, p.Size(), logging.PacketDropDuplicate)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketType1RTT, pn, p.Size(), logging.PacketDropDuplicate)
}
return false
}
var log func([]logging.Frame)
if s.tracer != nil {
if s.tracer != nil && s.tracer.ReceivedShortHeaderPacket != nil {
log = func(frames []logging.Frame) {
s.tracer.ReceivedShortHeaderPacket(
&logging.ShortHeader{
@ -911,6 +922,7 @@ func (s *connection) handleShortHeaderPacket(p receivedPacket, destConnID protoc
KeyPhase: keyPhase,
},
p.Size(),
p.ecn,
frames,
)
}
@ -933,22 +945,22 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
}()
if hdr.Type == protocol.PacketTypeRetry {
return s.handleRetryPacket(hdr, p.data)
return s.handleRetryPacket(hdr, p.data, p.rcvTime)
}
// The server can change the source connection ID with the first Handshake packet.
// After this, all packets with a different source connection have to be ignored.
if s.receivedFirstPacket && hdr.Type == protocol.PacketTypeInitial && hdr.SrcConnectionID != s.handshakeDestConnID {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeInitial, p.Size(), logging.PacketDropUnknownConnectionID)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeInitial, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnknownConnectionID)
}
s.logger.Debugf("Dropping Initial packet (%d bytes) with unexpected source connection ID: %s (expected %s)", p.Size(), hdr.SrcConnectionID, s.handshakeDestConnID)
return false
}
// drop 0-RTT packets, if we are a client
if s.perspective == protocol.PerspectiveClient && hdr.Type == protocol.PacketType0RTT {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketType0RTT, p.Size(), logging.PacketDropKeyUnavailable)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketType0RTT, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropKeyUnavailable)
}
return false
}
@ -964,10 +976,10 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
packet.hdr.Log(s.logger)
}
if s.receivedPacketHandler.IsPotentiallyDuplicate(packet.hdr.PacketNumber, packet.encryptionLevel) {
if pn := packet.hdr.PacketNumber; s.receivedPacketHandler.IsPotentiallyDuplicate(pn, packet.encryptionLevel) {
s.logger.Debugf("Dropping (potentially) duplicate packet.")
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), p.Size(), logging.PacketDropDuplicate)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), pn, p.Size(), logging.PacketDropDuplicate)
}
return false
}
@ -982,8 +994,8 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.PacketType) (wasQueued bool) {
switch err {
case handshake.ErrKeysDropped:
if s.tracer != nil {
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropKeyUnavailable)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropKeyUnavailable)
}
s.logger.Debugf("Dropping %s packet (%d bytes) because we already dropped the keys.", pt, p.Size())
case handshake.ErrKeysNotYetAvailable:
@ -998,16 +1010,16 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
})
case handshake.ErrDecryptionFailed:
// This might be a packet injected by an attacker. Drop it.
if s.tracer != nil {
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropPayloadDecryptError)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropPayloadDecryptError)
}
s.logger.Debugf("Dropping %s packet (%d bytes) that could not be unpacked. Error: %s", pt, p.Size(), err)
default:
var headerErr *headerParseError
if errors.As(err, &headerErr) {
// This might be a packet injected by an attacker. Drop it.
if s.tracer != nil {
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropHeaderParseError)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
}
s.logger.Debugf("Dropping %s packet (%d bytes) for which we couldn't unpack the header. Error: %s", pt, p.Size(), err)
} else {
@ -1019,25 +1031,25 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
return false
}
func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte) bool /* was this a valid Retry */ {
func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime time.Time) bool /* was this a valid Retry */ {
if s.perspective == protocol.PerspectiveServer {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
}
s.logger.Debugf("Ignoring Retry.")
return false
}
if s.receivedFirstPacket {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
}
s.logger.Debugf("Ignoring Retry, since we already received a packet.")
return false
}
destConnID := s.connIDManager.Get()
if hdr.SrcConnectionID == destConnID {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
}
s.logger.Debugf("Ignoring Retry, since the server didn't change the Source Connection ID.")
return false
@ -1051,8 +1063,8 @@ func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte) bool /* wa
tag := handshake.GetRetryIntegrityTag(data[:len(data)-16], destConnID, hdr.Version)
if !bytes.Equal(data[len(data)-16:], tag[:]) {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropPayloadDecryptError)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropPayloadDecryptError)
}
s.logger.Debugf("Ignoring spoofed Retry. Integrity Tag doesn't match.")
return false
@ -1063,12 +1075,12 @@ func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte) bool /* wa
(&wire.ExtendedHeader{Header: *hdr}).Log(s.logger)
s.logger.Debugf("Switching destination connection ID to: %s", hdr.SrcConnectionID)
}
if s.tracer != nil {
if s.tracer != nil && s.tracer.ReceivedRetry != nil {
s.tracer.ReceivedRetry(hdr)
}
newDestConnID := hdr.SrcConnectionID
s.receivedRetry = true
if err := s.sentPacketHandler.ResetForRetry(); err != nil {
if err := s.sentPacketHandler.ResetForRetry(rcvTime); err != nil {
s.closeLocal(err)
return false
}
@ -1084,16 +1096,16 @@ func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte) bool /* wa
func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
if s.perspective == protocol.PerspectiveServer || // servers never receive version negotiation packets
s.receivedFirstPacket || s.versionNegotiated { // ignore delayed / duplicated version negotiation packets
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedPacket)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket)
}
return
}
src, dest, supportedVersions, err := wire.ParseVersionNegotiationPacket(p.data)
if err != nil {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropHeaderParseError)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
}
s.logger.Debugf("Error parsing Version Negotiation packet: %s", err)
return
@ -1101,8 +1113,8 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
for _, v := range supportedVersions {
if v == s.version {
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedVersion)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedVersion)
}
// The Version Negotiation packet contains the version that we offered.
// This might be a packet sent by an attacker, or it was corrupted.
@ -1111,7 +1123,7 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
}
s.logger.Infof("Received a Version Negotiation packet. Supported Versions: %s", supportedVersions)
if s.tracer != nil {
if s.tracer != nil && s.tracer.ReceivedVersionNegotiationPacket != nil {
s.tracer.ReceivedVersionNegotiationPacket(dest, src, supportedVersions)
}
newVersion, ok := protocol.ChooseSupportedVersion(s.config.Versions, supportedVersions)
@ -1123,7 +1135,7 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
s.logger.Infof("No compatible QUIC version found.")
return
}
if s.tracer != nil {
if s.tracer != nil && s.tracer.NegotiatedVersion != nil {
s.tracer.NegotiatedVersion(newVersion, s.config.Versions, supportedVersions)
}
@ -1143,8 +1155,8 @@ func (s *connection) handleUnpackedLongHeaderPacket(
) error {
if !s.receivedFirstPacket {
s.receivedFirstPacket = true
if !s.versionNegotiated && s.tracer != nil {
var clientVersions, serverVersions []protocol.VersionNumber
if !s.versionNegotiated && s.tracer != nil && s.tracer.NegotiatedVersion != nil {
var clientVersions, serverVersions []protocol.Version
switch s.perspective {
case protocol.PerspectiveClient:
clientVersions = s.config.Versions
@ -1170,7 +1182,7 @@ func (s *connection) handleUnpackedLongHeaderPacket(
s.handshakeDestConnID = packet.hdr.SrcConnectionID
s.connIDManager.ChangeInitialConnID(packet.hdr.SrcConnectionID)
}
if s.tracer != nil {
if s.tracer != nil && s.tracer.StartedConnection != nil {
s.tracer.StartedConnection(
s.conn.LocalAddr(),
s.conn.RemoteAddr(),
@ -1181,7 +1193,8 @@ func (s *connection) handleUnpackedLongHeaderPacket(
}
}
if s.perspective == protocol.PerspectiveServer && packet.encryptionLevel == protocol.EncryptionHandshake {
if s.perspective == protocol.PerspectiveServer && packet.encryptionLevel == protocol.EncryptionHandshake &&
!s.droppedInitialKeys {
// On the server side, Initial keys are dropped as soon as the first Handshake packet is received.
// See Section 4.9.1 of RFC 9001.
if err := s.dropEncryptionLevel(protocol.EncryptionInitial); err != nil {
@ -1194,9 +1207,9 @@ func (s *connection) handleUnpackedLongHeaderPacket(
s.keepAlivePingSent = false
var log func([]logging.Frame)
if s.tracer != nil {
if s.tracer != nil && s.tracer.ReceivedLongHeaderPacket != nil {
log = func(frames []logging.Frame) {
s.tracer.ReceivedLongHeaderPacket(packet.hdr, packetSize, frames)
s.tracer.ReceivedLongHeaderPacket(packet.hdr, packetSize, ecn, frames)
}
}
isAckEliciting, err := s.handleFrames(packet.data, packet.hdr.DestConnectionID, packet.encryptionLevel, log)
@ -1342,8 +1355,8 @@ func (s *connection) handlePacket(p receivedPacket) {
select {
case s.receivedPackets <- p:
default:
if s.tracer != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention)
}
}
}
@ -1522,7 +1535,7 @@ func (s *connection) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encr
}
func (s *connection) handleDatagramFrame(f *wire.DatagramFrame) error {
if f.Length(s.version) > protocol.MaxDatagramFrameSize {
if f.Length(s.version) > wire.MaxDatagramSize {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "DATAGRAM frame too large",
@ -1568,13 +1581,6 @@ func (s *connection) closeRemote(e error) {
})
}
// Close the connection. It sends a NO_ERROR application error.
// It waits until the run loop has stopped before returning
func (s *connection) shutdown() {
s.closeLocal(nil)
<-s.ctx.Done()
}
func (s *connection) CloseWithError(code ApplicationErrorCode, desc string) error {
s.closeLocal(&qerr.ApplicationError{
ErrorCode: code,
@ -1584,6 +1590,11 @@ func (s *connection) CloseWithError(code ApplicationErrorCode, desc string) erro
return nil
}
func (s *connection) closeWithTransportError(code TransportErrorCode) {
s.closeLocal(&qerr.TransportError{ErrorCode: code})
<-s.ctx.Done()
}
func (s *connection) handleCloseError(closeErr *closeError) {
e := closeErr.err
if e == nil {
@ -1622,13 +1633,13 @@ func (s *connection) handleCloseError(closeErr *closeError) {
s.datagramQueue.CloseWithError(e)
}
if s.tracer != nil && !errors.As(e, &recreateErr) {
if s.tracer != nil && s.tracer.ClosedConnection != nil && !errors.As(e, &recreateErr) {
s.tracer.ClosedConnection(e)
}
// If this is a remote close we're done here
if closeErr.remote {
s.connIDGenerator.ReplaceWithClosed(s.perspective, nil)
s.connIDGenerator.ReplaceWithClosed(nil)
return
}
if closeErr.immediate {
@ -1645,11 +1656,11 @@ func (s *connection) handleCloseError(closeErr *closeError) {
if err != nil {
s.logger.Debugf("Error sending CONNECTION_CLOSE: %s", err)
}
s.connIDGenerator.ReplaceWithClosed(s.perspective, connClosePacket)
s.connIDGenerator.ReplaceWithClosed(connClosePacket)
}
func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel) error {
if s.tracer != nil {
if s.tracer != nil && s.tracer.DroppedEncryptionLevel != nil {
s.tracer.DroppedEncryptionLevel(encLevel)
}
s.sentPacketHandler.DropPackets(encLevel)
@ -1657,6 +1668,7 @@ func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel) erro
//nolint:exhaustive // only Initial and 0-RTT need special treatment
switch encLevel {
case protocol.EncryptionInitial:
s.droppedInitialKeys = true
s.cryptoStreamHandler.DiscardInitialKeys()
case protocol.Encryption0RTT:
s.streamsMap.ResetFor0RTT()
@ -1684,7 +1696,7 @@ func (s *connection) restoreTransportParameters(params *wire.TransportParameters
}
func (s *connection) handleTransportParameters(params *wire.TransportParameters) error {
if s.tracer != nil {
if s.tracer != nil && s.tracer.ReceivedTransportParameters != nil {
s.tracer.ReceivedTransportParameters(params)
}
if err := s.checkTransportParameters(params); err != nil {
@ -1751,7 +1763,7 @@ func (s *connection) applyTransportParameters() {
params := s.peerParams
// Our local idle timeout will always be > 0.
s.idleTimeout = utils.MinNonZeroDuration(s.config.MaxIdleTimeout, params.MaxIdleTimeout)
s.keepAliveInterval = utils.Min(s.config.KeepAlivePeriod, utils.Min(s.idleTimeout/2, protocol.MaxKeepAliveInterval))
s.keepAliveInterval = min(s.config.KeepAlivePeriod, min(s.idleTimeout/2, protocol.MaxKeepAliveInterval))
s.streamsMap.UpdateLimits(params)
s.frameParser.SetAckDelayExponent(params.AckDelayExponent)
s.connFlowController.UpdateSendWindow(params.InitialMaxData)
@ -1767,9 +1779,8 @@ func (s *connection) applyTransportParameters() {
}
}
func (s *connection) triggerSending() error {
func (s *connection) triggerSending(now time.Time) error {
s.pacingDeadline = time.Time{}
now := time.Now()
sendMode := s.sentPacketHandler.SendMode(now)
//nolint:exhaustive // No need to handle pacing limited here.
@ -1801,7 +1812,7 @@ func (s *connection) triggerSending() error {
s.scheduleSending()
return nil
}
return s.triggerSending()
return s.triggerSending(now)
case ackhandler.SendPTOHandshake:
if err := s.sendProbePacket(protocol.EncryptionHandshake, now); err != nil {
return err
@ -1810,7 +1821,7 @@ func (s *connection) triggerSending() error {
s.scheduleSending()
return nil
}
return s.triggerSending()
return s.triggerSending(now)
case ackhandler.SendPTOAppData:
if err := s.sendProbePacket(protocol.Encryption1RTT, now); err != nil {
return err
@ -1819,7 +1830,7 @@ func (s *connection) triggerSending() error {
s.scheduleSending()
return nil
}
return s.triggerSending()
return s.triggerSending(now)
default:
return fmt.Errorf("BUG: invalid send mode %d", sendMode)
}
@ -1836,9 +1847,10 @@ func (s *connection) sendPackets(now time.Time) error {
if err != nil {
return err
}
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, buf.Len(), false)
s.registerPackedShortHeaderPacket(p, now)
s.sendQueue.Send(buf, buf.Len())
ecn := s.sentPacketHandler.ECNMode(true)
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, buf.Len(), false)
s.registerPackedShortHeaderPacket(p, ecn, now)
s.sendQueue.Send(buf, 0, ecn)
// This is kind of a hack. We need to trigger sending again somehow.
s.pacingDeadline = deadlineSendImmediately
return nil
@ -1858,7 +1870,7 @@ func (s *connection) sendPackets(now time.Time) error {
return err
}
s.sentFirstPacket = true
if err := s.sendPackedCoalescedPacket(packet, now); err != nil {
if err := s.sendPackedCoalescedPacket(packet, s.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket()), now); err != nil {
return err
}
sendMode := s.sentPacketHandler.SendMode(now)
@ -1879,7 +1891,8 @@ func (s *connection) sendPackets(now time.Time) error {
func (s *connection) sendPacketsWithoutGSO(now time.Time) error {
for {
buf := getPacketBuffer()
if _, err := s.appendPacket(buf, s.mtuDiscoverer.CurrentSize(), now); err != nil {
ecn := s.sentPacketHandler.ECNMode(true)
if _, err := s.appendOneShortHeaderPacket(buf, s.mtuDiscoverer.CurrentSize(), ecn, now); err != nil {
if err == errNothingToPack {
buf.Release()
return nil
@ -1887,7 +1900,7 @@ func (s *connection) sendPacketsWithoutGSO(now time.Time) error {
return err
}
s.sendQueue.Send(buf, buf.Len())
s.sendQueue.Send(buf, 0, ecn)
if s.sendQueue.WouldBlock() {
return nil
@ -1912,9 +1925,10 @@ func (s *connection) sendPacketsWithGSO(now time.Time) error {
buf := getLargePacketBuffer()
maxSize := s.mtuDiscoverer.CurrentSize()
ecn := s.sentPacketHandler.ECNMode(true)
for {
var dontSendMore bool
size, err := s.appendPacket(buf, maxSize, now)
size, err := s.appendOneShortHeaderPacket(buf, maxSize, ecn, now)
if err != nil {
if err != errNothingToPack {
return err
@ -1936,15 +1950,19 @@ func (s *connection) sendPacketsWithGSO(now time.Time) error {
}
}
// Don't send more packets in this batch if they require a different ECN marking than the previous ones.
nextECN := s.sentPacketHandler.ECNMode(true)
// Append another packet if
// 1. The congestion controller and pacer allow sending more
// 2. The last packet appended was a full-size packet
// 3. We still have enough space for another full-size packet in the buffer
if !dontSendMore && size == maxSize && buf.Len()+maxSize <= buf.Cap() {
// 3. The next packet will have the same ECN marking
// 4. We still have enough space for another full-size packet in the buffer
if !dontSendMore && size == maxSize && nextECN == ecn && buf.Len()+maxSize <= buf.Cap() {
continue
}
s.sendQueue.Send(buf, maxSize)
s.sendQueue.Send(buf, uint16(maxSize), ecn)
if dontSendMore {
return nil
@ -1973,6 +1991,7 @@ func (s *connection) resetPacingDeadline() {
func (s *connection) maybeSendAckOnlyPacket(now time.Time) error {
if !s.handshakeConfirmed {
ecn := s.sentPacketHandler.ECNMode(false)
packet, err := s.packer.PackCoalescedPacket(true, s.mtuDiscoverer.CurrentSize(), s.version)
if err != nil {
return err
@ -1980,9 +1999,10 @@ func (s *connection) maybeSendAckOnlyPacket(now time.Time) error {
if packet == nil {
return nil
}
return s.sendPackedCoalescedPacket(packet, time.Now())
return s.sendPackedCoalescedPacket(packet, ecn, now)
}
ecn := s.sentPacketHandler.ECNMode(true)
p, buf, err := s.packer.PackAckOnlyPacket(s.mtuDiscoverer.CurrentSize(), s.version)
if err != nil {
if err == errNothingToPack {
@ -1990,9 +2010,9 @@ func (s *connection) maybeSendAckOnlyPacket(now time.Time) error {
}
return err
}
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, buf.Len(), false)
s.registerPackedShortHeaderPacket(p, now)
s.sendQueue.Send(buf, buf.Len())
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, buf.Len(), false)
s.registerPackedShortHeaderPacket(p, ecn, now)
s.sendQueue.Send(buf, 0, ecn)
return nil
}
@ -2024,24 +2044,24 @@ func (s *connection) sendProbePacket(encLevel protocol.EncryptionLevel, now time
if packet == nil || (len(packet.longHdrPackets) == 0 && packet.shortHdrPacket == nil) {
return fmt.Errorf("connection BUG: couldn't pack %s probe packet", encLevel)
}
return s.sendPackedCoalescedPacket(packet, now)
return s.sendPackedCoalescedPacket(packet, s.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket()), now)
}
// appendPacket appends a new packet to the given packetBuffer.
// appendOneShortHeaderPacket appends a new packet to the given packetBuffer.
// If there was nothing to pack, the returned size is 0.
func (s *connection) appendPacket(buf *packetBuffer, maxSize protocol.ByteCount, now time.Time) (protocol.ByteCount, error) {
func (s *connection) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.ByteCount, ecn protocol.ECN, now time.Time) (protocol.ByteCount, error) {
startLen := buf.Len()
p, err := s.packer.AppendPacket(buf, maxSize, s.version)
if err != nil {
return 0, err
}
size := buf.Len() - startLen
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, size, false)
s.registerPackedShortHeaderPacket(p, now)
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, size, false)
s.registerPackedShortHeaderPacket(p, ecn, now)
return size, nil
}
func (s *connection) registerPackedShortHeaderPacket(p shortHeaderPacket, now time.Time) {
func (s *connection) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, now time.Time) {
if s.firstAckElicitingPacketAfterIdleSentTime.IsZero() && (len(p.StreamFrames) > 0 || ackhandler.HasAckElicitingFrames(p.Frames)) {
s.firstAckElicitingPacketAfterIdleSentTime = now
}
@ -2050,12 +2070,12 @@ func (s *connection) registerPackedShortHeaderPacket(p shortHeaderPacket, now ti
if p.Ack != nil {
largestAcked = p.Ack.LargestAcked()
}
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, p.Length, p.IsPathMTUProbePacket)
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket)
s.connIDManager.SentPacket()
}
func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, now time.Time) error {
s.logCoalescedPacket(packet)
func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN, now time.Time) error {
s.logCoalescedPacket(packet, ecn)
for _, p := range packet.longHdrPackets {
if s.firstAckElicitingPacketAfterIdleSentTime.IsZero() && p.IsAckEliciting() {
s.firstAckElicitingPacketAfterIdleSentTime = now
@ -2064,8 +2084,9 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, now time
if p.ack != nil {
largestAcked = p.ack.LargestAcked()
}
s.sentPacketHandler.SentPacket(now, p.header.PacketNumber, largestAcked, p.streamFrames, p.frames, p.EncryptionLevel(), p.length, false)
if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake {
s.sentPacketHandler.SentPacket(now, p.header.PacketNumber, largestAcked, p.streamFrames, p.frames, p.EncryptionLevel(), ecn, p.length, false)
if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake &&
!s.droppedInitialKeys {
// On the client side, Initial keys are dropped as soon as the first Handshake packet is sent.
// See Section 4.9.1 of RFC 9001.
if err := s.dropEncryptionLevel(protocol.EncryptionInitial); err != nil {
@ -2081,11 +2102,10 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, now time
if p.Ack != nil {
largestAcked = p.Ack.LargestAcked()
}
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, p.Length, p.IsPathMTUProbePacket)
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket)
}
s.connIDManager.SentPacket()
s.sendQueue.Send(packet.buffer, packet.buffer.Len())
s.sendQueue.Send(packet.buffer, 0, ecn)
return nil
}
@ -2107,11 +2127,12 @@ func (s *connection) sendConnectionClose(e error) ([]byte, error) {
if err != nil {
return nil, err
}
s.logCoalescedPacket(packet)
return packet.buffer.Data, s.conn.Write(packet.buffer.Data, packet.buffer.Len())
ecn := s.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket())
s.logCoalescedPacket(packet, ecn)
return packet.buffer.Data, s.conn.Write(packet.buffer.Data, 0, ecn)
}
func (s *connection) logLongHeaderPacket(p *longHeaderPacket) {
func (s *connection) logLongHeaderPacket(p *longHeaderPacket, ecn protocol.ECN) {
// quic-go logging
if s.logger.Debug() {
p.header.Log(s.logger)
@ -2127,7 +2148,7 @@ func (s *connection) logLongHeaderPacket(p *longHeaderPacket) {
}
// tracing
if s.tracer != nil {
if s.tracer != nil && s.tracer.SentLongHeaderPacket != nil {
frames := make([]logging.Frame, 0, len(p.frames))
for _, f := range p.frames {
frames = append(frames, logutils.ConvertFrame(f.Frame))
@ -2139,7 +2160,7 @@ func (s *connection) logLongHeaderPacket(p *longHeaderPacket) {
if p.ack != nil {
ack = logutils.ConvertAckFrame(p.ack)
}
s.tracer.SentLongHeaderPacket(p.header, p.length, ack, frames)
s.tracer.SentLongHeaderPacket(p.header, p.length, ecn, ack, frames)
}
}
@ -2151,11 +2172,12 @@ func (s *connection) logShortHeaderPacket(
pn protocol.PacketNumber,
pnLen protocol.PacketNumberLen,
kp protocol.KeyPhaseBit,
ecn protocol.ECN,
size protocol.ByteCount,
isCoalesced bool,
) {
if s.logger.Debug() && !isCoalesced {
s.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, 1-RTT", pn, size, s.logID)
s.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, 1-RTT (ECN: %s)", pn, size, s.logID, ecn)
}
// quic-go logging
if s.logger.Debug() {
@ -2172,7 +2194,7 @@ func (s *connection) logShortHeaderPacket(
}
// tracing
if s.tracer != nil {
if s.tracer != nil && s.tracer.SentShortHeaderPacket != nil {
fs := make([]logging.Frame, 0, len(frames)+len(streamFrames))
for _, f := range frames {
fs = append(fs, logutils.ConvertFrame(f.Frame))
@ -2192,13 +2214,14 @@ func (s *connection) logShortHeaderPacket(
KeyPhase: kp,
},
size,
ecn,
ack,
fs,
)
}
}
func (s *connection) logCoalescedPacket(packet *coalescedPacket) {
func (s *connection) logCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN) {
if s.logger.Debug() {
// There's a short period between dropping both Initial and Handshake keys and completion of the handshake,
// during which we might call PackCoalescedPacket but just pack a short header packet.
@ -2211,6 +2234,7 @@ func (s *connection) logCoalescedPacket(packet *coalescedPacket) {
packet.shortHdrPacket.PacketNumber,
packet.shortHdrPacket.PacketNumberLen,
packet.shortHdrPacket.KeyPhase,
ecn,
packet.shortHdrPacket.Length,
false,
)
@ -2223,10 +2247,10 @@ func (s *connection) logCoalescedPacket(packet *coalescedPacket) {
}
}
for _, p := range packet.longHdrPackets {
s.logLongHeaderPacket(p)
s.logLongHeaderPacket(p, ecn)
}
if p := packet.shortHdrPacket; p != nil {
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, p.Length, true)
s.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, p.Length, true)
}
}
@ -2292,14 +2316,14 @@ func (s *connection) tryQueueingUndecryptablePacket(p receivedPacket, pt logging
panic("shouldn't queue undecryptable packets after handshake completion")
}
if len(s.undecryptablePackets)+1 > protocol.MaxUndecryptablePackets {
if s.tracer != nil {
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropDOSPrevention)
if s.tracer != nil && s.tracer.DroppedPacket != nil {
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention)
}
s.logger.Infof("Dropping undecryptable packet (%d bytes). Undecryptable packet queue full.", p.Size())
return
}
s.logger.Infof("Queueing packet (%d bytes) for later decryption", p.Size())
if s.tracer != nil {
if s.tracer != nil && s.tracer.BufferedPacket != nil {
s.tracer.BufferedPacket(pt, p.Size())
}
s.undecryptablePackets = append(s.undecryptablePackets, p)
@ -2331,21 +2355,23 @@ func (s *connection) onStreamCompleted(id protocol.StreamID) {
}
}
func (s *connection) SendMessage(p []byte) error {
func (s *connection) SendDatagram(p []byte) error {
if !s.supportsDatagrams() {
return errors.New("datagram support disabled")
}
f := &wire.DatagramFrame{DataLenPresent: true}
if protocol.ByteCount(len(p)) > f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version) {
return errors.New("message too large")
return &DatagramTooLargeError{
PeerMaxDatagramFrameSize: int64(s.peerParams.MaxDatagramFrameSize),
}
}
f.Data = make([]byte, len(p))
copy(f.Data, p)
return s.datagramQueue.AddAndWait(f)
return s.datagramQueue.Add(f)
}
func (s *connection) ReceiveMessage(ctx context.Context) ([]byte, error) {
func (s *connection) ReceiveDatagram(ctx context.Context) ([]byte, error) {
if !s.config.EnableDatagrams {
return nil, errors.New("datagram support disabled")
}
@ -2360,11 +2386,7 @@ func (s *connection) RemoteAddr() net.Addr {
return s.conn.RemoteAddr()
}
func (s *connection) getPerspective() protocol.Perspective {
return s.perspective
}
func (s *connection) GetVersion() protocol.VersionNumber {
func (s *connection) GetVersion() protocol.Version {
return s.version
}

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,6 @@ import (
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qerr"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/internal/wire"
)
@ -56,7 +55,7 @@ func (s *cryptoStreamImpl) HandleCryptoFrame(f *wire.CryptoFrame) error {
// could e.g. be a retransmission
return nil
}
s.highestOffset = utils.Max(s.highestOffset, highestOffset)
s.highestOffset = max(s.highestOffset, highestOffset)
if err := s.queue.Push(f.Data, f.Offset, nil); err != nil {
return err
}
@ -99,7 +98,7 @@ func (s *cryptoStreamImpl) HasData() bool {
func (s *cryptoStreamImpl) PopCryptoFrame(maxLen protocol.ByteCount) *wire.CryptoFrame {
f := &wire.CryptoFrame{Offset: s.writeOffset}
n := utils.Min(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
n := min(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
f.Data = s.writeBuf[:n]
s.writeBuf = s.writeBuf[n:]
s.writeOffset += n

View file

@ -4,14 +4,20 @@ import (
"context"
"sync"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/internal/utils/ringbuffer"
"github.com/refraction-networking/uquic/internal/wire"
)
const (
maxDatagramSendQueueLen = 32
maxDatagramRcvQueueLen = 128
)
type datagramQueue struct {
sendQueue chan *wire.DatagramFrame
nextFrame *wire.DatagramFrame
sendMx sync.Mutex
sendQueue ringbuffer.RingBuffer[*wire.DatagramFrame]
sent chan struct{} // used to notify Add that a datagram was dequeued
rcvMx sync.Mutex
rcvQueue [][]byte
@ -22,60 +28,65 @@ type datagramQueue 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),
rcvd: make(chan struct{}, 1),
dequeued: make(chan struct{}),
closed: make(chan struct{}),
logger: logger,
hasData: hasData,
rcvd: make(chan struct{}, 1),
sent: make(chan struct{}, 1),
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
}
// Add queues a new DATAGRAM frame for sending.
// Up to 32 DATAGRAM frames will be queued.
// Once that limit is reached, Add blocks until the queue size has reduced.
func (h *datagramQueue) Add(f *wire.DatagramFrame) error {
h.sendMx.Lock()
select {
case <-h.dequeued:
return nil
case <-h.closed:
return h.closeErr
for {
if h.sendQueue.Len() < maxDatagramSendQueueLen {
h.sendQueue.PushBack(f)
h.sendMx.Unlock()
h.hasData()
return nil
}
select {
case <-h.sent: // drain the queue so we don't loop immediately
default:
}
h.sendMx.Unlock()
select {
case <-h.closed:
return h.closeErr
case <-h.sent:
}
h.sendMx.Lock()
}
}
// Peek gets the next DATAGRAM frame for sending.
// If actually sent out, Pop needs to be called before the next call to Peek.
func (h *datagramQueue) Peek() *wire.DatagramFrame {
if h.nextFrame != nil {
return h.nextFrame
}
select {
case h.nextFrame = <-h.sendQueue:
h.dequeued <- struct{}{}
default:
h.sendMx.Lock()
defer h.sendMx.Unlock()
if h.sendQueue.Empty() {
return nil
}
return h.nextFrame
return h.sendQueue.PeekFront()
}
func (h *datagramQueue) Pop() {
if h.nextFrame == nil {
panic("datagramQueue BUG: Pop called for nil frame")
h.sendMx.Lock()
defer h.sendMx.Unlock()
_ = h.sendQueue.PopFront()
select {
case h.sent <- struct{}{}:
default:
}
h.nextFrame = nil
}
// HandleDatagramFrame handles a received DATAGRAM frame.
@ -84,7 +95,7 @@ func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
copy(data, f.Data)
var queued bool
h.rcvMx.Lock()
if len(h.rcvQueue) < protocol.DatagramRcvQueueLen {
if len(h.rcvQueue) < maxDatagramRcvQueueLen {
h.rcvQueue = append(h.rcvQueue, data)
queued = true
select {
@ -94,7 +105,7 @@ func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
}
h.rcvMx.Unlock()
if !queued && h.logger.Debug() {
h.logger.Debugf("Discarding DATAGRAM frame (%d bytes payload)", len(f.Data))
h.logger.Debugf("Discarding received DATAGRAM frame (%d bytes payload)", len(f.Data))
}
}

View file

@ -3,6 +3,7 @@ package quic
import (
"context"
"errors"
"time"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/internal/wire"
@ -26,55 +27,65 @@ var _ = Describe("Datagram Queue", func() {
})
It("queues a datagram", func() {
done := make(chan struct{})
frame := &wire.DatagramFrame{Data: []byte("foobar")}
go func() {
defer GinkgoRecover()
defer close(done)
Expect(queue.AddAndWait(frame)).To(Succeed())
}()
Eventually(queued).Should(HaveLen(1))
Consistently(done).ShouldNot(BeClosed())
Expect(queue.Add(frame)).To(Succeed())
Expect(queued).To(HaveLen(1))
f := queue.Peek()
Expect(f.Data).To(Equal([]byte("foobar")))
Eventually(done).Should(BeClosed())
queue.Pop()
Expect(queue.Peek()).To(BeNil())
})
It("returns the same datagram multiple times, when Pop isn't called", func() {
sent := make(chan struct{}, 1)
It("blocks when the maximum number of datagrams have been queued", func() {
for i := 0; i < maxDatagramSendQueueLen; i++ {
Expect(queue.Add(&wire.DatagramFrame{Data: []byte{0}})).To(Succeed())
}
errChan := make(chan error, 1)
go func() {
defer GinkgoRecover()
Expect(queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
sent <- struct{}{}
Expect(queue.AddAndWait(&wire.DatagramFrame{Data: []byte("bar")})).To(Succeed())
sent <- struct{}{}
errChan <- queue.Add(&wire.DatagramFrame{Data: []byte("foobar")})
}()
Consistently(errChan, 50*time.Millisecond).ShouldNot(Receive())
Expect(queue.Peek()).ToNot(BeNil())
Consistently(errChan, 50*time.Millisecond).ShouldNot(Receive())
queue.Pop()
Eventually(errChan).Should(Receive(BeNil()))
for i := 1; i < maxDatagramSendQueueLen; i++ {
queue.Pop()
}
f := queue.Peek()
Expect(f).ToNot(BeNil())
Expect(f.Data).To(Equal([]byte("foobar")))
})
Eventually(queued).Should(HaveLen(1))
It("returns the same datagram multiple times, when Pop isn't called", func() {
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("bar")})).To(Succeed())
Eventually(queued).Should(HaveLen(2))
f := queue.Peek()
Expect(f.Data).To(Equal([]byte("foo")))
Eventually(sent).Should(Receive())
Expect(queue.Peek()).To(Equal(f))
Expect(queue.Peek()).To(Equal(f))
queue.Pop()
Eventually(func() *wire.DatagramFrame { f = queue.Peek(); return f }).ShouldNot(BeNil())
f = queue.Peek()
Expect(f).ToNot(BeNil())
Expect(f.Data).To(Equal([]byte("bar")))
})
It("closes", func() {
for i := 0; i < maxDatagramSendQueueLen; i++ {
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
}
errChan := make(chan error, 1)
go func() {
defer GinkgoRecover()
errChan <- queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foobar")})
errChan <- queue.Add(&wire.DatagramFrame{Data: []byte("foo")})
}()
Consistently(errChan).ShouldNot(Receive())
queue.CloseWithError(errors.New("test error"))
Eventually(errChan).Should(Receive(MatchError("test error")))
Consistently(errChan, 25*time.Millisecond).ShouldNot(Receive())
testErr := errors.New("test error")
queue.CloseWithError(testErr)
Eventually(errChan).Should(Receive(MatchError(testErr)))
})
})

BIN
docs/uQUIC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/uQUIC_nopadding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

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

View file

@ -1,12 +1,9 @@
package main
import (
"bufio"
"bytes"
"context"
"crypto/x509"
"flag"
"fmt"
"io"
"log"
"net/http"
@ -18,29 +15,16 @@ import (
quic "github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/http3"
"github.com/refraction-networking/uquic/internal/testdata"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/logging"
"github.com/refraction-networking/uquic/qlog"
)
func main() {
verbose := flag.Bool("v", false, "verbose")
quiet := flag.Bool("q", false, "don't print the data")
keyLogFile := flag.String("keylog", "", "key log file")
insecure := flag.Bool("insecure", false, "skip certificate verification")
enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)")
flag.Parse()
urls := flag.Args()
logger := utils.DefaultLogger
if *verbose {
logger.SetLogLevel(utils.LogLevelDebug)
} else {
logger.SetLogLevel(utils.LogLevelInfo)
}
logger.SetLogTimeFormat("")
var keyLog io.Writer
if len(*keyLogFile) > 0 {
f, err := os.Create(*keyLogFile)
@ -57,25 +41,15 @@ func main() {
}
testdata.AddRootCA(pool)
var qconf quic.Config
if *enableQlog {
qconf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
filename := fmt.Sprintf("client_%x.qlog", connID)
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
log.Printf("Creating qlog file %s.\n", filename)
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
}
}
roundTripper := &http3.RoundTripper{
TLSClientConfig: &tls.Config{
RootCAs: pool,
InsecureSkipVerify: *insecure,
KeyLogWriter: keyLog,
},
QuicConfig: &qconf,
QuicConfig: &quic.Config{
Tracer: qlog.DefaultTracer,
},
}
defer roundTripper.Close()
hclient := &http.Client{
@ -85,13 +59,13 @@ func main() {
var wg sync.WaitGroup
wg.Add(len(urls))
for _, addr := range urls {
logger.Infof("GET %s", addr)
log.Printf("GET %s", addr)
go func(addr string) {
rsp, err := hclient.Get(addr)
if err != nil {
log.Fatal(err)
}
logger.Infof("Got response for %s: %#v", addr, rsp)
log.Printf("Got response for %s: %#v", addr, rsp)
body := &bytes.Buffer{}
_, err = io.Copy(body, rsp.Body)
@ -99,10 +73,9 @@ func main() {
log.Fatal(err)
}
if *quiet {
logger.Infof("Response Body: %d bytes", body.Len())
log.Printf("Response Body: %d bytes", body.Len())
} else {
logger.Infof("Response Body:")
logger.Infof("%s", body.Bytes())
log.Printf("Response Body (%d bytes):\n%s", body.Len(), body.Bytes())
}
wg.Done()
}(addr)

View file

@ -37,14 +37,19 @@ func echoServer() error {
if err != nil {
return err
}
defer listener.Close()
conn, err := listener.Accept(context.Background())
if err != nil {
return err
}
stream, err := conn.AcceptStream(context.Background())
if err != nil {
panic(err)
}
defer stream.Close()
// Echo through the loggingWriter
_, err = io.Copy(loggingWriter{stream}, stream)
return err
@ -59,11 +64,13 @@ func clientMain() error {
if err != nil {
return err
}
defer conn.CloseWithError(0, "")
stream, err := conn.OpenStreamSync(context.Background())
if err != nil {
return err
}
defer stream.Close()
fmt.Printf("Client: Sending '%s'\n", message)
_, err = stream.Write([]byte(message))

View file

@ -1,8 +1,6 @@
package main
import (
"bufio"
"context"
"crypto/md5"
"errors"
"flag"
@ -11,7 +9,6 @@ import (
"log"
"mime/multipart"
"net/http"
"os"
"strconv"
"strings"
"sync"
@ -21,8 +18,6 @@ import (
quic "github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/http3"
"github.com/refraction-networking/uquic/internal/testdata"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/logging"
"github.com/refraction-networking/uquic/qlog"
)
@ -121,7 +116,7 @@ func setupHandler(www string) http.Handler {
err = errors.New("couldn't get uploaded file size")
}
}
utils.DefaultLogger.Infof("Error receiving upload: %#v", err)
log.Printf("Error receiving upload: %#v", err)
}
io.WriteString(w, `<html><body><form action="/demo/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadfile"><br>
@ -139,57 +134,45 @@ func main() {
}()
// runtime.SetBlockProfileRate(1)
verbose := flag.Bool("v", false, "verbose")
bs := binds{}
flag.Var(&bs, "bind", "bind to")
www := flag.String("www", "", "www data")
tcp := flag.Bool("tcp", false, "also listen on TCP")
enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)")
key := flag.String("key", "", "TLS key (requires -cert option)")
cert := flag.String("cert", "", "TLS certificate (requires -key option)")
flag.Parse()
logger := utils.DefaultLogger
if *verbose {
logger.SetLogLevel(utils.LogLevelDebug)
} else {
logger.SetLogLevel(utils.LogLevelInfo)
}
logger.SetLogTimeFormat("")
if len(bs) == 0 {
bs = binds{"localhost:6121"}
}
handler := setupHandler(*www)
quicConf := &quic.Config{}
if *enableQlog {
quicConf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
filename := fmt.Sprintf("server_%x.qlog", connID)
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
log.Printf("Creating qlog file %s.\n", filename)
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
}
}
var wg sync.WaitGroup
wg.Add(len(bs))
var certFile, keyFile string
if *key != "" && *cert != "" {
keyFile = *key
certFile = *cert
} else {
certFile, keyFile = testdata.GetCertificatePaths()
}
for _, b := range bs {
fmt.Println("listening on", b)
bCap := b
go func() {
var err error
if *tcp {
certFile, keyFile := testdata.GetCertificatePaths()
err = http3.ListenAndServe(bCap, certFile, keyFile, handler)
} else {
server := http3.Server{
Handler: handler,
Addr: bCap,
QuicConfig: quicConf,
Handler: handler,
Addr: bCap,
QuicConfig: &quic.Config{
Tracer: qlog.DefaultTracer,
},
}
err = server.ListenAndServeTLS(testdata.GetCertificatePaths())
err = server.ListenAndServeTLS(certFile, keyFile)
}
if err != nil {
fmt.Println(err)

View file

@ -15,14 +15,26 @@ type framer interface {
HasData() bool
QueueControlFrame(wire.Frame)
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount)
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.Version) ([]ackhandler.Frame, protocol.ByteCount)
AddActiveStream(protocol.StreamID)
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount)
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.Version) ([]ackhandler.StreamFrame, protocol.ByteCount)
Handle0RTTRejection() error
// QueuedTooManyControlFrames says if the control frame queue exceeded its maximum queue length.
// This is a hack.
// It is easier to implement than propagating an error return value in QueueControlFrame.
// The correct solution would be to queue frames with their respective structs.
// See https://github.com/quic-go/quic-go/issues/4271 for the queueing of stream-related control frames.
QueuedTooManyControlFrames() bool
}
const (
maxPathResponses = 256
maxControlFrames = 16 << 10
)
type framerI struct {
mutex sync.Mutex
@ -31,8 +43,10 @@ type framerI struct {
activeStreams map[protocol.StreamID]struct{}
streamQueue ringbuffer.RingBuffer[protocol.StreamID]
controlFrameMutex sync.Mutex
controlFrames []wire.Frame
controlFrameMutex sync.Mutex
controlFrames []wire.Frame
pathResponses []*wire.PathResponseFrame
queuedTooManyControlFrames bool
}
var _ framer = &framerI{}
@ -52,20 +66,48 @@ func (f *framerI) HasData() bool {
return true
}
f.controlFrameMutex.Lock()
hasData = len(f.controlFrames) > 0
f.controlFrameMutex.Unlock()
return hasData
defer f.controlFrameMutex.Unlock()
return len(f.controlFrames) > 0 || len(f.pathResponses) > 0
}
func (f *framerI) QueueControlFrame(frame wire.Frame) {
f.controlFrameMutex.Lock()
defer f.controlFrameMutex.Unlock()
if pr, ok := frame.(*wire.PathResponseFrame); ok {
// Only queue up to maxPathResponses PATH_RESPONSE frames.
// This limit should be high enough to never be hit in practice,
// unless the peer is doing something malicious.
if len(f.pathResponses) >= maxPathResponses {
return
}
f.pathResponses = append(f.pathResponses, pr)
return
}
// This is a hack.
if len(f.controlFrames) >= maxControlFrames {
f.queuedTooManyControlFrames = true
return
}
f.controlFrames = append(f.controlFrames, frame)
f.controlFrameMutex.Unlock()
}
func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount) {
var length protocol.ByteCount
func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol.ByteCount, v protocol.Version) ([]ackhandler.Frame, protocol.ByteCount) {
f.controlFrameMutex.Lock()
defer f.controlFrameMutex.Unlock()
var length protocol.ByteCount
// add a PATH_RESPONSE first, but only pack a single PATH_RESPONSE per packet
if len(f.pathResponses) > 0 {
frame := f.pathResponses[0]
frameLen := frame.Length(v)
if frameLen <= maxLen {
frames = append(frames, ackhandler.Frame{Frame: frame})
length += frameLen
f.pathResponses = f.pathResponses[1:]
}
}
for len(f.controlFrames) > 0 {
frame := f.controlFrames[len(f.controlFrames)-1]
frameLen := frame.Length(v)
@ -76,10 +118,13 @@ func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol
length += frameLen
f.controlFrames = f.controlFrames[:len(f.controlFrames)-1]
}
f.controlFrameMutex.Unlock()
return frames, length
}
func (f *framerI) QueuedTooManyControlFrames() bool {
return f.queuedTooManyControlFrames
}
func (f *framerI) AddActiveStream(id protocol.StreamID) {
f.mutex.Lock()
if _, ok := f.activeStreams[id]; !ok {
@ -89,7 +134,7 @@ func (f *framerI) AddActiveStream(id protocol.StreamID) {
f.mutex.Unlock()
}
func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount) {
func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen protocol.ByteCount, v protocol.Version) ([]ackhandler.StreamFrame, protocol.ByteCount) {
startLen := len(frames)
var length protocol.ByteCount
f.mutex.Lock()

View file

@ -2,16 +2,16 @@ package quic
import (
"bytes"
"math/rand"
"golang.org/x/exp/rand"
"github.com/refraction-networking/uquic/internal/ackhandler"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/wire"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
var _ = Describe("Framer", func() {
@ -24,7 +24,7 @@ var _ = Describe("Framer", func() {
framer framer
stream1, stream2 *MockSendStreamI
streamGetter *MockStreamGetter
version protocol.VersionNumber
version protocol.Version
)
BeforeEach(func() {
@ -109,6 +109,72 @@ var _ = Describe("Framer", func() {
Expect(fs).To(HaveLen(2))
Expect(length).To(Equal(ping.Length(version) + ncid.Length(version)))
})
It("detects when too many frames are queued", func() {
for i := 0; i < maxControlFrames-1; i++ {
framer.QueueControlFrame(&wire.PingFrame{})
framer.QueueControlFrame(&wire.PingFrame{})
Expect(framer.QueuedTooManyControlFrames()).To(BeFalse())
frames, _ := framer.AppendControlFrames([]ackhandler.Frame{}, 1, protocol.Version1)
Expect(frames).To(HaveLen(1))
Expect(framer.(*framerI).controlFrames).To(HaveLen(i + 1))
}
framer.QueueControlFrame(&wire.PingFrame{})
Expect(framer.QueuedTooManyControlFrames()).To(BeFalse())
Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames))
framer.QueueControlFrame(&wire.PingFrame{})
Expect(framer.QueuedTooManyControlFrames()).To(BeTrue())
Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames))
})
})
Context("handling PATH_RESPONSE frames", func() {
It("packs a single PATH_RESPONSE per packet", func() {
f1 := &wire.PathResponseFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}}
f2 := &wire.PathResponseFrame{Data: [8]byte{2, 3, 4, 5, 6, 7, 8, 9}}
cf1 := &wire.DataBlockedFrame{MaximumData: 1337}
cf2 := &wire.HandshakeDoneFrame{}
framer.QueueControlFrame(f1)
framer.QueueControlFrame(f2)
framer.QueueControlFrame(cf1)
framer.QueueControlFrame(cf2)
// the first packet should contain a single PATH_RESPONSE frame, but all the other control frames
Expect(framer.HasData()).To(BeTrue())
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
Expect(frames).To(HaveLen(3))
Expect(frames[0].Frame).To(Equal(f1))
Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf1))
Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf2))
Expect(length).To(Equal(f1.Length(protocol.Version1) + cf1.Length(protocol.Version1) + cf2.Length(protocol.Version1)))
// the second packet should contain the other PATH_RESPONSE frame
Expect(framer.HasData()).To(BeTrue())
frames, length = framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
Expect(frames).To(HaveLen(1))
Expect(frames[0].Frame).To(Equal(f2))
Expect(length).To(Equal(f2.Length(protocol.Version1)))
Expect(framer.HasData()).To(BeFalse())
})
It("limits the number of queued PATH_RESPONSE frames", func() {
var pathResponses []*wire.PathResponseFrame
for i := 0; i < 2*maxPathResponses; i++ {
var f wire.PathResponseFrame
rand.Read(f.Data[:])
pathResponses = append(pathResponses, &f)
framer.QueueControlFrame(&f)
}
for i := 0; i < maxPathResponses; i++ {
Expect(framer.HasData()).To(BeTrue())
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
Expect(frames).To(HaveLen(1))
Expect(frames[0].Frame).To(Equal(pathResponses[i]))
Expect(length).To(Equal(pathResponses[i].Length(protocol.Version1)))
}
Expect(framer.HasData()).To(BeFalse())
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
Expect(frames).To(BeEmpty())
Expect(length).To(BeZero())
})
})
Context("popping STREAM frames", func() {
@ -297,7 +363,7 @@ var _ = Describe("Framer", func() {
It("pops maximum size STREAM frames", func() {
for i := protocol.MinStreamFrameSize; i < 2000; i++ {
streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil)
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
f := &wire.StreamFrame{
StreamID: id1,
DataLenPresent: true,
@ -319,7 +385,7 @@ var _ = Describe("Framer", func() {
for i := 2 * protocol.MinStreamFrameSize; i < 2000; i++ {
streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil)
streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil)
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
f := &wire.StreamFrame{
StreamID: id2,
DataLenPresent: true,
@ -327,7 +393,7 @@ var _ = Describe("Framer", func() {
f.Data = make([]byte, f.MaxDataLen(protocol.MinStreamFrameSize, v))
return ackhandler.StreamFrame{Frame: f}, true, false
})
stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
f := &wire.StreamFrame{
StreamID: id2,
DataLenPresent: true,

View file

@ -56,22 +56,23 @@ func Fuzz(data []byte) int {
continue
}
}
validateFrame(f)
startLen := len(b)
parsedLen := initialLen - len(data)
b, err = f.Append(b, version)
if err != nil {
panic(fmt.Sprintf("Error writing frame %#v: %s", f, err))
panic(fmt.Sprintf("error writing frame %#v: %s", f, err))
}
frameLen := protocol.ByteCount(len(b) - startLen)
if f.Length(version) != frameLen {
panic(fmt.Sprintf("Inconsistent frame length for %#v: expected %d, got %d", f, frameLen, f.Length(version)))
panic(fmt.Sprintf("inconsistent frame length for %#v: expected %d, got %d", f, frameLen, f.Length(version)))
}
if sf, ok := f.(*wire.StreamFrame); ok {
sf.PutBack()
}
if frameLen > protocol.ByteCount(parsedLen) {
panic(fmt.Sprintf("Serialized length (%d) is longer than parsed length (%d)", len(b), parsedLen))
panic(fmt.Sprintf("serialized length (%d) is longer than parsed length (%d)", len(b), parsedLen))
}
}
@ -80,3 +81,52 @@ func Fuzz(data []byte) int {
}
return 1
}
func validateFrame(frame wire.Frame) {
switch f := frame.(type) {
case *wire.StreamFrame:
if protocol.ByteCount(len(f.Data)) != f.DataLen() {
panic("STREAM frame: inconsistent data length")
}
case *wire.AckFrame:
if f.DelayTime < 0 {
panic(fmt.Sprintf("invalid ACK delay_time: %s", f.DelayTime))
}
if f.LargestAcked() < f.LowestAcked() {
panic("ACK: largest acknowledged is smaller than lowest acknowledged")
}
for _, r := range f.AckRanges {
if r.Largest < 0 || r.Smallest < 0 {
panic("ACK range contains a negative packet number")
}
}
if !f.AcksPacket(f.LargestAcked()) {
panic("ACK frame claims that largest acknowledged is not acknowledged")
}
if !f.AcksPacket(f.LowestAcked()) {
panic("ACK frame claims that lowest acknowledged is not acknowledged")
}
_ = f.AcksPacket(100)
_ = f.AcksPacket((f.LargestAcked() + f.LowestAcked()) / 2)
case *wire.NewConnectionIDFrame:
if f.ConnectionID.Len() < 1 || f.ConnectionID.Len() > 20 {
panic(fmt.Sprintf("invalid NEW_CONNECTION_ID frame length: %s", f.ConnectionID))
}
case *wire.NewTokenFrame:
if len(f.Token) == 0 {
panic("NEW_TOKEN frame with an empty token")
}
case *wire.MaxStreamsFrame:
if f.MaxStreamNum > protocol.MaxStreamCount {
panic("MAX_STREAMS frame with an invalid Maximum Streams value")
}
case *wire.StreamsBlockedFrame:
if f.StreamLimit > protocol.MaxStreamCount {
panic("STREAMS_BLOCKED frame with an invalid Maximum Streams value")
}
case *wire.ConnectionCloseFrame:
if f.IsApplicationError && f.FrameType != 0 {
panic("CONNECTION_CLOSE for an application error containing a frame type")
}
}
}

View file

@ -18,6 +18,7 @@ import (
"github.com/refraction-networking/uquic/fuzzing/internal/helper"
"github.com/refraction-networking/uquic/internal/handshake"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qtls"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/internal/wire"
)
@ -85,33 +86,6 @@ func (m messageType) String() string {
}
}
func appendSuites(suites []uint16, rand uint8) []uint16 {
const (
s1 = tls.TLS_AES_128_GCM_SHA256
s2 = tls.TLS_AES_256_GCM_SHA384
s3 = tls.TLS_CHACHA20_POLY1305_SHA256
)
switch rand % 4 {
default:
return suites
case 1:
return append(suites, s1)
case 2:
return append(suites, s2)
case 3:
return append(suites, s3)
}
}
// consumes 2 bits
func getSuites(rand uint8) []uint16 {
suites := make([]uint16, 0, 3)
for i := 1; i <= 3; i++ {
suites = appendSuites(suites, rand>>i%4)
}
return suites
}
// consumes 3 bits
func getClientAuth(rand uint8) tls.ClientAuthType {
switch rand {
@ -148,6 +122,7 @@ func getTransportParameters(seed uint8) *wire.TransportParameters {
const maxVarInt = math.MaxUint64 / 4
r := mrand.New(mrand.NewSource(int64(seed)))
return &wire.TransportParameters{
ActiveConnectionIDLimit: 2,
InitialMaxData: protocol.ByteCount(r.Int63n(maxVarInt)),
InitialMaxStreamDataBidiLocal: protocol.ByteCount(r.Int63n(maxVarInt)),
InitialMaxStreamDataBidiRemote: protocol.ByteCount(r.Int63n(maxVarInt)),
@ -207,14 +182,26 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
SessionTicketKey: sessionTicketKey,
}
// This sets the cipher suite for both client and server.
// The way crypto/tls is designed doesn't allow us to set different cipher suites for client and server.
resetCipherSuite := func() {}
switch (runConfig[0] >> 6) % 4 {
case 0:
resetCipherSuite = qtls.SetCipherSuite(tls.TLS_AES_128_GCM_SHA256)
case 1:
resetCipherSuite = qtls.SetCipherSuite(tls.TLS_AES_256_GCM_SHA384)
case 3:
resetCipherSuite = qtls.SetCipherSuite(tls.TLS_CHACHA20_POLY1305_SHA256)
default:
}
defer resetCipherSuite()
enable0RTTClient := helper.NthBit(runConfig[0], 0)
enable0RTTServer := helper.NthBit(runConfig[0], 1)
sendPostHandshakeMessageToClient := helper.NthBit(runConfig[0], 3)
sendPostHandshakeMessageToServer := helper.NthBit(runConfig[0], 4)
sendSessionTicket := helper.NthBit(runConfig[0], 5)
clientConf.CipherSuites = getSuites(runConfig[0] >> 6)
serverConf.ClientAuth = getClientAuth(runConfig[1] & 0b00000111)
serverConf.CipherSuites = getSuites(runConfig[1] >> 6)
serverConf.SessionTicketsDisabled = helper.NthBit(runConfig[1], 3)
if helper.NthBit(runConfig[2], 0) {
clientConf.RootCAs = x509.NewCertPool()
@ -303,6 +290,7 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
if err := client.StartHandshake(); err != nil {
log.Fatal(err)
}
defer client.Close()
server := handshake.NewCryptoSetupServer(
protocol.ConnectionID{},
@ -319,12 +307,13 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
if err := server.StartHandshake(); err != nil {
log.Fatal(err)
}
defer server.Close()
var clientHandshakeComplete, serverHandshakeComplete bool
for {
var processedEvent bool
clientLoop:
for {
var processedEvent bool
ev := client.NextEvent()
//nolint:exhaustive // only need to process a few events
switch ev.Kind {
@ -335,11 +324,16 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
break clientLoop
case handshake.EventWriteInitialData, handshake.EventWriteHandshakeData:
msg := ev.Data
encLevel := protocol.EncryptionInitial
if ev.Kind == handshake.EventWriteHandshakeData {
encLevel = protocol.EncryptionHandshake
}
if msg[0] == messageToReplace {
fmt.Printf("replacing %s message to the server with %s at %s\n", messageType(msg[0]), messageType(data[0]), messageToReplaceEncLevel)
msg = data
encLevel = messageToReplaceEncLevel
}
if err := server.HandleMessage(msg, messageToReplaceEncLevel); err != nil {
if err := server.HandleMessage(msg, encLevel); err != nil {
return 1
}
case handshake.EventHandshakeComplete:
@ -348,9 +342,9 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
processedEvent = true
}
processedEvent = false
serverLoop:
for {
var processedEvent bool
ev := server.NextEvent()
//nolint:exhaustive // only need to process a few events
switch ev.Kind {
@ -360,12 +354,17 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
}
break serverLoop
case handshake.EventWriteInitialData, handshake.EventWriteHandshakeData:
encLevel := protocol.EncryptionInitial
if ev.Kind == handshake.EventWriteHandshakeData {
encLevel = protocol.EncryptionHandshake
}
msg := ev.Data
if msg[0] == messageToReplace {
fmt.Printf("replacing %s message to the client with %s at %s\n", messageType(msg[0]), messageType(data[0]), messageToReplaceEncLevel)
msg = data
encLevel = messageToReplaceEncLevel
}
if err := client.HandleMessage(msg, messageToReplaceEncLevel); err != nil {
if err := client.HandleMessage(msg, encLevel); err != nil {
return 1
}
case handshake.EventHandshakeComplete:
@ -410,10 +409,13 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
}
client.HandleMessage(ticket, protocol.Encryption1RTT)
}
if sendPostHandshakeMessageToClient {
fmt.Println("sending post handshake message to the client at", messageToReplaceEncLevel)
client.HandleMessage(data, messageToReplaceEncLevel)
}
if sendPostHandshakeMessageToServer {
fmt.Println("sending post handshake message to the server at", messageToReplaceEncLevel)
server.HandleMessage(data, messageToReplaceEncLevel)
}

View file

@ -20,9 +20,9 @@ func getRandomData(l int) []byte {
}
func getVNP(src, dest protocol.ArbitraryLenConnectionID, numVersions int) []byte {
versions := make([]protocol.VersionNumber, numVersions)
versions := make([]protocol.Version, numVersions)
for i := 0; i < numVersions; i++ {
versions[i] = protocol.VersionNumber(rand.Uint32())
versions[i] = protocol.Version(rand.Uint32())
}
return wire.ComposeVersionNegotiation(src, dest, versions)
}

View file

@ -2,24 +2,22 @@ package tokens
import (
"encoding/binary"
"math/rand"
"net"
"time"
"github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/internal/handshake"
"github.com/refraction-networking/uquic/internal/protocol"
)
func Fuzz(data []byte) int {
if len(data) < 8 {
if len(data) < 32 {
return -1
}
seed := binary.BigEndian.Uint64(data[:8])
data = data[8:]
tg, err := handshake.NewTokenGenerator(rand.New(rand.NewSource(int64(seed))))
if err != nil {
panic(err)
}
var key quic.TokenGeneratorKey
copy(key[:], data[:32])
data = data[32:]
tg := handshake.NewTokenGenerator(key)
if len(data) < 1 {
return -1
}

View file

@ -3,7 +3,7 @@ package main
import (
"log"
"math"
"net"
"net/netip"
"time"
"golang.org/x/exp/rand"
@ -59,11 +59,13 @@ func main() {
if rand.Int()%2 == 0 {
var token protocol.StatelessResetToken
rand.Read(token[:])
var ip4 [4]byte
rand.Read(ip4[:])
var ip6 [16]byte
rand.Read(ip6[:])
tp.PreferredAddress = &wire.PreferredAddress{
IPv4: net.IPv4(uint8(rand.Int()), uint8(rand.Int()), uint8(rand.Int()), uint8(rand.Int())),
IPv4Port: uint16(rand.Int()),
IPv6: net.IP(getRandomData(16)),
IPv6Port: uint16(rand.Int()),
IPv4: netip.AddrPortFrom(netip.AddrFrom4(ip4), uint16(rand.Int())),
IPv6: netip.AddrPortFrom(netip.AddrFrom16(ip6), uint16(rand.Int())),
ConnectionID: protocol.ParseConnectionID(getRandomData(rand.Intn(21))),
StatelessResetToken: token,
}

View file

@ -2,6 +2,7 @@ package transportparameters
import (
"bytes"
"errors"
"fmt"
"github.com/refraction-networking/uquic/fuzzing/internal/helper"
@ -26,23 +27,29 @@ func Fuzz(data []byte) int {
return fuzzTransportParameters(data[PrefixLen:], helper.NthBit(data[0], 1))
}
func fuzzTransportParameters(data []byte, isServer bool) int {
perspective := protocol.PerspectiveClient
if isServer {
perspective = protocol.PerspectiveServer
func fuzzTransportParameters(data []byte, sentByServer bool) int {
sentBy := protocol.PerspectiveClient
if sentByServer {
sentBy = protocol.PerspectiveServer
}
tp := &wire.TransportParameters{}
if err := tp.Unmarshal(data, perspective); err != nil {
if err := tp.Unmarshal(data, sentBy); err != nil {
return 0
}
_ = tp.String()
if err := validateTransportParameters(tp, sentBy); err != nil {
panic(err)
}
tp2 := &wire.TransportParameters{}
if err := tp2.Unmarshal(tp.Marshal(perspective), perspective); err != nil {
if err := tp2.Unmarshal(tp.Marshal(sentBy), sentBy); err != nil {
fmt.Printf("%#v\n", tp)
panic(err)
}
if err := validateTransportParameters(tp2, sentBy); err != nil {
panic(err)
}
return 1
}
@ -58,3 +65,34 @@ func fuzzTransportParametersForSessionTicket(data []byte) int {
}
return 1
}
func validateTransportParameters(tp *wire.TransportParameters, sentBy protocol.Perspective) error {
if sentBy == protocol.PerspectiveClient && tp.StatelessResetToken != nil {
return errors.New("client's transport parameters contained stateless reset token")
}
if tp.MaxIdleTimeout < 0 {
return fmt.Errorf("negative max_idle_timeout: %s", tp.MaxIdleTimeout)
}
if tp.AckDelayExponent > 20 {
return fmt.Errorf("invalid ack_delay_exponent: %d", tp.AckDelayExponent)
}
if tp.MaxUDPPayloadSize < 1200 {
return fmt.Errorf("invalid max_udp_payload_size: %d", tp.MaxUDPPayloadSize)
}
if tp.ActiveConnectionIDLimit < 2 {
return fmt.Errorf("invalid active_connection_id_limit: %d", tp.ActiveConnectionIDLimit)
}
if tp.OriginalDestinationConnectionID.Len() > 20 {
return fmt.Errorf("invalid original_destination_connection_id length: %s", tp.InitialSourceConnectionID)
}
if tp.InitialSourceConnectionID.Len() > 20 {
return fmt.Errorf("invalid initial_source_connection_id length: %s", tp.InitialSourceConnectionID)
}
if tp.RetrySourceConnectionID != nil && tp.RetrySourceConnectionID.Len() > 20 {
return fmt.Errorf("invalid retry_source_connection_id length: %s", tp.RetrySourceConnectionID)
}
if tp.PreferredAddress != nil && tp.PreferredAddress.ConnectionID.Len() > 20 {
return fmt.Errorf("invalid preferred_address connection ID length: %s", tp.PreferredAddress.ConnectionID)
}
return nil
}

44
go.mod
View file

@ -1,34 +1,34 @@
module github.com/refraction-networking/uquic
go 1.20
go 1.21.0
require (
github.com/francoispqt/gojay v1.2.13
github.com/gaukas/clienthellod v0.4.0
github.com/golang/mock v1.6.0
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.10
github.com/onsi/ginkgo/v2 v2.17.2
github.com/onsi/gomega v1.33.1
github.com/quic-go/qpack v0.4.0
github.com/refraction-networking/utls v1.4.1
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de
golang.org/x/net v0.14.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.11.0
github.com/refraction-networking/clienthellod v0.5.0-alpha2
github.com/refraction-networking/utls v1.6.7
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.31.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/net v0.32.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.28.0
golang.org/x/time v0.8.0
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/quic-go/quic-go v0.37.3 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/klauspost/compress v1.17.8 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

113
go.sum
View file

@ -8,15 +8,16 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -24,39 +25,32 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gaukas/clienthellod v0.4.0 h1:DySeZT4c3Xw6OGMzHRlAuOHx9q1P7vQNjA7YkyHrqac=
github.com/gaukas/clienthellod v0.4.0/go.mod h1:gjt7a7cNNzZV4yTe0jKcXtj0a7u6RL2KQvijxFOvcZE=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -66,13 +60,11 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -82,10 +74,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -96,10 +88,10 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.37.3 h1:pkHH3xaMNUNAh6OtgEV/0K6Fz+YIJXhPzgd/ShiRDm4=
github.com/quic-go/quic-go v0.37.3/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/refraction-networking/utls v1.4.1 h1:5VXwhNzrnWrvbJW8IVpptJKrErZGqoRbn7wqu2jqMrU=
github.com/refraction-networking/utls v1.4.1/go.mod h1:JkUIj+Pc8eyFB0z+A4RJRZmoT43ajjFZWVMXuZQ8BEQ=
github.com/refraction-networking/clienthellod v0.5.0-alpha2 h1:h4y/a97p9EsxAdhXYCBcf8kGfroJ6sjTQ4F/yJyna4A=
github.com/refraction-networking/clienthellod v0.5.0-alpha2/go.mod h1:4vN+Qh4x2TznUMsfw6N3ohGjwvfs6lnwwNPUn7zI9bQ=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
@ -126,34 +118,33 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE=
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -164,9 +155,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -177,41 +167,32 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@ -228,13 +209,13 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=

104
http3/README.md Normal file
View file

@ -0,0 +1,104 @@
# HTTP/3
[![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go/http3)](https://pkg.go.dev/github.com/quic-go/quic-go/http3)
This package implements HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
It aims to provide feature parity with the standard library's HTTP/1.1 and HTTP/2 implementation.
## Serving HTTP/3
The easiest way to start an HTTP/3 server is using
```go
mux := http.NewServeMux()
// ... add HTTP handlers to mux ...
// If mux is nil, the http.DefaultServeMux is used.
http3.ListenAndServeQUIC("0.0.0.0:443", "/path/to/cert", "/path/to/key", mux)
```
`ListenAndServeQUIC` is a convenience function. For more configurability, set up an `http3.Server` explicitly:
```go
server := http3.Server{
Handler: mux,
Addr: "0.0.0.0:443",
TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}), // use your tls.Config here
QuicConfig: &quic.Config{},
}
err := server.ListenAndServe()
```
The `http3.Server` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#Server) for a complete list. The `QuicConfig` is used to configure the underlying QUIC connection. More details can be found in the documentation of the QUIC package.
It is also possible to manually set up a `quic.Transport`, and then pass the listener to the server. This is useful when you want to set configuration options on the `quic.Transport`.
```go
tr := quic.Transport{Conn: conn}
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
quicConf := &quic.Config{} // QUIC connection options
server := http3.Server{}
ln, _ := tr.ListenEarly(tlsConf, quicConf)
server.ServeListener(ln)
```
Alternatively, it is also possible to pass fully established QUIC connections to the HTTP/3 server. This is useful if the QUIC server offers multiple ALPNs (via `NextProtos` in the `tls.Config`).
```go
tr := quic.Transport{Conn: conn}
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
quicConf := &quic.Config{} // QUIC connection options
server := http3.Server{}
// alternatively, use tr.ListenEarly to accept 0-RTT connections
ln, _ := tr.Listen(tlsConf, quicConf)
for {
c, _ := ln.Accept()
switch c.ConnectionState().TLS.NegotiatedProtocol {
case http3.NextProtoH3:
go server.ServeQUICConn(c)
// ... handle other protocols ...
}
}
```
## Dialing HTTP/3
This package provides a `http.RoundTripper` implementation that can be used on the `http.Client`:
```go
&http3.RoundTripper{
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
QuicConfig: &quic.Config{}, // QUIC connection options
}
defer roundTripper.Close()
client := &http.Client{
Transport: roundTripper,
}
```
The `http3.RoundTripper` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#RoundTripper) for a complete list.
To use a custom `quic.Transport`, the function used to dial new QUIC connections can be configured:
```go
tr := quic.Transport{}
roundTripper := &http3.RoundTripper{
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
QuicConfig: &quic.Config{}, // QUIC connection options
Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
a, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
return tr.DialEarly(ctx, a, tlsConf, quicConf)
},
}
```
## Using the same UDP Socket for Server and Roundtripper
Since QUIC demultiplexes packets based on their connection IDs, it is possible allows running a QUIC server and client on the same UDP socket. This also works when using HTTP/3: HTTP requests can be sent from the same socket that a server is listening on.
To achieve this using this package, first initialize a single `quic.Transport`, and pass a `quic.EarlyListner` obtained from that transport to `http3.Server.ServeListener`, and use the `DialEarly` function of the transport as the `Dial` function for the `http3.RoundTripper`.
## QPACK
HTTP/3 utilizes QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) for efficient HTTP header field compression. Our implementation, available at[quic-go/qpack](https://github.com/quic-go/qpack), provides a minimal implementation of the protocol.
While the current implementation is a fully interoperable implementation of the QPACK protocol, it only uses the static compression table. The dynamic table would allow for more effective compression of frequently transmitted header fields. This can be particularly beneficial in scenarios where headers have considerable redundancy or in high-throughput environments.
If you think that your application would benefit from higher compression efficiency, or if you're interested in contributing improvements here, please let us know in [#2424](https://github.com/quic-go/quic-go/issues/2424).

View file

@ -63,7 +63,8 @@ func (r *body) wasStreamHijacked() bool {
}
func (r *body) Read(b []byte) (int, error) {
return r.str.Read(b)
n, err := r.str.Read(b)
return n, maybeReplaceError(err)
}
func (r *body) Close() error {
@ -106,7 +107,7 @@ func (r *hijackableBody) Read(b []byte) (int, error) {
if err != nil {
r.requestDone()
}
return n, err
return n, maybeReplaceError(err)
}
func (r *hijackableBody) requestDone() {

View file

@ -6,9 +6,9 @@ import (
quic "github.com/refraction-networking/uquic"
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
var _ = Describe("Response Body", func() {

View file

@ -61,6 +61,9 @@ type client struct {
dialer dialFunc
handshakeErr error
receivedSettings chan struct{} // closed once the server's SETTINGS frame was processed
settings *Settings // set once receivedSettings is closed
requestWriter *requestWriter
decoder *qpack.Decoder
@ -76,10 +79,14 @@ var _ roundTripCloser = &client{}
func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) {
if conf == nil {
conf = defaultQuicConfig.Clone()
conf.EnableDatagrams = opts.EnableDatagram
}
if opts.EnableDatagram && !conf.EnableDatagrams {
return nil, errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
}
if len(conf.Versions) == 0 {
conf = conf.Clone()
conf.Versions = []quic.VersionNumber{protocol.SupportedVersions[0]}
conf.Versions = []quic.Version{protocol.SupportedVersions[0]}
}
if len(conf.Versions) != 1 {
return nil, errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
@ -87,7 +94,6 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
if conf.MaxIncomingStreams == 0 {
conf.MaxIncomingStreams = -1 // don't allow any bidirectional streams
}
conf.EnableDatagrams = opts.EnableDatagram
logger := utils.DefaultLogger.WithPrefix("h3 client")
if tlsConf == nil {
@ -107,14 +113,15 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
tlsConf.NextProtos = []string{versionToALPN(conf.Versions[0])}
return &client{
hostname: authorityAddr("https", hostname),
tlsConf: tlsConf,
requestWriter: newRequestWriter(logger),
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
config: conf,
opts: opts,
dialer: dialer,
logger: logger,
hostname: authorityAddr("https", hostname),
tlsConf: tlsConf,
requestWriter: newRequestWriter(logger),
receivedSettings: make(chan struct{}),
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
config: conf,
opts: opts,
dialer: dialer,
logger: logger,
}, nil
}
@ -183,6 +190,8 @@ func (c *client) handleBidirectionalStreams(conn quic.EarlyConnection) {
}
func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
var rcvdControlStream atomic.Bool
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
@ -217,6 +226,11 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
return
}
// Only a single control stream is allowed.
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
return
}
f, err := parseNextFrame(str, nil)
if err != nil {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
@ -227,6 +241,12 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
return
}
c.settings = &Settings{
EnableDatagram: sf.Datagram,
EnableExtendedConnect: sf.ExtendedConnect,
Other: sf.Other,
}
close(c.receivedSettings)
if !sf.Datagram {
return
}
@ -257,6 +277,15 @@ func (c *client) maxHeaderBytes() uint64 {
// RoundTripOpt executes a request and returns a response
func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
rsp, err := c.roundTripOpt(req, opt)
if err != nil && req.Context().Err() != nil {
// if the context was canceled, return the context cancellation error
err = req.Context().Err()
}
return rsp, err
}
func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if authorityAddr("https", hostnameFromRequest(req)) != c.hostname {
return nil, fmt.Errorf("http3 client BUG: RoundTripOpt called for the wrong client (expected %s, got %s)", c.hostname, req.Host)
}
@ -283,6 +312,18 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
}
}
if opt.CheckSettings != nil {
// wait for the server's SETTINGS frame to arrive
select {
case <-c.receivedSettings:
case <-conn.Context().Done():
return nil, context.Cause(conn.Context())
}
if err := opt.CheckSettings(*c.settings); err != nil {
return nil, err
}
}
str, err := conn.OpenStreamSync(req.Context())
if err != nil {
return nil, err
@ -321,13 +362,13 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
}
conn.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason)
}
return nil, rerr.err
return nil, maybeReplaceError(rerr.err)
}
if opt.DontCloseRequestStream {
close(reqDone)
<-done
}
return rsp, rerr.err
return rsp, maybeReplaceError(rerr.err)
}
// cancelingReader reads from the io.Reader.

View file

@ -11,19 +11,20 @@ import (
"sync"
"time"
quic "github.com/refraction-networking/uquic"
tls "github.com/refraction-networking/utls"
quic "github.com/refraction-networking/uquic"
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qerr"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/quicvarint"
"github.com/golang/mock/gomock"
"github.com/quic-go/qpack"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
var _ = Describe("Client", func() {
@ -56,7 +57,7 @@ var _ = Describe("Client", func() {
It("rejects quic.Configs that allow multiple QUIC versions", func() {
qconf := &quic.Config{
Versions: []quic.VersionNumber{protocol.Version2, protocol.Version1},
Versions: []quic.Version{protocol.Version2, protocol.Version1},
}
_, err := newClient("localhost:1337", nil, &roundTripperOpts{}, qconf, nil)
Expect(err).To(MatchError("can only use a single QUIC version for dialing a HTTP/3 connection"))
@ -69,7 +70,7 @@ var _ = Describe("Client", func() {
dialAddr = func(_ context.Context, _ string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
Expect(quicConf.MaxIncomingStreams).To(Equal(defaultQuicConfig.MaxIncomingStreams))
Expect(tlsConf.NextProtos).To(Equal([]string{NextProtoH3}))
Expect(quicConf.Versions).To(Equal([]protocol.VersionNumber{protocol.Version1}))
Expect(quicConf.Versions).To(Equal([]protocol.Version{protocol.Version1}))
dialAddrCalled = true
return nil, errors.New("test done")
}
@ -214,9 +215,10 @@ var _ = Describe("Client", func() {
testDone = make(chan struct{})
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
defer GinkgoRecover()
close(settingsFrameWritten)
return len(b), nil
})
conn = mockquic.NewMockEarlyConnection(mockCtrl)
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
@ -340,9 +342,10 @@ var _ = Describe("Client", func() {
testDone = make(chan struct{})
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
defer GinkgoRecover()
close(settingsFrameWritten)
return len(b), nil
})
conn = mockquic.NewMockEarlyConnection(mockCtrl)
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
@ -441,19 +444,19 @@ var _ = Describe("Client", func() {
conn *mockquic.MockEarlyConnection
settingsFrameWritten chan struct{}
)
testDone := make(chan struct{})
testDone := make(chan struct{}, 1)
BeforeEach(func() {
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
defer GinkgoRecover()
close(settingsFrameWritten)
return len(b), nil
})
conn = mockquic.NewMockEarlyConnection(mockCtrl)
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
conn.EXPECT().HandshakeComplete().Return(handshakeChan)
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
dialAddr = func(context.Context, string, *tls.Config, *quic.Config) (quic.EarlyConnection, error) {
return conn, nil
}
@ -469,10 +472,15 @@ var _ = Describe("Client", func() {
It("parses the SETTINGS frame", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&settingsFrame{}).Append(b)
b = (&settingsFrame{
Datagram: true,
ExtendedConnect: true,
Other: map[uint64]uint64{1337: 42},
}).Append(b)
r := bytes.NewReader(b)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
@ -480,11 +488,72 @@ var _ = Describe("Client", func() {
<-testDone
return nil, errors.New("test done")
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
conn.EXPECT().Context().Return(context.Background())
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
defer GinkgoRecover()
Expect(settings.EnableDatagram).To(BeTrue())
Expect(settings.EnableExtendedConnect).To(BeTrue())
Expect(settings.Other).To(HaveLen(1))
Expect(settings.Other).To(HaveKeyWithValue(uint64(1337), uint64(42)))
return nil
}})
Expect(err).To(MatchError("done"))
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
})
It("allows the client to reject the SETTINGS using the CheckSettings RoundTripOpt", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&settingsFrame{}).Append(b)
r := bytes.NewReader(b)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
// Don't EXPECT any call to OpenStreamSync.
// When the SETTINGS are rejected, we don't even open the request stream.
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
conn.EXPECT().Context().Return(context.Background())
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
return errors.New("wrong settings")
}})
Expect(err).To(MatchError("wrong settings"))
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
})
It("rejects duplicate control streams", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&settingsFrame{}).Append(b)
r1 := bytes.NewReader(b)
controlStr1 := mockquic.NewMockStream(mockCtrl)
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
r2 := bytes.NewReader(b)
controlStr2 := mockquic.NewMockStream(mockCtrl)
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
done := make(chan struct{})
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
close(done)
return nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr1, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr2, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-done
return nil, errors.New("test done")
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(HaveOccurred())
Eventually(done).Should(BeClosed())
})
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
streamType := t
name := "encoder"
@ -497,6 +566,7 @@ var _ = Describe("Client", func() {
str := mockquic.NewMockStream(mockCtrl)
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return str, nil
})
@ -510,15 +580,14 @@ var _ = Describe("Client", func() {
})
}
It("resets streams Other than the control stream and the QPACK streams", func() {
It("resets streams other than the control stream and the QPACK streams", func() {
buf := bytes.NewBuffer(quicvarint.Append(nil, 0x1337))
str := mockquic.NewMockStream(mockCtrl)
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
done := make(chan struct{})
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
close(done)
})
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return str, nil
})
@ -537,6 +606,8 @@ var _ = Describe("Client", func() {
r := bytes.NewReader(b)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
@ -545,22 +616,53 @@ var _ = Describe("Client", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("done"))
Eventually(done).Should(BeClosed())
})
It("errors when the first frame on the control stream is not a SETTINGS frame, when checking SETTINGS", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&dataFrame{}).Append(b)
r := bytes.NewReader(b)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
// Don't EXPECT any calls to OpenStreamSync.
// We fail before we even get the chance to open the request stream.
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
doneCtx, doneCancel := context.WithCancelCause(context.Background())
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
doneCancel(errors.New("done"))
return nil
})
conn.EXPECT().Context().Return(doneCtx).Times(2)
var checked bool
_, err := cl.RoundTripOpt(req, RoundTripOpt{
CheckSettings: func(Settings) error { checked = true; return nil },
})
Expect(checked).To(BeFalse())
Expect(err).To(MatchError("done"))
Eventually(doneCtx.Done()).Should(BeClosed())
})
It("errors when parsing the frame on the control stream fails", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&settingsFrame{}).Append(b)
r := bytes.NewReader(b[:len(b)-1])
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
@ -569,10 +671,9 @@ var _ = Describe("Client", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) error {
close(done)
return nil
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("done"))
@ -583,6 +684,7 @@ var _ = Describe("Client", func() {
buf := bytes.NewBuffer(quicvarint.Append(nil, streamTypePushStream))
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
@ -591,10 +693,9 @@ var _ = Describe("Client", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeIDError))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("done"))
@ -608,6 +709,7 @@ var _ = Describe("Client", func() {
r := bytes.NewReader(b)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
@ -617,11 +719,9 @@ var _ = Describe("Client", func() {
})
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
Expect(reason).To(Equal("missing QUIC Datagram support"))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("done"))
@ -670,13 +770,14 @@ var _ = Describe("Client", func() {
BeforeEach(func() {
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
defer GinkgoRecover()
r := bytes.NewReader(b)
streamType, err := quicvarint.Read(r)
Expect(err).ToNot(HaveOccurred())
Expect(streamType).To(BeEquivalentTo(streamTypeControlStream))
close(settingsFrameWritten)
return len(b), nil
}) // SETTINGS frame
str = mockquic.NewMockStream(mockCtrl)
conn = mockquic.NewMockEarlyConnection(mockCtrl)
@ -778,7 +879,7 @@ var _ = Describe("Client", func() {
It("sends a request", func() {
done := make(chan struct{})
gomock.InOrder(
str.EXPECT().Close().Do(func() { close(done) }),
str.EXPECT().Close().Do(func() error { close(done); return nil }),
str.EXPECT().CancelWrite(gomock.Any()).MaxTimes(1), // when reading the response errors
)
// the response body is sent asynchronously, while already reading the response
@ -832,7 +933,7 @@ var _ = Describe("Client", func() {
return 0, errors.New("test done")
})
closed := make(chan struct{})
str.EXPECT().Close().Do(func() { close(closed) })
str.EXPECT().Close().Do(func() error { close(closed); return nil })
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("test done"))
Eventually(closed).Should(BeClosed())
@ -843,7 +944,7 @@ var _ = Describe("Client", func() {
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any())
closed := make(chan struct{})
r := bytes.NewReader(b)
str.EXPECT().Close().Do(func() { close(closed) })
str.EXPECT().Close().Do(func() error { close(closed); return nil })
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("expected first frame to be a HEADERS frame"))
@ -861,7 +962,7 @@ var _ = Describe("Client", func() {
r := bytes.NewReader(b)
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
closed := make(chan struct{})
str.EXPECT().Close().Do(func() { close(closed) })
str.EXPECT().Close().Do(func() error { close(closed); return nil })
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(HaveOccurred())
@ -873,7 +974,7 @@ var _ = Describe("Client", func() {
r := bytes.NewReader(b)
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
closed := make(chan struct{})
str.EXPECT().Close().Do(func() { close(closed) })
str.EXPECT().Close().Do(func() error { close(closed); return nil })
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
Expect(err).To(MatchError("HEADERS frame too large: 1338 bytes (max: 1337)"))
@ -926,7 +1027,7 @@ var _ = Describe("Client", func() {
return 0, errors.New("test done")
})
_, err := cl.RoundTripOpt(req, roundTripOpt)
Expect(err).To(MatchError("test done"))
Expect(err).To(MatchError(context.Canceled))
Eventually(done).Should(BeClosed())
})
})

58
http3/error.go Normal file
View file

@ -0,0 +1,58 @@
package http3
import (
"errors"
"fmt"
quic "github.com/refraction-networking/uquic"
)
// Error is returned from the round tripper (for HTTP clients)
// and inside the HTTP handler (for HTTP servers) if an HTTP/3 error occurs.
// See section 8 of RFC 9114.
type Error struct {
Remote bool
ErrorCode ErrCode
ErrorMessage string
}
var _ error = &Error{}
func (e *Error) Error() string {
s := e.ErrorCode.string()
if s == "" {
s = fmt.Sprintf("H3 error (%#x)", uint64(e.ErrorCode))
}
// Usually errors are remote. Only make it explicit for local errors.
if !e.Remote {
s += " (local)"
}
if e.ErrorMessage != "" {
s += ": " + e.ErrorMessage
}
return s
}
func maybeReplaceError(err error) error {
if err == nil {
return nil
}
var (
e Error
strErr *quic.StreamError
appErr *quic.ApplicationError
)
switch {
default:
return err
case errors.As(err, &strErr):
e.Remote = strErr.Remote
e.ErrorCode = ErrCode(strErr.ErrorCode)
case errors.As(err, &appErr):
e.Remote = appErr.Remote
e.ErrorCode = ErrCode(appErr.ErrorCode)
e.ErrorMessage = appErr.ErrorMessage
}
return &e
}

View file

@ -26,10 +26,18 @@ const (
ErrCodeMessageError ErrCode = 0x10e
ErrCodeConnectError ErrCode = 0x10f
ErrCodeVersionFallback ErrCode = 0x110
ErrCodeDatagramError ErrCode = 0x4a1268
ErrCodeDatagramError ErrCode = 0x33
)
func (e ErrCode) String() string {
s := e.string()
if s != "" {
return s
}
return fmt.Sprintf("unknown error code: %#x", uint16(e))
}
func (e ErrCode) string() string {
switch e {
case ErrCodeNoError:
return "H3_NO_ERROR"
@ -68,6 +76,6 @@ func (e ErrCode) String() string {
case ErrCodeDatagramError:
return "H3_DATAGRAM_ERROR"
default:
return fmt.Sprintf("unknown error code: %#x", uint16(e))
return ""
}
}

40
http3/error_test.go Normal file
View file

@ -0,0 +1,40 @@
package http3
import (
"errors"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
quic "github.com/refraction-networking/uquic"
)
var _ = Describe("HTTP/3 errors", func() {
It("converts", func() {
Expect(maybeReplaceError(nil)).To(BeNil())
Expect(maybeReplaceError(errors.New("foobar"))).To(MatchError("foobar"))
Expect(maybeReplaceError(&quic.StreamError{
ErrorCode: 1337,
Remote: true,
})).To(Equal(&Error{
Remote: true,
ErrorCode: 1337,
}))
Expect(maybeReplaceError(&quic.ApplicationError{
ErrorCode: 42,
Remote: true,
ErrorMessage: "foobar",
})).To(Equal(&Error{
Remote: true,
ErrorCode: 42,
ErrorMessage: "foobar",
}))
})
It("has a string representation", func() {
Expect((&Error{ErrorCode: 0x10c, Remote: true}).Error()).To(Equal("H3_REQUEST_CANCELLED"))
Expect((&Error{ErrorCode: 0x10c, Remote: true, ErrorMessage: "foobar"}).Error()).To(Equal("H3_REQUEST_CANCELLED: foobar"))
Expect((&Error{ErrorCode: 0x10c, Remote: false}).Error()).To(Equal("H3_REQUEST_CANCELLED (local)"))
Expect((&Error{ErrorCode: 0x10c, Remote: false, ErrorMessage: "foobar"}).Error()).To(Equal("H3_REQUEST_CANCELLED (local): foobar"))
Expect((&Error{ErrorCode: 0x1337, Remote: true}).Error()).To(Equal("H3 error (0x1337)"))
})
})

View file

@ -88,11 +88,18 @@ func (f *headersFrame) Append(b []byte) []byte {
return quicvarint.Append(b, f.Length)
}
const settingDatagram = 0xffd277
const (
// Extended CONNECT, RFC 9220
settingExtendedConnect = 0x8
// HTTP Datagrams, RFC 9297
settingDatagram = 0x33
)
type settingsFrame struct {
Datagram bool
Other map[uint64]uint64 // all settings that we don't explicitly recognize
Datagram bool // HTTP Datagrams, RFC 9297
ExtendedConnect bool // Extended CONNECT, RFC 9220
Other map[uint64]uint64 // all settings that we don't explicitly recognize
}
func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
@ -108,7 +115,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
}
frame := &settingsFrame{}
b := bytes.NewReader(buf)
var readDatagram bool
var readDatagram, readExtendedConnect bool
for b.Len() > 0 {
id, err := quicvarint.Read(b)
if err != nil { // should not happen. We allocated the whole frame already.
@ -120,13 +127,22 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
}
switch id {
case settingExtendedConnect:
if readExtendedConnect {
return nil, fmt.Errorf("duplicate setting: %d", id)
}
readExtendedConnect = true
if val != 0 && val != 1 {
return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", val)
}
frame.ExtendedConnect = val == 1
case settingDatagram:
if readDatagram {
return nil, fmt.Errorf("duplicate setting: %d", id)
}
readDatagram = true
if val != 0 && val != 1 {
return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val)
return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", val)
}
frame.Datagram = val == 1
default:
@ -151,11 +167,18 @@ func (f *settingsFrame) Append(b []byte) []byte {
if f.Datagram {
l += quicvarint.Len(settingDatagram) + quicvarint.Len(1)
}
if f.ExtendedConnect {
l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
}
b = quicvarint.Append(b, uint64(l))
if f.Datagram {
b = quicvarint.Append(b, settingDatagram)
b = quicvarint.Append(b, 1)
}
if f.ExtendedConnect {
b = quicvarint.Append(b, settingExtendedConnect)
b = quicvarint.Append(b, 1)
}
for id, val := range f.Other {
b = quicvarint.Append(b, id)
b = quicvarint.Append(b, val)

View file

@ -127,8 +127,8 @@ var _ = Describe("Frames", func() {
}
})
Context("H3_DATAGRAM", func() {
It("reads the H3_DATAGRAM value", func() {
Context("HTTP Datagrams", func() {
It("reads the SETTINGS_H3_DATAGRAM value", func() {
settings := quicvarint.Append(nil, settingDatagram)
settings = quicvarint.Append(settings, 1)
data := quicvarint.Append(nil, 4) // type byte
@ -141,7 +141,7 @@ var _ = Describe("Frames", func() {
Expect(sf.Datagram).To(BeTrue())
})
It("rejects duplicate H3_DATAGRAM entries", func() {
It("rejects duplicate SETTINGS_H3_DATAGRAM entries", func() {
settings := quicvarint.Append(nil, settingDatagram)
settings = quicvarint.Append(settings, 1)
settings = quicvarint.Append(settings, settingDatagram)
@ -153,23 +153,67 @@ var _ = Describe("Frames", func() {
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingDatagram)))
})
It("rejects invalid values for the H3_DATAGRAM entry", func() {
It("rejects invalid values for the SETTINGS_H3_DATAGRAM entry", func() {
settings := quicvarint.Append(nil, settingDatagram)
settings = quicvarint.Append(settings, 1337)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
_, err := parseNextFrame(bytes.NewReader(data), nil)
Expect(err).To(MatchError("invalid value for H3_DATAGRAM: 1337"))
Expect(err).To(MatchError("invalid value for SETTINGS_H3_DATAGRAM: 1337"))
})
It("writes the H3_DATAGRAM setting", func() {
It("writes the SETTINGS_H3_DATAGRAM setting", func() {
sf := &settingsFrame{Datagram: true}
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
Expect(err).ToNot(HaveOccurred())
Expect(frame).To(Equal(sf))
})
})
Context("Extended Connect", func() {
It("reads the SETTINGS_ENABLE_CONNECT_PROTOCOL value", func() {
settings := quicvarint.Append(nil, settingExtendedConnect)
settings = quicvarint.Append(settings, 1)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
f, err := parseNextFrame(bytes.NewReader(data), nil)
Expect(err).ToNot(HaveOccurred())
Expect(f).To(BeAssignableToTypeOf(&settingsFrame{}))
sf := f.(*settingsFrame)
Expect(sf.ExtendedConnect).To(BeTrue())
})
It("rejects duplicate SETTINGS_ENABLE_CONNECT_PROTOCOL entries", func() {
settings := quicvarint.Append(nil, settingExtendedConnect)
settings = quicvarint.Append(settings, 1)
settings = quicvarint.Append(settings, settingExtendedConnect)
settings = quicvarint.Append(settings, 1)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
_, err := parseNextFrame(bytes.NewReader(data), nil)
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingExtendedConnect)))
})
It("rejects invalid values for the SETTINGS_ENABLE_CONNECT_PROTOCOL entry", func() {
settings := quicvarint.Append(nil, settingExtendedConnect)
settings = quicvarint.Append(settings, 1337)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
_, err := parseNextFrame(bytes.NewReader(data), nil)
Expect(err).To(MatchError("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337"))
})
It("writes the SETTINGS_ENABLE_CONNECT_PROTOCOL setting", func() {
sf := &settingsFrame{ExtendedConnect: true}
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
Expect(err).ToNot(HaveOccurred())
Expect(frame).To(Equal(sf))
})
})
})
Context("hijacking", func() {

View file

@ -126,9 +126,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
return nil, errors.New(":path, :authority and :method must not be empty")
}
if !isExtendedConnected && len(hdr.Protocol) > 0 {
return nil, errors.New(":protocol must be empty")
}
var u *url.URL
var requestURI string
var protocol string
protocol := "HTTP/3.0"
if isConnect {
u = &url.URL{}
@ -137,15 +142,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
if err != nil {
return nil, err
}
protocol = hdr.Protocol
} else {
u.Path = hdr.Path
}
u.Scheme = hdr.Scheme
u.Host = hdr.Authority
requestURI = hdr.Authority
protocol = hdr.Protocol
} else {
protocol = "HTTP/3.0"
u, err = url.ParseRequestURI(hdr.Path)
if err != nil {
return nil, fmt.Errorf("invalid content length: %w", err)

View file

@ -212,6 +212,17 @@ var _ = Describe("Request", func() {
Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
})
It("errors with invalid protocol", func() {
headers := []qpack.HeaderField{
{Name: ":path", Value: "/foo"},
{Name: ":authority", Value: "quic.clemente.io"},
{Name: ":method", Value: "GET"},
{Name: ":protocol", Value: "connect-udp"},
}
_, err := requestFromHeaders(headers)
Expect(err).To(MatchError(":protocol must be empty"))
})
Context("regular HTTP CONNECT", func() {
It("handles CONNECT method", func() {
headers := []qpack.HeaderField{
@ -221,6 +232,7 @@ var _ = Describe("Request", func() {
req, err := requestFromHeaders(headers)
Expect(err).NotTo(HaveOccurred())
Expect(req.Method).To(Equal(http.MethodConnect))
Expect(req.Proto).To(Equal("HTTP/3.0"))
Expect(req.RequestURI).To(Equal("quic.clemente.io"))
})

View file

@ -6,10 +6,9 @@ import (
"testing"
"time"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
func TestHttp3(t *testing.T) {

View file

@ -5,7 +5,6 @@ import (
"fmt"
quic "github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/internal/utils"
)
// A Stream is a HTTP/3 stream.
@ -115,7 +114,7 @@ func (s *lengthLimitedStream) Read(b []byte) (int, error) {
if err := s.checkContentLengthViolation(); err != nil {
return 0, err
}
n, err := s.stream.Read(b[:utils.Min(int64(len(b)), s.contentLength-s.read)])
n, err := s.stream.Read(b[:min(int64(len(b)), s.contentLength-s.read)])
s.read += int64(n)
if err := s.checkContentLengthViolation(); err != nil {
return n, err

View file

@ -7,9 +7,9 @@ import (
quic "github.com/refraction-networking/uquic"
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
func getDataFrame(data []byte) []byte {

View file

@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/refraction-networking/uquic/http3 (interfaces: QUICEarlyListener)
//
// Generated by this command:
//
// mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener
//
// Package http3 is a generated GoMock package.
package http3
@ -9,7 +14,7 @@ import (
net "net"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
gomock "go.uber.org/mock/gomock"
quic "github.com/refraction-networking/uquic"
)
@ -46,9 +51,33 @@ func (m *MockQUICEarlyListener) Accept(arg0 context.Context) (quic.EarlyConnecti
}
// Accept indicates an expected call of Accept.
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 interface{}) *gomock.Call {
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 any) *MockQUICEarlyListenerAcceptCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
return &MockQUICEarlyListenerAcceptCall{Call: call}
}
// MockQUICEarlyListenerAcceptCall wrap *gomock.Call
type MockQUICEarlyListenerAcceptCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockQUICEarlyListenerAcceptCall) Return(arg0 quic.EarlyConnection, arg1 error) *MockQUICEarlyListenerAcceptCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockQUICEarlyListenerAcceptCall) Do(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockQUICEarlyListenerAcceptCall) DoAndReturn(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Addr mocks base method.
@ -60,9 +89,33 @@ func (m *MockQUICEarlyListener) Addr() net.Addr {
}
// Addr indicates an expected call of Addr.
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *gomock.Call {
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *MockQUICEarlyListenerAddrCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
return &MockQUICEarlyListenerAddrCall{Call: call}
}
// MockQUICEarlyListenerAddrCall wrap *gomock.Call
type MockQUICEarlyListenerAddrCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockQUICEarlyListenerAddrCall) Return(arg0 net.Addr) *MockQUICEarlyListenerAddrCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockQUICEarlyListenerAddrCall) Do(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockQUICEarlyListenerAddrCall) DoAndReturn(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Close mocks base method.
@ -74,7 +127,31 @@ func (m *MockQUICEarlyListener) Close() error {
}
// Close indicates an expected call of Close.
func (mr *MockQUICEarlyListenerMockRecorder) Close() *gomock.Call {
func (mr *MockQUICEarlyListenerMockRecorder) Close() *MockQUICEarlyListenerCloseCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
return &MockQUICEarlyListenerCloseCall{Call: call}
}
// MockQUICEarlyListenerCloseCall wrap *gomock.Call
type MockQUICEarlyListenerCloseCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockQUICEarlyListenerCloseCall) Return(arg0 error) *MockQUICEarlyListenerCloseCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockQUICEarlyListenerCloseCall) Do(f func() error) *MockQUICEarlyListenerCloseCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockQUICEarlyListenerCloseCall) DoAndReturn(f func() error) *MockQUICEarlyListenerCloseCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View file

@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/refraction-networking/uquic/http3 (interfaces: RoundTripCloser)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser
//
// Package http3 is a generated GoMock package.
package http3
@ -8,7 +13,7 @@ import (
http "net/http"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
gomock "go.uber.org/mock/gomock"
)
// MockRoundTripCloser is a mock of RoundTripCloser interface.
@ -43,9 +48,33 @@ func (m *MockRoundTripCloser) Close() error {
}
// Close indicates an expected call of Close.
func (mr *MockRoundTripCloserMockRecorder) Close() *gomock.Call {
func (mr *MockRoundTripCloserMockRecorder) Close() *MockRoundTripCloserCloseCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
return &MockRoundTripCloserCloseCall{Call: call}
}
// MockRoundTripCloserCloseCall wrap *gomock.Call
type MockRoundTripCloserCloseCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockRoundTripCloserCloseCall) Return(arg0 error) *MockRoundTripCloserCloseCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockRoundTripCloserCloseCall) Do(f func() error) *MockRoundTripCloserCloseCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockRoundTripCloserCloseCall) DoAndReturn(f func() error) *MockRoundTripCloserCloseCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// HandshakeComplete mocks base method.
@ -57,9 +86,33 @@ func (m *MockRoundTripCloser) HandshakeComplete() bool {
}
// HandshakeComplete indicates an expected call of HandshakeComplete.
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *gomock.Call {
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *MockRoundTripCloserHandshakeCompleteCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
return &MockRoundTripCloserHandshakeCompleteCall{Call: call}
}
// MockRoundTripCloserHandshakeCompleteCall wrap *gomock.Call
type MockRoundTripCloserHandshakeCompleteCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockRoundTripCloserHandshakeCompleteCall) Return(arg0 bool) *MockRoundTripCloserHandshakeCompleteCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockRoundTripCloserHandshakeCompleteCall) Do(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockRoundTripCloserHandshakeCompleteCall) DoAndReturn(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// RoundTripOpt mocks base method.
@ -72,7 +125,31 @@ func (m *MockRoundTripCloser) RoundTripOpt(arg0 *http.Request, arg1 RoundTripOpt
}
// RoundTripOpt indicates an expected call of RoundTripOpt.
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 any) *MockRoundTripCloserRoundTripOptCall {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
return &MockRoundTripCloserRoundTripOptCall{Call: call}
}
// MockRoundTripCloserRoundTripOptCall wrap *gomock.Call
type MockRoundTripCloserRoundTripOptCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockRoundTripCloserRoundTripOptCall) Return(arg0 *http.Response, arg1 error) *MockRoundTripCloserRoundTripOptCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockRoundTripCloserRoundTripOptCall) Do(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockRoundTripCloserRoundTripOptCall) DoAndReturn(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View file

@ -2,7 +2,7 @@
package http3
//go:generate sh -c "go run github.com/golang/mock/mockgen -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/refraction-networking/uquic/http3 RoundTripCloser"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser"
type RoundTripCloser = roundTripCloser
//go:generate sh -c "go run github.com/golang/mock/mockgen -package http3 -destination mock_quic_early_listener_test.go github.com/refraction-networking/uquic/http3 QUICEarlyListener"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener"

View file

@ -8,8 +8,8 @@ import (
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/golang/mock/gomock"
"github.com/quic-go/qpack"
"go.uber.org/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

View file

@ -15,19 +15,62 @@ import (
"github.com/quic-go/qpack"
)
// The maximum length of an encoded HTTP/3 frame header is 16:
// The frame has a type and length field, both QUIC varints (maximum 8 bytes in length)
const frameHeaderLen = 16
// headerWriter wraps the stream, so that the first Write call flushes the header to the stream
type headerWriter struct {
str quic.Stream
header http.Header
status int // status code passed to WriteHeader
written bool
logger utils.Logger
}
// writeHeader encodes and flush header to the stream
func (hw *headerWriter) writeHeader() error {
var headers bytes.Buffer
enc := qpack.NewEncoder(&headers)
enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(hw.status)})
for k, v := range hw.header {
for index := range v {
enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]})
}
}
buf := make([]byte, 0, frameHeaderLen+headers.Len())
buf = (&headersFrame{Length: uint64(headers.Len())}).Append(buf)
hw.logger.Infof("Responding with %d", hw.status)
buf = append(buf, headers.Bytes()...)
_, err := hw.str.Write(buf)
return err
}
// first Write will trigger flushing header
func (hw *headerWriter) Write(p []byte) (int, error) {
if !hw.written {
if err := hw.writeHeader(); err != nil {
return 0, err
}
hw.written = true
}
return hw.str.Write(p)
}
type responseWriter struct {
*headerWriter
conn quic.Connection
str quic.Stream
bufferedStr *bufio.Writer
buf []byte
header http.Header
status int // status code passed to WriteHeader
headerWritten bool
contentLen int64 // if handler set valid Content-Length header
numWritten int64 // bytes written
logger utils.Logger
headerWritten bool
isHead bool
}
var (
@ -37,13 +80,16 @@ var (
)
func newResponseWriter(str quic.Stream, conn quic.Connection, logger utils.Logger) *responseWriter {
hw := &headerWriter{
str: str,
header: http.Header{},
logger: logger,
}
return &responseWriter{
header: http.Header{},
buf: make([]byte, 16),
conn: conn,
str: str,
bufferedStr: bufio.NewWriter(str),
logger: logger,
headerWriter: hw,
buf: make([]byte, frameHeaderLen),
conn: conn,
bufferedStr: bufio.NewWriter(hw),
}
}
@ -83,27 +129,8 @@ func (w *responseWriter) WriteHeader(status int) {
}
w.status = status
var headers bytes.Buffer
enc := qpack.NewEncoder(&headers)
enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
for k, v := range w.header {
for index := range v {
enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]})
}
}
w.buf = w.buf[:0]
w.buf = (&headersFrame{Length: uint64(headers.Len())}).Append(w.buf)
w.logger.Infof("Responding with %d", status)
if _, err := w.bufferedStr.Write(w.buf); err != nil {
w.logger.Errorf("could not write headers frame: %s", err.Error())
}
if _, err := w.bufferedStr.Write(headers.Bytes()); err != nil {
w.logger.Errorf("could not write header frame payload: %s", err.Error())
}
if !w.headerWritten {
w.Flush()
w.writeHeader()
}
}
@ -136,16 +163,30 @@ func (w *responseWriter) Write(p []byte) (int, error) {
return 0, http.ErrContentLength
}
if w.isHead {
return len(p), nil
}
df := &dataFrame{Length: uint64(len(p))}
w.buf = w.buf[:0]
w.buf = df.Append(w.buf)
if _, err := w.bufferedStr.Write(w.buf); err != nil {
return 0, err
return 0, maybeReplaceError(err)
}
return w.bufferedStr.Write(p)
n, err := w.bufferedStr.Write(p)
return n, maybeReplaceError(err)
}
func (w *responseWriter) FlushError() error {
if !w.headerWritten {
w.WriteHeader(http.StatusOK)
}
if !w.written {
if err := w.writeHeader(); err != nil {
return maybeReplaceError(err)
}
w.written = true
}
return w.bufferedStr.Flush()
}

View file

@ -9,11 +9,11 @@ import (
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/golang/mock/gomock"
"github.com/quic-go/qpack"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
var _ = Describe("Response Writer", func() {

View file

@ -17,6 +17,30 @@ import (
"golang.org/x/net/http/httpguts"
)
// Settings are HTTP/3 settings that apply to the underlying connection.
type Settings struct {
// Support for HTTP/3 datagrams (RFC 9297)
EnableDatagram bool
// Extended CONNECT, RFC 9220
EnableExtendedConnect bool
// Other settings, defined by the application
Other map[uint64]uint64
}
// RoundTripOpt are options for the Transport.RoundTripOpt method.
type RoundTripOpt struct {
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
OnlyCachedConn bool
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
// If set, context cancellations have no effect after the response headers are received.
DontCloseRequestStream bool
// CheckSettings is run before the request is sent to the server.
// If not yet received, it blocks until the server's SETTINGS frame is received.
// If an error is returned, the request won't be sent to the server, and the error is returned.
CheckSettings func(Settings) error
}
type roundTripCloser interface {
RoundTripOpt(*http.Request, RoundTripOpt) (*http.Response, error)
HandshakeComplete() bool
@ -50,9 +74,8 @@ type RoundTripper struct {
// If nil, reasonable default values will be used.
QuicConfig *quic.Config
// Enable support for HTTP/3 datagrams.
// If set to true, QuicConfig.EnableDatagram will be set.
// See https://www.ietf.org/archive/id/draft-schinazi-masque-h3-datagram-02.html.
// Enable support for HTTP/3 datagrams (RFC 9297).
// If a QuicConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
EnableDatagrams bool
// Additional HTTP/3 settings.
@ -89,16 +112,6 @@ type RoundTripper struct {
transport *quic.Transport
}
// RoundTripOpt are options for the Transport.RoundTripOpt method.
type RoundTripOpt struct {
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
OnlyCachedConn bool
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
// If set, context cancellations have no effect after the response headers are received.
DontCloseRequestStream bool
}
var (
_ http.RoundTripper = &RoundTripper{}
_ io.Closer = &RoundTripper{}
@ -204,6 +217,7 @@ func (r *RoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTr
MaxHeaderBytes: r.MaxResponseHeaderBytes,
StreamHijacker: r.StreamHijacker,
UniStreamHijacker: r.UniStreamHijacker,
AdditionalSettings: r.AdditionalSettings,
},
r.QuicConfig,
dial,

View file

@ -14,9 +14,9 @@ import (
"github.com/refraction-networking/uquic/internal/qerr"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
type mockBody struct {
@ -72,6 +72,21 @@ var _ = Describe("RoundTripper", func() {
Expect(err).To(MatchError(testErr))
})
It("creates new clients with additional settings", func() {
testErr := errors.New("test err")
req, err := http.NewRequest("GET", "https://quic.clemente.io/foobar.html", nil)
Expect(err).ToNot(HaveOccurred())
rt.AdditionalSettings = map[uint64]uint64{1337: 42}
rt.newClient = func(_ string, _ *tls.Config, opts *roundTripperOpts, conf *quic.Config, _ dialFunc) (roundTripCloser, error) {
cl := NewMockRoundTripCloser(mockCtrl)
cl.EXPECT().RoundTripOpt(gomock.Any(), gomock.Any()).Return(nil, testErr)
Expect(opts.AdditionalSettings).To(HaveKeyWithValue(uint64(1337), uint64(42)))
return cl, nil
}
_, err = rt.RoundTrip(req)
Expect(err).To(MatchError(testErr))
})
It("uses the quic.Config, if provided", func() {
config := &quic.Config{HandshakeIdleTimeout: time.Millisecond}
var receivedConfig *quic.Config
@ -85,6 +100,19 @@ var _ = Describe("RoundTripper", func() {
Expect(receivedConfig.HandshakeIdleTimeout).To(Equal(config.HandshakeIdleTimeout))
})
It("requires quic.Config.EnableDatagram if HTTP datagrams are enabled", func() {
rt.QuicConfig = &quic.Config{EnableDatagrams: false}
rt.Dial = func(_ context.Context, _ string, _ *tls.Config, config *quic.Config) (quic.EarlyConnection, error) {
return nil, errors.New("handshake error")
}
rt.EnableDatagrams = true
_, err := rt.RoundTrip(req)
Expect(err).To(MatchError("HTTP Datagrams enabled, but QUIC Datagrams disabled"))
rt.QuicConfig.EnableDatagrams = true
_, err = rt.RoundTrip(req)
Expect(err).To(MatchError("handshake error"))
})
It("uses the custom dialer, if provided", func() {
var dialed bool
dialer := func(_ context.Context, _ string, tlsCfgP *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {

View file

@ -9,8 +9,10 @@ import (
"net"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
tls "github.com/refraction-networking/utls"
@ -31,14 +33,11 @@ var (
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
return quic.ListenAddrEarly(addr, tlsConf, config)
}
errPanicked = errors.New("panicked")
)
const (
// NextProtoH3Draft29 is the ALPN protocol negotiated during the TLS handshake, for QUIC draft 29.
NextProtoH3Draft29 = "h3-29"
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
NextProtoH3 = "h3"
)
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
const NextProtoH3 = "h3"
// StreamType is the stream type of a unidirectional stream.
type StreamType uint64
@ -59,7 +58,7 @@ type QUICEarlyListener interface {
var _ QUICEarlyListener = &quic.EarlyListener{}
func versionToALPN(v protocol.VersionNumber) string {
func versionToALPN(v protocol.Version) string {
//nolint:exhaustive // These are all the versions we care about.
switch v {
case protocol.Version1, protocol.Version2:
@ -81,7 +80,7 @@ func ConfigureTLSConfig(tlsConf *tls.Config) *tls.Config {
// determine the ALPN from the QUIC version used
proto := NextProtoH3
val := ch.Context().Value(quic.QUICVersionContextKey)
if v, ok := val.(quic.VersionNumber); ok {
if v, ok := val.(quic.Version); ok {
proto = versionToALPN(v)
}
config := tlsConf
@ -120,6 +119,16 @@ func (k *contextKey) String() string { return "quic-go/http3 context value " + k
// type *http3.Server.
var ServerContextKey = &contextKey{"http3-server"}
// RemoteAddrContextKey is a context key. It can be used in
// HTTP handlers with Context.Value to access the remote
// address of the connection. The associated value will be of
// type net.Addr.
//
// Use this value instead of [http.Request.RemoteAddr] if you
// require access to the remote address of the connection rather
// than its string representation.
var RemoteAddrContextKey = &contextKey{"remote-addr"}
type requestError struct {
err error
streamErr ErrCode
@ -178,7 +187,7 @@ type Server struct {
// EnableDatagrams enables support for HTTP/3 datagrams.
// If set to true, QuicConfig.EnableDatagram will be set.
// See https://datatracker.ietf.org/doc/html/draft-ietf-masque-h3-datagram-07.
// See https://datatracker.ietf.org/doc/html/rfc9297.
EnableDatagrams bool
// MaxHeaderBytes controls the maximum number of bytes the server will
@ -205,6 +214,11 @@ type Server struct {
// In that case, the stream type will not be set.
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
// ConnContext optionally specifies a function that modifies
// the context used for a new connection c. The provided ctx
// has a ServerContextKey value.
ConnContext func(ctx context.Context, c quic.Connection) context.Context
mutex sync.RWMutex
listeners map[*QUICEarlyListener]listenerInfo
@ -278,7 +292,7 @@ func (s *Server) ServeListener(ln QUICEarlyListener) error {
}
go func() {
if err := s.handleConn(conn); err != nil {
s.logger.Debugf(err.Error())
s.logger.Debugf("handling connection failed: %s", err)
}
}()
}
@ -412,10 +426,11 @@ func (s *Server) addListener(l *QUICEarlyListener) error {
s.listeners = make(map[*QUICEarlyListener]listenerInfo)
}
if port, err := extractPort((*l).Addr().String()); err == nil {
laddr := (*l).Addr()
if port, err := extractPort(laddr.String()); err == nil {
s.listeners[l] = listenerInfo{port}
} else {
s.logger.Errorf("Unable to extract port from listener %+v, will not be announced using SetQuicHeaders: %s", err)
s.logger.Errorf("Unable to extract port from listener %s, will not be announced using SetQuicHeaders: %s", laddr, err)
s.listeners[l] = listenerInfo{}
}
s.generateAltSvcHeader()
@ -439,7 +454,11 @@ func (s *Server) handleConn(conn quic.Connection) error {
}
b := make([]byte, 0, 64)
b = quicvarint.Append(b, streamTypeControlStream) // stream type
b = (&settingsFrame{Datagram: s.EnableDatagrams, Other: s.AdditionalSettings}).Append(b)
b = (&settingsFrame{
Datagram: s.EnableDatagrams,
ExtendedConnect: true,
Other: s.AdditionalSettings,
}).Append(b)
str.Write(b)
go s.handleUnidirectionalStreams(conn)
@ -482,6 +501,8 @@ func (s *Server) handleConn(conn quic.Connection) error {
}
func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
var rcvdControlStream atomic.Bool
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
@ -515,6 +536,11 @@ func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
return
}
// Only a single control stream is allowed.
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
return
}
f, err := parseNextFrame(str, nil)
if err != nil {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
@ -620,8 +646,18 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
ctx := str.Context()
ctx = context.WithValue(ctx, ServerContextKey, s)
ctx = context.WithValue(ctx, http.LocalAddrContextKey, conn.LocalAddr())
ctx = context.WithValue(ctx, RemoteAddrContextKey, conn.RemoteAddr())
if s.ConnContext != nil {
ctx = s.ConnContext(ctx, conn)
if ctx == nil {
panic("http3: ConnContext returned nil")
}
}
req = req.WithContext(ctx)
r := newResponseWriter(str, conn, s.logger)
if req.Method == http.MethodHead {
r.isHead = true
}
handler := s.Handler
if handler == nil {
handler = http.DefaultServeMux
@ -651,11 +687,21 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
// only write response when there is no panic
if !panicked {
r.WriteHeader(http.StatusOK)
// response not written to the client yet, set Content-Length
if !r.written {
if _, haveCL := r.header["Content-Length"]; !haveCL {
r.header.Set("Content-Length", strconv.FormatInt(r.numWritten, 10))
}
}
r.Flush()
}
// If the EOF was read by the handler, CancelRead() is a no-op.
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
// abort the stream when there is a panic
if panicked {
return newStreamError(ErrCodeInternalError, errPanicked)
}
return requestError{}
}
@ -720,7 +766,7 @@ func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) er
return server.ListenAndServeTLS(certFile, keyFile)
}
// ListenAndServe listens on the given network address for both, TLS and QUIC
// ListenAndServe listens on the given network address for both TLS/TCP and QUIC
// connections in parallel. It returns if one of the two returns an error.
// http.DefaultServeMux is used when handler is nil.
// The correct Alt-Svc headers for QUIC are set.
@ -762,8 +808,8 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error
Handler: handler,
}
hErr := make(chan error)
qErr := make(chan error)
hErr := make(chan error, 1)
qErr := make(chan error, 1)
go func() {
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
quicServer.SetQuicHeaders(w.Header())

View file

@ -17,12 +17,13 @@ import (
quic "github.com/refraction-networking/uquic"
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qerr"
"github.com/refraction-networking/uquic/internal/testdata"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/quicvarint"
"github.com/golang/mock/gomock"
"github.com/quic-go/qpack"
"go.uber.org/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -68,11 +69,15 @@ var _ = Describe("Server", func() {
s *Server
origQuicListenAddr = quicListenAddr
)
type testConnContextKey string
BeforeEach(func() {
s = &Server{
TLSConfig: testdata.GetTLSConfig(),
logger: utils.DefaultLogger,
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
return context.WithValue(ctx, testConnContextKey("test"), c)
},
}
origQuicListenAddr = quicListenAddr
})
@ -164,6 +169,7 @@ var _ = Describe("Server", func() {
Expect(req.Host).To(Equal("www.example.com"))
Expect(req.RemoteAddr).To(Equal("127.0.0.1:1337"))
Expect(req.Context().Value(ServerContextKey)).To(Equal(s))
Expect(req.Context().Value(testConnContextKey("test"))).ToNot(Equal(nil))
})
It("returns 200 with an empty handler", func() {
@ -181,6 +187,86 @@ var _ = Describe("Server", func() {
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
})
It("sets Content-Length when the handler doesn't flush to the client", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foobar"))
})
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(exampleGetRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(hfs).To(HaveKeyWithValue("content-length", []string{"6"}))
// status, content-length, date, content-type
Expect(hfs).To(HaveLen(4))
})
It("not sets Content-Length when the handler flushes to the client", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foobar"))
// force flush
w.(http.Flusher).Flush()
})
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(exampleGetRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
// status, date, content-type
Expect(hfs).To(HaveLen(3))
})
It("response to HEAD request should not have body", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foobar"))
})
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
Expect(err).ToNot(HaveOccurred())
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(headRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(responseBuf.Bytes()).To(HaveLen(0))
})
It("response to HEAD request should also do content sniffing", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html></html>"))
})
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
Expect(err).ToNot(HaveOccurred())
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(headRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(hfs).To(HaveKeyWithValue("content-length", []string{"13"}))
Expect(hfs).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"}))
})
It("handles a aborting handler", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic(http.ErrAbortHandler)
@ -193,7 +279,7 @@ var _ = Describe("Server", func() {
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
Expect(serr.err).To(MatchError(errPanicked))
Expect(responseBuf.Bytes()).To(HaveLen(0))
})
@ -209,7 +295,7 @@ var _ = Describe("Server", func() {
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
Expect(serr.err).To(MatchError(errPanicked))
Expect(responseBuf.Bytes()).To(HaveLen(0))
})
@ -413,7 +499,7 @@ var _ = Describe("Server", func() {
Context("control stream handling", func() {
var conn *mockquic.MockEarlyConnection
testDone := make(chan struct{})
testDone := make(chan struct{}, 1)
BeforeEach(func() {
conn = mockquic.NewMockEarlyConnection(mockCtrl)
@ -444,6 +530,34 @@ var _ = Describe("Server", func() {
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
})
It("rejects duplicate control streams", func() {
b := quicvarint.Append(nil, streamTypeControlStream)
b = (&settingsFrame{}).Append(b)
r1 := bytes.NewReader(b)
controlStr1 := mockquic.NewMockStream(mockCtrl)
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
r2 := bytes.NewReader(b)
controlStr2 := mockquic.NewMockStream(mockCtrl)
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
done := make(chan struct{})
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
close(done)
return nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr1, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr2, nil
})
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-done
return nil, errors.New("test done")
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
})
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
streamType := t
name := "encoder"
@ -473,9 +587,7 @@ var _ = Describe("Server", func() {
str := mockquic.NewMockStream(mockCtrl)
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
done := make(chan struct{})
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
close(done)
})
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return str, nil
@ -502,10 +614,9 @@ var _ = Describe("Server", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -525,10 +636,9 @@ var _ = Describe("Server", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -548,10 +658,9 @@ var _ = Describe("Server", func() {
return nil, errors.New("test done")
})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeStreamCreationError))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -573,11 +682,9 @@ var _ = Describe("Server", func() {
})
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
Expect(reason).To(Equal("missing QUIC Datagram support"))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -623,7 +730,7 @@ var _ = Describe("Server", func() {
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeNoError))
str.EXPECT().Close().Do(func() { close(done) })
str.EXPECT().Close().Do(func() error { close(done); return nil })
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -698,9 +805,9 @@ var _ = Describe("Server", func() {
}).AnyTimes()
done := make(chan struct{})
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
Expect(code).To(Equal(quic.ApplicationErrorCode(ErrCodeFrameUnexpected)))
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
close(done)
return nil
})
s.handleConn(conn)
Eventually(done).Should(BeClosed())
@ -778,7 +885,7 @@ var _ = Describe("Server", func() {
Context("setting http headers", func() {
BeforeEach(func() {
s.QuicConfig = &quic.Config{Versions: []protocol.VersionNumber{protocol.Version1}}
s.QuicConfig = &quic.Config{Versions: []protocol.Version{protocol.Version1}}
})
var ln1 QUICEarlyListener
@ -839,7 +946,7 @@ var _ = Describe("Server", func() {
})
It("works if the quic.Config sets QUIC versions", func() {
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version2}
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version2}
addListener(":443", &ln1)
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
removeListener(&ln1)
@ -878,7 +985,7 @@ var _ = Describe("Server", func() {
})
It("doesn't duplicate Alt-Svc values", func() {
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version1}
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version1}
addListener(":443", &ln1)
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
removeListener(&ln1)
@ -919,7 +1026,7 @@ var _ = Describe("Server", func() {
Context("ConfigureTLSConfig", func() {
It("advertises v1 by default", func() {
conf := ConfigureTLSConfig(testdata.GetTLSConfig())
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.Version{quic.Version1}})
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
@ -947,7 +1054,7 @@ var _ = Describe("Server", func() {
},
}
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
@ -960,7 +1067,7 @@ var _ = Describe("Server", func() {
tlsConf := testdata.GetTLSConfig()
tlsConf.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return nil, nil }
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
@ -978,7 +1085,7 @@ var _ = Describe("Server", func() {
},
}
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
@ -1010,7 +1117,7 @@ var _ = Describe("Server", func() {
}
stopAccept := make(chan struct{})
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept
return nil, errors.New("closed")
})
@ -1023,7 +1130,7 @@ var _ = Describe("Server", func() {
}()
Consistently(done).ShouldNot(BeClosed())
ln.EXPECT().Close().Do(func() { close(stopAccept) })
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
Expect(s.Close()).To(Succeed())
Eventually(done).Should(BeClosed())
})
@ -1045,13 +1152,13 @@ var _ = Describe("Server", func() {
}
stopAccept1 := make(chan struct{})
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept1
return nil, errors.New("closed")
})
ln1.EXPECT().Addr() // generate alt-svc headers
stopAccept2 := make(chan struct{})
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept2
return nil, errors.New("closed")
})
@ -1072,8 +1179,8 @@ var _ = Describe("Server", func() {
Consistently(done1).ShouldNot(BeClosed())
Expect(done2).ToNot(BeClosed())
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
Expect(s.Close()).To(Succeed())
Eventually(done1).Should(BeClosed())
Eventually(done2).Should(BeClosed())
@ -1098,7 +1205,7 @@ var _ = Describe("Server", func() {
s := &Server{}
stopAccept := make(chan struct{})
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept
return nil, errors.New("closed")
})
@ -1112,7 +1219,7 @@ var _ = Describe("Server", func() {
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
Consistently(done).ShouldNot(BeClosed())
ln.EXPECT().Close().Do(func() { close(stopAccept) })
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
Expect(s.Close()).To(Succeed())
Eventually(done).Should(BeClosed())
})
@ -1132,13 +1239,13 @@ var _ = Describe("Server", func() {
s := &Server{}
stopAccept1 := make(chan struct{})
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept1
return nil, errors.New("closed")
})
ln1.EXPECT().Addr() // generate alt-svc headers
stopAccept2 := make(chan struct{})
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
<-stopAccept2
return nil, errors.New("closed")
})
@ -1160,8 +1267,8 @@ var _ = Describe("Server", func() {
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
Consistently(done1).ShouldNot(BeClosed())
Expect(done2).ToNot(BeClosed())
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
Expect(s.Close()).To(Succeed())
Eventually(done1).Should(BeClosed())
Eventually(done2).Should(BeClosed())

View file

@ -1,8 +1,29 @@
module test
go 1.16
go 1.21
// The version doesn't matter here, as we're replacing it with the currently checked out code anyway.
require github.com/refraction-networking/uquic v0.21.0
require github.com/refraction-networking/uquic v0.0.5
replace github.com/refraction-networking/uquic => ../../
require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/gaukas/clienthellod v0.4.2 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/refraction-networking/utls v1.6.4 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)
replace github.com/quic-go/quic-go => ../../

View file

@ -1,369 +1,76 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/gaukas/clienthellod v0.4.2 h1:LPJ+LSeqt99pqeCV4C0cllk+pyWmERisP7w6qWr7eqE=
github.com/gaukas/clienthellod v0.4.2/go.mod h1:M57+dsu0ZScvmdnNxaxsDPM46WhSEdPYAOdNgfL7IKA=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=
github.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/refraction-networking/uquic v0.0.5 h1:Ldk4nY6KaxI0PtMDGelzesdmZSuMywY9DOmqKsH7AV8=
github.com/refraction-networking/uquic v0.0.5/go.mod h1:ts5LSEq8Cn34V5CyeJqGkRTwBGI8Ij1ckJQ8K4YEUig=
github.com/refraction-networking/utls v1.6.4 h1:aeynTroaYn7y+mFtqv8D0bQ4bw0y9nJHneGxJ7lvRDM=
github.com/refraction-networking/utls v1.6.4/go.mod h1:2VL2xfiqgFAZtJKeUTlf+PSYFs3Eu7km0gCtXJ3m8zs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View file

@ -31,7 +31,7 @@ var _ = Describe("Stream Cancellations", func() {
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
var canceledCounter int32
var canceledCounter atomic.Int32
go func() {
defer GinkgoRecover()
var wg sync.WaitGroup
@ -50,18 +50,18 @@ var _ = Describe("Stream Cancellations", func() {
ErrorCode: quic.StreamErrorCode(str.StreamID()),
Remote: true,
}))
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
return
}
if err := str.Close(); err != nil {
Expect(err).To(MatchError(fmt.Sprintf("close called for canceled stream %d", str.StreamID())))
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
return
}
}()
}
wg.Wait()
numCanceledStreamsChan <- atomic.LoadInt32(&canceledCounter)
numCanceledStreamsChan <- canceledCounter.Load()
}()
return numCanceledStreamsChan
}
@ -80,7 +80,7 @@ var _ = Describe("Stream Cancellations", func() {
)
Expect(err).ToNot(HaveOccurred())
var canceledCounter int32
var canceledCounter atomic.Int32
var wg sync.WaitGroup
wg.Add(numStreams)
for i := 0; i < numStreams; i++ {
@ -91,7 +91,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
// cancel around 2/3 of the streams
if rand.Int31()%3 != 0 {
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
resetErr := quic.StreamErrorCode(str.StreamID())
str.CancelRead(resetErr)
_, err := str.Read([]byte{0})
@ -113,7 +113,7 @@ var _ = Describe("Stream Cancellations", func() {
Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
Expect(conn.CloseWithError(0, "")).To(Succeed())
clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
clientCanceledCounter := canceledCounter.Load()
// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
@ -132,7 +132,7 @@ var _ = Describe("Stream Cancellations", func() {
)
Expect(err).ToNot(HaveOccurred())
var canceledCounter int32
var canceledCounter atomic.Int32
var wg sync.WaitGroup
wg.Add(numStreams)
for i := 0; i < numStreams; i++ {
@ -148,7 +148,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
str.CancelRead(quic.StreamErrorCode(str.StreamID()))
Expect(data).To(Equal(PRData[:length]))
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
return
}
data, err := io.ReadAll(str)
@ -162,7 +162,7 @@ var _ = Describe("Stream Cancellations", func() {
Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
Expect(conn.CloseWithError(0, "")).To(Succeed())
clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
clientCanceledCounter := canceledCounter.Load()
// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
@ -185,7 +185,7 @@ var _ = Describe("Stream Cancellations", func() {
var wg sync.WaitGroup
wg.Add(numStreams)
var counter int32
var counter atomic.Int32
for i := 0; i < numStreams; i++ {
go func() {
defer GinkgoRecover()
@ -199,7 +199,7 @@ var _ = Describe("Stream Cancellations", func() {
defer close(done)
b := make([]byte, 32)
if _, err := str.Read(b); err != nil {
atomic.AddInt32(&counter, 1)
counter.Add(1)
Expect(err).To(Equal(&quic.StreamError{
StreamID: str.StreamID(),
ErrorCode: 1234,
@ -214,7 +214,7 @@ var _ = Describe("Stream Cancellations", func() {
}
wg.Wait()
Expect(conn.CloseWithError(0, "")).To(Succeed())
numCanceled := atomic.LoadInt32(&counter)
numCanceled := counter.Load()
fmt.Fprintf(GinkgoWriter, "canceled %d out of %d streams", numCanceled, numStreams)
Expect(numCanceled).ToNot(BeZero())
Eventually(serverCanceledCounterChan).Should(Receive())
@ -232,7 +232,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
var wg sync.WaitGroup
var counter int32
var counter atomic.Int32
wg.Add(numStreams)
for i := 0; i < numStreams; i++ {
go func() {
@ -242,7 +242,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
data, err := io.ReadAll(str)
if err != nil {
atomic.AddInt32(&counter, 1)
counter.Add(1)
Expect(err).To(MatchError(&quic.StreamError{
StreamID: str.StreamID(),
ErrorCode: quic.StreamErrorCode(str.StreamID()),
@ -254,7 +254,7 @@ var _ = Describe("Stream Cancellations", func() {
}
wg.Wait()
streamCount := atomic.LoadInt32(&counter)
streamCount := counter.Load()
fmt.Fprintf(GinkgoWriter, "Canceled writing on %d of %d streams\n", streamCount, numStreams)
Expect(streamCount).To(BeNumerically(">", numStreams/10))
Expect(numStreams - streamCount).To(BeNumerically(">", numStreams/10))
@ -267,7 +267,7 @@ var _ = Describe("Stream Cancellations", func() {
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
Expect(err).ToNot(HaveOccurred())
var canceledCounter int32
var canceledCounter atomic.Int32
go func() {
defer GinkgoRecover()
conn, err := server.Accept(context.Background())
@ -280,7 +280,7 @@ var _ = Describe("Stream Cancellations", func() {
// cancel about 2/3 of the streams
if rand.Int31()%3 != 0 {
str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
return
}
_, err = str.Write(PRData)
@ -291,14 +291,14 @@ var _ = Describe("Stream Cancellations", func() {
}()
clientCanceledStreams := runClient(server)
Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
})
It("downloads when the server cancels some streams after sending some data", func() {
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
Expect(err).ToNot(HaveOccurred())
var canceledCounter int32
var canceledCounter atomic.Int32
go func() {
defer GinkgoRecover()
conn, err := server.Accept(context.Background())
@ -314,7 +314,7 @@ var _ = Describe("Stream Cancellations", func() {
_, err = str.Write(PRData[:length])
Expect(err).ToNot(HaveOccurred())
str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
atomic.AddInt32(&canceledCounter, 1)
canceledCounter.Add(1)
return
}
_, err = str.Write(PRData)
@ -325,7 +325,7 @@ var _ = Describe("Stream Cancellations", func() {
}()
clientCanceledStreams := runClient(server)
Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
})
})
@ -378,7 +378,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
var wg sync.WaitGroup
var counter int32
var counter atomic.Int32
wg.Add(numStreams)
for i := 0; i < numStreams; i++ {
go func() {
@ -399,13 +399,13 @@ var _ = Describe("Stream Cancellations", func() {
}))
return
}
atomic.AddInt32(&counter, 1)
counter.Add(1)
Expect(data).To(Equal(PRData))
}()
}
wg.Wait()
count := atomic.LoadInt32(&counter)
count := counter.Load()
Expect(count).To(BeNumerically(">", numStreams/15))
fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
@ -464,7 +464,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
var wg sync.WaitGroup
var counter int32
var counter atomic.Int32
wg.Add(numStreams)
for i := 0; i < numStreams; i++ {
go func() {
@ -495,14 +495,14 @@ var _ = Describe("Stream Cancellations", func() {
return
}
atomic.AddInt32(&counter, 1)
counter.Add(1)
Expect(data).To(Equal(PRData))
}()
}
wg.Wait()
Eventually(done).Should(BeClosed())
count := atomic.LoadInt32(&counter)
count := counter.Load()
Expect(count).To(BeNumerically(">", numStreams/15))
fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
@ -543,7 +543,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
var numToAccept int
var counter int32
var counter atomic.Int32
var wg sync.WaitGroup
wg.Add(numStreams)
for numToAccept < numStreams {
@ -561,7 +561,7 @@ var _ = Describe("Stream Cancellations", func() {
str, err := conn.AcceptUniStream(ctx)
if err != nil {
if err.Error() == "context canceled" {
atomic.AddInt32(&counter, 1)
counter.Add(1)
}
return
}
@ -573,7 +573,7 @@ var _ = Describe("Stream Cancellations", func() {
}
wg.Wait()
count := atomic.LoadInt32(&counter)
count := counter.Load()
fmt.Fprintf(GinkgoWriter, "Canceled AcceptStream %d times\n", count)
Expect(count).To(BeNumerically(">", numStreams/2))
Expect(conn.CloseWithError(0, "")).To(Succeed())
@ -589,7 +589,7 @@ var _ = Describe("Stream Cancellations", func() {
Expect(err).ToNot(HaveOccurred())
msg := make(chan struct{}, 1)
var numCanceled int32
var numCanceled atomic.Int32
go func() {
defer GinkgoRecover()
defer close(msg)
@ -603,7 +603,7 @@ var _ = Describe("Stream Cancellations", func() {
str, err := conn.OpenUniStreamSync(ctx)
if err != nil {
Expect(err).To(MatchError(context.DeadlineExceeded))
atomic.AddInt32(&numCanceled, 1)
numCanceled.Add(1)
select {
case msg <- struct{}{}:
default:
@ -644,7 +644,7 @@ var _ = Describe("Stream Cancellations", func() {
}
wg.Wait()
count := atomic.LoadInt32(&numCanceled)
count := numCanceled.Load()
fmt.Fprintf(GinkgoWriter, "Canceled OpenStreamSync %d times\n", count)
Expect(count).To(BeNumerically(">=", numStreams-maxIncomingStreams))
Expect(conn.CloseWithError(0, "")).To(Succeed())

View file

@ -50,6 +50,7 @@ var _ = Describe("Connection ID lengths tests", func() {
ConnectionIDLength: connIDLen,
ConnectionIDGenerator: connIDGenerator,
}
addTracer(tr)
ln, err := tr.Listen(getTLSConfig(), getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
go func() {
@ -92,6 +93,7 @@ var _ = Describe("Connection ID lengths tests", func() {
ConnectionIDLength: connIDLen,
ConnectionIDGenerator: connIDGenerator,
}
addTracer(tr)
defer tr.Close()
cl, err := tr.Dial(
context.Background(),

View file

@ -19,11 +19,12 @@ import (
)
var _ = Describe("Datagram test", func() {
const num = 100
const concurrentSends = 100
const maxDatagramSize = 250
var (
serverConn, clientConn *net.UDPConn
dropped, total int32
dropped, total atomic.Int32
)
startServerAndProxy := func(enableDatagram, expectDatagramSupport bool) (port int, closeFn func()) {
@ -47,19 +48,24 @@ var _ = Describe("Datagram test", func() {
if expectDatagramSupport {
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
if enableDatagram {
f := &wire.DatagramFrame{DataLenPresent: true}
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
wg.Add(concurrentSends)
for i := 0; i < concurrentSends; i++ {
go func(i int) {
defer GinkgoRecover()
defer wg.Done()
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(i))
Expect(conn.SendMessage(b)).To(Succeed())
Expect(conn.SendDatagram(b)).To(Succeed())
}(i)
}
maxDatagramMessageSize := f.MaxDataLen(maxDatagramSize, conn.ConnectionState().Version)
b := make([]byte, maxDatagramMessageSize+1)
Expect(conn.SendDatagram(b)).To(MatchError(&quic.DatagramTooLargeError{
PeerMaxDatagramFrameSize: int64(maxDatagramMessageSize),
}))
wg.Wait()
}
} else {
@ -81,9 +87,9 @@ var _ = Describe("Datagram test", func() {
}
drop := mrand.Int()%10 == 0
if drop {
atomic.AddInt32(&dropped, 1)
dropped.Add(1)
}
atomic.AddInt32(&total, 1)
total.Add(1)
return drop
},
})
@ -103,6 +109,8 @@ var _ = Describe("Datagram test", func() {
})
It("sends datagrams", func() {
oldMaxDatagramSize := wire.MaxDatagramSize
wire.MaxDatagramSize = maxDatagramSize
proxyPort, close := startServerAndProxy(true, true)
defer close()
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
@ -120,22 +128,23 @@ var _ = Describe("Datagram test", func() {
for {
// Close the connection if no message is received for 100 ms.
timer := time.AfterFunc(scaleDuration(100*time.Millisecond), func() { conn.CloseWithError(0, "") })
if _, err := conn.ReceiveMessage(context.Background()); err != nil {
if _, err := conn.ReceiveDatagram(context.Background()); err != nil {
break
}
timer.Stop()
counter++
}
numDropped := int(atomic.LoadInt32(&dropped))
expVal := num - numDropped
fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, atomic.LoadInt32(&total))
fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, num)
numDropped := int(dropped.Load())
expVal := concurrentSends - numDropped
fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, total.Load())
fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, concurrentSends)
Expect(counter).To(And(
BeNumerically(">", expVal*9/10),
BeNumerically("<", num),
BeNumerically("<", concurrentSends),
))
Eventually(conn.Context().Done).Should(BeClosed())
wire.MaxDatagramSize = oldMaxDatagramSize
})
It("server can disable datagram", func() {
@ -170,7 +179,7 @@ var _ = Describe("Datagram test", func() {
Expect(err).ToNot(HaveOccurred())
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
Expect(conn.SendMessage([]byte{0})).To(HaveOccurred())
Expect(conn.SendDatagram([]byte{0})).To(HaveOccurred())
close()
conn.CloseWithError(0, "")

View file

@ -13,7 +13,7 @@ import (
)
var _ = Describe("Stream deadline tests", func() {
setup := func() (*quic.Listener, quic.Stream, quic.Stream) {
setup := func() (serverStr, clientStr quic.Stream, close func()) {
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
strChan := make(chan quic.SendStream)
@ -35,19 +35,21 @@ var _ = Describe("Stream deadline tests", func() {
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
clientStr, err := conn.OpenStream()
clientStr, err = conn.OpenStream()
Expect(err).ToNot(HaveOccurred())
_, err = clientStr.Write([]byte{0}) // need to write one byte so the server learns about the stream
Expect(err).ToNot(HaveOccurred())
var serverStr quic.Stream
Eventually(strChan).Should(Receive(&serverStr))
return server, serverStr, clientStr
return serverStr, clientStr, func() {
Expect(server.Close()).To(Succeed())
Expect(conn.CloseWithError(0, "")).To(Succeed())
}
}
Context("read deadlines", func() {
It("completes a transfer when the deadline is set", func() {
server, serverStr, clientStr := setup()
defer server.Close()
serverStr, clientStr, closeFn := setup()
defer closeFn()
const timeout = time.Millisecond
done := make(chan struct{})
@ -81,8 +83,8 @@ var _ = Describe("Stream deadline tests", func() {
})
It("completes a transfer when the deadline is set concurrently", func() {
server, serverStr, clientStr := setup()
defer server.Close()
serverStr, clientStr, closeFn := setup()
defer closeFn()
const timeout = time.Millisecond
go func() {
@ -131,8 +133,8 @@ var _ = Describe("Stream deadline tests", func() {
Context("write deadlines", func() {
It("completes a transfer when the deadline is set", func() {
server, serverStr, clientStr := setup()
defer server.Close()
serverStr, clientStr, closeFn := setup()
defer closeFn()
const timeout = time.Millisecond
done := make(chan struct{})
@ -164,8 +166,8 @@ var _ = Describe("Stream deadline tests", func() {
})
It("completes a transfer when the deadline is set concurrently", func() {
server, serverStr, clientStr := setup()
defer server.Close()
serverStr, clientStr, closeFn := setup()
defer closeFn()
const timeout = time.Millisecond
readDone := make(chan struct{})

View file

@ -67,14 +67,14 @@ var _ = Describe("Drop Tests", func() {
fmt.Fprintf(GinkgoWriter, "Dropping packets for %s, after a delay of %s\n", dropDuration, dropDelay)
startTime := time.Now()
var numDroppedPackets int32
var numDroppedPackets atomic.Int32
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
if !d.Is(direction) {
return false
}
drop := time.Now().After(startTime.Add(dropDelay)) && time.Now().Before(startTime.Add(dropDelay).Add(dropDuration))
if drop {
atomic.AddInt32(&numDroppedPackets, 1)
numDroppedPackets.Add(1)
}
return drop
})
@ -114,7 +114,7 @@ var _ = Describe("Drop Tests", func() {
Expect(b[0]).To(Equal(i))
}
close(done)
numDropped := atomic.LoadInt32(&numDroppedPackets)
numDropped := numDroppedPackets.Load()
fmt.Fprintf(GinkgoWriter, "Dropped %d packets.\n", numDropped)
Expect(numDropped).To(BeNumerically(">", 0))
})

View file

@ -1,21 +0,0 @@
//go:build go1.19 && !go1.20
package self_test
import (
"errors"
"net/http"
"time"
)
const go120 = false
var errNotSupported = errors.New("not supported")
func setReadDeadline(w http.ResponseWriter, deadline time.Time) error {
return errNotSupported
}
func setWriteDeadline(w http.ResponseWriter, deadline time.Time) error {
return errNotSupported
}

View file

@ -1,22 +0,0 @@
//go:build go1.20
package self_test
import (
"net/http"
"time"
)
const go120 = true
func setReadDeadline(w http.ResponseWriter, deadline time.Time) error {
rc := http.NewResponseController(w)
return rc.SetReadDeadline(deadline)
}
func setWriteDeadline(w http.ResponseWriter, deadline time.Time) error {
rc := http.NewResponseController(w)
return rc.SetWriteDeadline(deadline)
}

View file

@ -10,13 +10,12 @@ import (
"sync/atomic"
"time"
quic "github.com/refraction-networking/uquic"
tls "github.com/refraction-networking/utls"
"github.com/refraction-networking/uquic/quicvarint"
quic "github.com/refraction-networking/uquic"
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
"github.com/refraction-networking/uquic/internal/wire"
"github.com/refraction-networking/uquic/quicvarint"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -27,23 +26,17 @@ var directions = []quicproxy.Direction{quicproxy.DirectionIncoming, quicproxy.Di
type applicationProtocol struct {
name string
run func()
run func(ln *quic.Listener, port int)
}
var _ = Describe("Handshake drop tests", func() {
var (
proxy *quicproxy.QuicProxy
ln *quic.Listener
)
data := GeneratePRData(5000)
const timeout = 2 * time.Minute
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) {
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) (ln *quic.Listener, proxyPort int, closeFn func()) {
conf := getQuicConfig(&quic.Config{
MaxIdleTimeout: timeout,
HandshakeIdleTimeout: timeout,
RequireAddressValidation: func(net.Addr) bool { return doRetry },
MaxIdleTimeout: timeout,
HandshakeIdleTimeout: timeout,
})
var tlsConf *tls.Config
if longCertChain {
@ -51,11 +44,18 @@ var _ = Describe("Handshake drop tests", func() {
} else {
tlsConf = getTLSConfig()
}
var err error
ln, err = quic.ListenAddr("localhost:0", tlsConf, conf)
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
conn, err := net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
tr := &quic.Transport{Conn: conn}
if doRetry {
tr.VerifySourceAddress = func(net.Addr) bool { return true }
}
ln, err = tr.Listen(tlsConf, conf)
Expect(err).ToNot(HaveOccurred())
serverPort := ln.Addr().(*net.UDPAddr).Port
proxy, err = quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
DropPacket: dropCallback,
DelayPacket: func(dir quicproxy.Direction, packet []byte) time.Duration {
@ -63,11 +63,18 @@ var _ = Describe("Handshake drop tests", func() {
},
})
Expect(err).ToNot(HaveOccurred())
return ln, proxy.LocalPort(), func() {
ln.Close()
tr.Close()
conn.Close()
proxy.Close()
}
}
clientSpeaksFirst := &applicationProtocol{
name: "client speaks first",
run: func() {
run: func(ln *quic.Listener, port int) {
serverConnChan := make(chan quic.Connection)
go func() {
defer GinkgoRecover()
@ -83,7 +90,7 @@ var _ = Describe("Handshake drop tests", func() {
}()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
fmt.Sprintf("localhost:%d", port),
getTLSClientConfig(),
getQuicConfig(&quic.Config{
MaxIdleTimeout: timeout,
@ -106,7 +113,7 @@ var _ = Describe("Handshake drop tests", func() {
serverSpeaksFirst := &applicationProtocol{
name: "server speaks first",
run: func() {
run: func(ln *quic.Listener, port int) {
serverConnChan := make(chan quic.Connection)
go func() {
defer GinkgoRecover()
@ -121,7 +128,7 @@ var _ = Describe("Handshake drop tests", func() {
}()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
fmt.Sprintf("localhost:%d", port),
getTLSClientConfig(),
getQuicConfig(&quic.Config{
MaxIdleTimeout: timeout,
@ -144,7 +151,7 @@ var _ = Describe("Handshake drop tests", func() {
nobodySpeaks := &applicationProtocol{
name: "nobody speaks",
run: func() {
run: func(ln *quic.Listener, port int) {
serverConnChan := make(chan quic.Connection)
go func() {
defer GinkgoRecover()
@ -154,7 +161,7 @@ var _ = Describe("Handshake drop tests", func() {
}()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
fmt.Sprintf("localhost:%d", port),
getTLSClientConfig(),
getQuicConfig(&quic.Config{
MaxIdleTimeout: timeout,
@ -170,11 +177,6 @@ var _ = Describe("Handshake drop tests", func() {
},
}
AfterEach(func() {
Expect(ln.Close()).To(Succeed())
Expect(proxy.Close()).To(Succeed())
})
for _, d := range directions {
direction := d
@ -195,35 +197,37 @@ var _ = Describe("Handshake drop tests", func() {
Context(app.name, func() {
It(fmt.Sprintf("establishes a connection when the first packet is lost in %s direction", direction), func() {
var incoming, outgoing int32
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
var incoming, outgoing atomic.Int32
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
var p int32
//nolint:exhaustive
switch d {
case quicproxy.DirectionIncoming:
p = atomic.AddInt32(&incoming, 1)
p = incoming.Add(1)
case quicproxy.DirectionOutgoing:
p = atomic.AddInt32(&outgoing, 1)
p = outgoing.Add(1)
}
return p == 1 && d.Is(direction)
}, doRetry, longCertChain)
app.run()
defer closeFn()
app.run(ln, proxyPort)
})
It(fmt.Sprintf("establishes a connection when the second packet is lost in %s direction", direction), func() {
var incoming, outgoing int32
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
var incoming, outgoing atomic.Int32
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
var p int32
//nolint:exhaustive
switch d {
case quicproxy.DirectionIncoming:
p = atomic.AddInt32(&incoming, 1)
p = incoming.Add(1)
case quicproxy.DirectionOutgoing:
p = atomic.AddInt32(&outgoing, 1)
p = outgoing.Add(1)
}
return p == 2 && d.Is(direction)
}, doRetry, longCertChain)
app.run()
defer closeFn()
app.run(ln, proxyPort)
})
It(fmt.Sprintf("establishes a connection when 1/3 of the packets are lost in %s direction", direction), func() {
@ -231,7 +235,7 @@ var _ = Describe("Handshake drop tests", func() {
var mx sync.Mutex
var incoming, outgoing int
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
drop := mrand.Int63n(int64(3)) == 0
mx.Lock()
@ -261,7 +265,8 @@ var _ = Describe("Handshake drop tests", func() {
}
return drop
}, doRetry, longCertChain)
app.run()
defer closeFn()
app.run(ln, proxyPort)
})
})
}
@ -282,13 +287,14 @@ var _ = Describe("Handshake drop tests", func() {
uint64(27+31*(1000+mrand.Int63()/31)) % quicvarint.Max: b,
}
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
if d == quicproxy.DirectionOutgoing {
return false
}
return mrand.Intn(3) == 0
}, false, false)
clientSpeaksFirst.run()
defer closeFn()
clientSpeaksFirst.run(ln, proxyPort)
})
}
})

View file

@ -55,9 +55,19 @@ var _ = Describe("Handshake RTT tests", func() {
// 1 RTT for verifying the source address
// 1 RTT for the TLS handshake
It("is forward-secure after 2 RTTs", func() {
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
ln, err := quic.ListenAddr("localhost:0", serverTLSConfig, serverConfig)
It("is forward-secure after 2 RTTs with Retry", func() {
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
defer udpConn.Close()
tr := &quic.Transport{
Conn: udpConn,
VerifySourceAddress: func(net.Addr) bool { return true },
}
addTracer(tr)
defer tr.Close()
ln, err := tr.Listen(serverTLSConfig, serverConfig)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
@ -67,7 +77,10 @@ var _ = Describe("Handshake RTT tests", func() {
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
getTLSClientConfig(),
getQuicConfig(nil),
getQuicConfig(&quic.Config{GetConfigForClient: func(info *quic.ClientHelloInfo) (*quic.Config, error) {
Expect(info.AddrVerified).To(BeTrue())
return nil, nil
}}),
)
Expect(err).ToNot(HaveOccurred())
defer conn.CloseWithError(0, "")
@ -85,7 +98,10 @@ var _ = Describe("Handshake RTT tests", func() {
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
getTLSClientConfig(),
getQuicConfig(nil),
getQuicConfig(&quic.Config{GetConfigForClient: func(info *quic.ClientHelloInfo) (*quic.Config, error) {
Expect(info.AddrVerified).To(BeFalse())
return nil, nil
}}),
)
Expect(err).ToNot(HaveOccurred())
defer conn.CloseWithError(0, "")

View file

@ -7,11 +7,11 @@ import (
"net"
"time"
tls "github.com/refraction-networking/utls"
quic "github.com/refraction-networking/uquic"
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/qerr"
tls "github.com/refraction-networking/utls"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -80,6 +80,46 @@ var _ = Describe("Handshake tests", func() {
}()
}
It("returns the context cancellation error on timeouts", func() {
ctx, cancel := context.WithTimeout(context.Background(), scaleDuration(20*time.Millisecond))
defer cancel()
errChan := make(chan error, 1)
go func() {
_, err := quic.DialAddr(
ctx,
"localhost:1234", // nobody is listening on this port, but we're going to cancel this dial anyway
getTLSClientConfig(),
getQuicConfig(nil),
)
errChan <- err
}()
var err error
Eventually(errChan).Should(Receive(&err))
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(context.DeadlineExceeded))
})
It("returns the cancellation reason when a dial is canceled", func() {
ctx, cancel := context.WithCancelCause(context.Background())
errChan := make(chan error, 1)
go func() {
_, err := quic.DialAddr(
ctx,
"localhost:1234", // nobody is listening on this port, but we're going to cancel this dial anyway
getTLSClientConfig(),
getQuicConfig(nil),
)
errChan <- err
}()
cancel(errors.New("application cancelled"))
var err error
Eventually(errChan).Should(Receive(&err))
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("application cancelled"))
})
// Context("using different cipher suites", func() {
// for n, id := range map[string]uint16{
// "TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
@ -130,13 +170,14 @@ var _ = Describe("Handshake tests", func() {
Context("Certificate validation", func() {
It("accepts the certificate", func() {
runServer(getTLSConfig())
_, err := quic.DialAddr(
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
getTLSClientConfig(),
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
conn.CloseWithError(0, "")
})
It("has the right local and remote address on the tls.Config.GetConfigForClient ClientHelloInfo.Conn", func() {
@ -165,6 +206,7 @@ var _ = Describe("Handshake tests", func() {
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
defer conn.CloseWithError(0, "")
Eventually(done).Should(BeClosed())
Expect(server.Addr()).To(Equal(local))
Expect(conn.LocalAddr().(*net.UDPAddr).Port).To(Equal(remote.(*net.UDPAddr).Port))
@ -174,13 +216,14 @@ var _ = Describe("Handshake tests", func() {
It("works with a long certificate chain", func() {
runServer(getTLSConfigWithLongCertChain())
_, err := quic.DialAddr(
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
getTLSClientConfig(),
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
conn.CloseWithError(0, "")
})
It("errors if the server name doesn't match", func() {
@ -201,6 +244,8 @@ var _ = Describe("Handshake tests", func() {
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode.IsCryptoError()).To(BeTrue())
Expect(transportErr.Error()).To(ContainSubstring("x509: certificate is valid for localhost, not foo.bar"))
var certErr *tls.CertificateVerificationError
Expect(errors.As(transportErr, &certErr)).To(BeTrue())
})
It("fails the handshake if the client fails to provide the requested client cert", func() {
@ -254,7 +299,7 @@ var _ = Describe("Handshake tests", func() {
})
})
Context("rate limiting", func() {
Context("queuening and accepting connections", func() {
var (
server *quic.Listener
pconn net.PacketConn
@ -279,7 +324,10 @@ var _ = Describe("Handshake tests", func() {
Expect(err).ToNot(HaveOccurred())
pconn, err = net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
dialer = &quic.Transport{Conn: pconn, ConnectionIDLength: 4}
dialer = &quic.Transport{
Conn: pconn,
ConnectionIDLength: 4,
}
})
AfterEach(func() {
@ -296,8 +344,11 @@ var _ = Describe("Handshake tests", func() {
}
time.Sleep(25 * time.Millisecond) // wait a bit for the connection to be queued
_, err := dial()
Expect(err).To(HaveOccurred())
conn, err := dial()
Expect(err).ToNot(HaveOccurred())
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, err = conn.AcceptStream(ctx)
var transportErr *quic.TransportError
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
@ -306,18 +357,21 @@ var _ = Describe("Handshake tests", func() {
_, err = server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
// dial again, and expect that this dial succeeds
conn, err := dial()
conn2, err := dial()
Expect(err).ToNot(HaveOccurred())
defer conn.CloseWithError(0, "")
defer conn2.CloseWithError(0, "")
time.Sleep(25 * time.Millisecond) // wait a bit for the connection to be queued
_, err = dial()
Expect(err).To(HaveOccurred())
conn3, err := dial()
Expect(err).ToNot(HaveOccurred())
ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, err = conn3.AcceptStream(ctx)
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
})
It("removes closed connections from the accept queue", func() {
It("also returns closed connections from the accept queue", func() {
firstConn, err := dial()
Expect(err).ToNot(HaveOccurred())
@ -328,25 +382,79 @@ var _ = Describe("Handshake tests", func() {
}
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
_, err = dial()
Expect(err).To(HaveOccurred())
conn, err := dial()
Expect(err).ToNot(HaveOccurred())
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, err = conn.AcceptStream(ctx)
var transportErr *quic.TransportError
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
// Now close the one of the connection that are waiting to be accepted.
// This should free one spot in the queue.
Expect(firstConn.CloseWithError(0, ""))
const appErrCode quic.ApplicationErrorCode = 12345
Expect(firstConn.CloseWithError(appErrCode, ""))
Eventually(firstConn.Context().Done()).Should(BeClosed())
time.Sleep(scaleDuration(200 * time.Millisecond))
// dial again, and expect that this dial succeeds
_, err = dial()
// dial again, and expect that this fails again
conn2, err := dial()
Expect(err).ToNot(HaveOccurred())
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, err = conn2.AcceptStream(ctx)
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
_, err = dial()
Expect(err).To(HaveOccurred())
// now accept all connections
var closedConn quic.Connection
for i := 0; i < protocol.MaxAcceptQueueSize; i++ {
conn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
if conn.Context().Err() != nil {
if closedConn != nil {
Fail("only expected a single closed connection")
}
closedConn = conn
}
}
Expect(closedConn).ToNot(BeNil()) // there should be exactly one closed connection
_, err = closedConn.AcceptStream(context.Background())
var appErr *quic.ApplicationError
Expect(errors.As(err, &appErr)).To(BeTrue())
Expect(appErr.ErrorCode).To(Equal(appErrCode))
})
It("closes handshaking connections when the server is closed", func() {
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
tr := &quic.Transport{Conn: udpConn}
addTracer(tr)
defer tr.Close()
tlsConf := &tls.Config{}
done := make(chan struct{})
tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
<-done
return nil, errors.New("closed")
}
ln, err := tr.Listen(tlsConf, getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
errChan := make(chan error, 1)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go func() {
defer GinkgoRecover()
_, err := quic.DialAddr(ctx, ln.Addr().String(), getTLSClientConfig(), getQuicConfig(nil))
errChan <- err
}()
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
Expect(ln.Close()).To(Succeed())
close(done)
err = <-errChan
var transportErr *quic.TransportError
Expect(errors.As(err, &transportErr)).To(BeTrue())
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
})
@ -451,16 +559,42 @@ var _ = Describe("Handshake tests", func() {
})
It("rejects invalid Retry token with the INVALID_TOKEN error", func() {
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
serverConfig.MaxRetryTokenAge = time.Nanosecond
const rtt = 10 * time.Millisecond
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), serverConfig)
// The validity period of the retry token is the handshake timeout,
// which is twice the handshake idle timeout.
// By setting the handshake timeout shorter than the RTT, the token will have expired by the time
// it reaches the server.
serverConfig.HandshakeIdleTimeout = rtt / 5
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
defer udpConn.Close()
tr := &quic.Transport{
Conn: udpConn,
VerifySourceAddress: func(net.Addr) bool { return true },
}
addTracer(tr)
defer tr.Close()
server, err := tr.Listen(getTLSConfig(), serverConfig)
Expect(err).ToNot(HaveOccurred())
defer server.Close()
serverPort := server.Addr().(*net.UDPAddr).Port
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
DelayPacket: func(quicproxy.Direction, []byte) time.Duration {
return rtt / 2
},
})
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
_, err = quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
getTLSClientConfig(),
nil,
)

View file

@ -20,7 +20,7 @@ import (
type listenerWrapper struct {
http3.QUICEarlyListener
listenerClosed bool
count int32
count atomic.Int32
}
func (ln *listenerWrapper) Close() error {
@ -29,14 +29,18 @@ func (ln *listenerWrapper) Close() error {
}
func (ln *listenerWrapper) Faker() *fakeClosingListener {
atomic.AddInt32(&ln.count, 1)
ln.count.Add(1)
ctx, cancel := context.WithCancel(context.Background())
return &fakeClosingListener{ln, 0, ctx, cancel}
return &fakeClosingListener{
listenerWrapper: ln,
ctx: ctx,
cancel: cancel,
}
}
type fakeClosingListener struct {
*listenerWrapper
closed int32
closed atomic.Bool
ctx context.Context
cancel context.CancelFunc
}
@ -47,9 +51,9 @@ func (ln *fakeClosingListener) Accept(ctx context.Context) (quic.EarlyConnection
}
func (ln *fakeClosingListener) Close() error {
if atomic.CompareAndSwapInt32(&ln.closed, 0, 1) {
if ln.closed.CompareAndSwap(false, true) {
ln.cancel()
if atomic.AddInt32(&ln.listenerWrapper.count, -1) == 0 {
if ln.listenerWrapper.count.Add(-1) == 0 {
ln.listenerWrapper.Close()
}
}
@ -145,8 +149,8 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
// and only the fake listener should be closed
Expect(server1.Close()).NotTo(HaveOccurred())
Eventually(stoppedServing1).Should(BeClosed())
Expect(fake1.closed).To(Equal(int32(1)))
Expect(fake2.closed).To(Equal(int32(0)))
Expect(fake1.closed.Load()).To(BeTrue())
Expect(fake2.closed.Load()).To(BeFalse())
Expect(ln.listenerClosed).ToNot(BeTrue())
Expect(client.Transport.(*http3.RoundTripper).Close()).NotTo(HaveOccurred())
@ -161,7 +165,7 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
// close the other server - both the fake and the actual listeners must close now
Expect(server2.Close()).NotTo(HaveOccurred())
Eventually(stoppedServing2).Should(BeClosed())
Expect(fake2.closed).To(Equal(int32(1)))
Expect(fake2.closed.Load()).To(BeTrue())
Expect(ln.listenerClosed).To(BeTrue())
})
})

View file

@ -42,7 +42,7 @@ var _ = Describe("HTTP tests", func() {
rt *http3.RoundTripper
server *http3.Server
stoppedServing chan struct{}
port string
port int
)
BeforeEach(func() {
@ -93,7 +93,7 @@ var _ = Describe("HTTP tests", func() {
Expect(err).NotTo(HaveOccurred())
conn, err := net.ListenUDP("udp", addr)
Expect(err).NotTo(HaveOccurred())
port = strconv.Itoa(conn.LocalAddr().(*net.UDPAddr).Port)
port = conn.LocalAddr().(*net.UDPAddr).Port
stoppedServing = make(chan struct{})
@ -120,7 +120,7 @@ var _ = Describe("HTTP tests", func() {
})
It("downloads a hello", func() {
resp, err := client.Get("https://localhost:" + port + "/hello")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
@ -128,13 +128,45 @@ var _ = Describe("HTTP tests", func() {
Expect(string(body)).To(Equal("Hello, World!\n"))
})
It("sets content-length for small response", func() {
mux.HandleFunc("/small", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
w.Write([]byte("foobar"))
})
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/small", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Header.Get("Content-Length")).To(Equal(strconv.Itoa(len("foobar"))))
})
It("detects stream errors when server panics when writing response", func() {
respChan := make(chan struct{})
mux.HandleFunc("/writing_and_panicking", func(w http.ResponseWriter, r *http.Request) {
// no recover here as it will interfere with the handler
w.Write([]byte("foobar"))
w.(http.Flusher).Flush()
// wait for the client to receive the response
<-respChan
panic(http.ErrAbortHandler)
})
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/writing_and_panicking", port))
close(respChan)
Expect(err).ToNot(HaveOccurred())
body, err := io.ReadAll(resp.Body)
Expect(err).To(HaveOccurred())
// the body will be a prefix of what's written
Expect(bytes.HasPrefix([]byte("foobar"), body)).To(BeTrue())
})
It("requests to different servers with the same udpconn", func() {
resp, err := client.Get("https://localhost:" + port + "/remoteAddr")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remoteAddr", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
addr1 := resp.Header.Get("X-RemoteAddr")
Expect(addr1).ToNot(Equal(""))
resp, err = client.Get("https://127.0.0.1:" + port + "/remoteAddr")
resp, err = client.Get(fmt.Sprintf("https://127.0.0.1:%d/remoteAddr", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
addr2 := resp.Header.Get("X-RemoteAddr")
@ -146,7 +178,7 @@ var _ = Describe("HTTP tests", func() {
group, ctx := errgroup.WithContext(context.Background())
for i := 0; i < 2; i++ {
group.Go(func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://localhost:"+port+"/hello", nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/hello", port), nil)
Expect(err).ToNot(HaveOccurred())
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
@ -172,7 +204,7 @@ var _ = Describe("HTTP tests", func() {
close(handlerCalled)
})
req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/headers/request", nil)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/headers/request", port), nil)
Expect(err).ToNot(HaveOccurred())
req.Header.Set("foo", "bar")
req.Header.Set("lorem", "ipsum")
@ -189,7 +221,7 @@ var _ = Describe("HTTP tests", func() {
w.Header().Set("lorem", "ipsum")
})
resp, err := client.Get("https://localhost:" + port + "/headers/response")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/headers/response", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Header.Get("foo")).To(Equal("bar"))
@ -197,7 +229,7 @@ var _ = Describe("HTTP tests", func() {
})
It("downloads a small file", func() {
resp, err := client.Get("https://localhost:" + port + "/prdata")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second))
@ -206,7 +238,7 @@ var _ = Describe("HTTP tests", func() {
})
It("downloads a large file", func() {
resp, err := client.Get("https://localhost:" + port + "/prdatalong")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdatalong", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second))
@ -218,7 +250,7 @@ var _ = Describe("HTTP tests", func() {
const num = 150
for i := 0; i < num; i++ {
resp, err := client.Get("https://localhost:" + port + "/hello")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
@ -231,7 +263,7 @@ var _ = Describe("HTTP tests", func() {
const num = 150
for i := 0; i < num; i++ {
resp, err := client.Get("https://localhost:" + port + "/prdata")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Body.Close()).To(Succeed())
@ -240,7 +272,7 @@ var _ = Describe("HTTP tests", func() {
It("posts a small message", func() {
resp, err := client.Post(
"https://localhost:"+port+"/echo",
fmt.Sprintf("https://localhost:%d/echo", port),
"text/plain",
bytes.NewReader([]byte("Hello, world!")),
)
@ -253,7 +285,7 @@ var _ = Describe("HTTP tests", func() {
It("uploads a file", func() {
resp, err := client.Post(
"https://localhost:"+port+"/echo",
fmt.Sprintf("https://localhost:%d/echo", port),
"text/plain",
bytes.NewReader(PRData),
)
@ -277,7 +309,7 @@ var _ = Describe("HTTP tests", func() {
})
client.Transport.(*http3.RoundTripper).DisableCompression = false
resp, err := client.Get("https://localhost:" + port + "/gzipped/hello")
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/gzipped/hello", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Uncompressed).To(BeTrue())
@ -287,6 +319,21 @@ var _ = Describe("HTTP tests", func() {
Expect(string(body)).To(Equal("Hello, World!\n"))
})
It("handles context cancellations", func() {
mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
})
ctx, cancel := context.WithCancel(context.Background())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil)
Expect(err).ToNot(HaveOccurred())
time.AfterFunc(50*time.Millisecond, cancel)
_, err = client.Do(req)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(context.Canceled))
})
It("cancels requests", func() {
handlerCalled := make(chan struct{})
mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) {
@ -295,15 +342,16 @@ var _ = Describe("HTTP tests", func() {
for {
if _, err := w.Write([]byte("foobar")); err != nil {
Expect(r.Context().Done()).To(BeClosed())
var strErr *quic.StreamError
Expect(errors.As(err, &strErr)).To(BeTrue())
Expect(strErr.ErrorCode).To(Equal(quic.StreamErrorCode(0x10c)))
var http3Err *http3.Error
Expect(errors.As(err, &http3Err)).To(BeTrue())
Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c)))
Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED"))
return
}
}
})
req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/cancel", nil)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil)
Expect(err).ToNot(HaveOccurred())
ctx, cancel := context.WithCancel(context.Background())
req = req.WithContext(ctx)
@ -313,7 +361,10 @@ var _ = Describe("HTTP tests", func() {
cancel()
Eventually(handlerCalled).Should(BeClosed())
_, err = resp.Body.Read([]byte{0})
Expect(err).To(HaveOccurred())
var http3Err *http3.Error
Expect(errors.As(err, &http3Err)).To(BeTrue())
Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c)))
Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED (local)"))
})
It("allows streamed HTTP requests", func() {
@ -336,7 +387,7 @@ var _ = Describe("HTTP tests", func() {
})
r, w := io.Pipe()
req, err := http.NewRequest("PUT", "https://localhost:"+port+"/echoline", r)
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("https://localhost:%d/echoline", port), r)
Expect(err).ToNot(HaveOccurred())
rsp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
@ -373,7 +424,7 @@ var _ = Describe("HTTP tests", func() {
}()
})
req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/httpstreamer", nil)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/httpstreamer", port), nil)
Expect(err).ToNot(HaveOccurred())
rsp, err := client.Transport.(*http3.RoundTripper).RoundTripOpt(req, http3.RoundTripOpt{DontCloseRequestStream: true})
Expect(err).ToNot(HaveOccurred())
@ -416,51 +467,109 @@ var _ = Describe("HTTP tests", func() {
Eventually(done).Should(BeClosed())
})
if go120 {
It("supports read deadlines", func() {
mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
err := setReadDeadline(w, time.Now().Add(deadlineDelay))
Expect(err).ToNot(HaveOccurred())
It("supports read deadlines", func() {
mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
rc := http.NewResponseController(w)
Expect(rc.SetReadDeadline(time.Now().Add(deadlineDelay))).To(Succeed())
body, err := io.ReadAll(r.Body)
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
Expect(body).To(ContainSubstring("aa"))
body, err := io.ReadAll(r.Body)
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
Expect(body).To(ContainSubstring("aa"))
w.Write([]byte("ok"))
})
expectedEnd := time.Now().Add(deadlineDelay)
resp, err := client.Post("https://localhost:"+port+"/read-deadline", "text/plain", neverEnding('a'))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
Expect(err).ToNot(HaveOccurred())
Expect(time.Now().After(expectedEnd)).To(BeTrue())
Expect(string(body)).To(Equal("ok"))
w.Write([]byte("ok"))
})
It("supports write deadlines", func() {
mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
err := setWriteDeadline(w, time.Now().Add(deadlineDelay))
Expect(err).ToNot(HaveOccurred())
expectedEnd := time.Now().Add(deadlineDelay)
resp, err := client.Post(
fmt.Sprintf("https://localhost:%d/read-deadline", port),
"text/plain",
neverEnding('a'),
)
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
_, err = io.Copy(w, neverEnding('a'))
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
})
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
Expect(err).ToNot(HaveOccurred())
Expect(time.Now().After(expectedEnd)).To(BeTrue())
Expect(string(body)).To(Equal("ok"))
})
expectedEnd := time.Now().Add(deadlineDelay)
It("supports write deadlines", func() {
mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
rc := http.NewResponseController(w)
Expect(rc.SetWriteDeadline(time.Now().Add(deadlineDelay))).To(Succeed())
resp, err := client.Get("https://localhost:" + port + "/write-deadline")
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
Expect(err).ToNot(HaveOccurred())
Expect(time.Now().After(expectedEnd)).To(BeTrue())
Expect(string(body)).To(ContainSubstring("aa"))
_, err := io.Copy(w, neverEnding('a'))
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
})
}
expectedEnd := time.Now().Add(deadlineDelay)
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/write-deadline", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
Expect(err).ToNot(HaveOccurred())
Expect(time.Now().After(expectedEnd)).To(BeTrue())
Expect(string(body)).To(ContainSubstring("aa"))
})
It("sets remote address", func() {
mux.HandleFunc("/remote-addr", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
_, ok := r.Context().Value(http3.RemoteAddrContextKey).(net.Addr)
Expect(ok).To(BeTrue())
})
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remote-addr", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
})
It("sets conn context", func() {
type ctxKey int
server.ConnContext = func(ctx context.Context, c quic.Connection) context.Context {
serv, ok := ctx.Value(http3.ServerContextKey).(*http3.Server)
Expect(ok).To(BeTrue())
Expect(serv).To(Equal(server))
ctx = context.WithValue(ctx, ctxKey(0), "Hello")
ctx = context.WithValue(ctx, ctxKey(1), c)
return ctx
}
mux.HandleFunc("/conn-context", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
v, ok := r.Context().Value(ctxKey(0)).(string)
Expect(ok).To(BeTrue())
Expect(v).To(Equal("Hello"))
c, ok := r.Context().Value(ctxKey(1)).(quic.Connection)
Expect(ok).To(BeTrue())
Expect(c).ToNot(BeNil())
serv, ok := r.Context().Value(http3.ServerContextKey).(*http3.Server)
Expect(ok).To(BeTrue())
Expect(serv).To(Equal(server))
})
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/conn-context", port))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))
})
It("checks the server's settings", func() {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/hello", port), nil)
Expect(err).ToNot(HaveOccurred())
testErr := errors.New("test error")
_, err = rt.RoundTripOpt(req, http3.RoundTripOpt{CheckSettings: func(settings http3.Settings) error {
Expect(settings.EnableExtendedConnect).To(BeTrue())
Expect(settings.EnableDatagram).To(BeFalse())
Expect(settings.Other).To(BeEmpty())
return testErr
}})
Expect(err).To(MatchError(err))
})
})

View file

@ -38,16 +38,13 @@ func countKeyPhases() (sent, received int) {
return
}
type keyUpdateConnTracer struct {
logging.NullConnectionTracer
}
func (t *keyUpdateConnTracer) SentShortHeaderPacket(hdr *logging.ShortHeader, _ logging.ByteCount, _ *logging.AckFrame, _ []logging.Frame) {
sentHeaders = append(sentHeaders, hdr)
}
func (t *keyUpdateConnTracer) ReceivedShortHeaderPacket(hdr *logging.ShortHeader, size logging.ByteCount, frames []logging.Frame) {
receivedHeaders = append(receivedHeaders, hdr)
var keyUpdateConnTracer = &logging.ConnectionTracer{
SentShortHeaderPacket: func(hdr *logging.ShortHeader, _ logging.ByteCount, _ logging.ECN, _ *logging.AckFrame, _ []logging.Frame) {
sentHeaders = append(sentHeaders, hdr)
},
ReceivedShortHeaderPacket: func(hdr *logging.ShortHeader, _ logging.ByteCount, _ logging.ECN, _ []logging.Frame) {
receivedHeaders = append(receivedHeaders, hdr)
},
}
var _ = Describe("Key Update tests", func() {
@ -75,8 +72,8 @@ var _ = Describe("Key Update tests", func() {
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
getTLSClientConfig(),
getQuicConfig(&quic.Config{Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
return &keyUpdateConnTracer{}
getQuicConfig(&quic.Config{Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return keyUpdateConnTracer
}}),
)
Expect(err).ToNot(HaveOccurred())

View file

@ -15,8 +15,8 @@ import (
quic "github.com/refraction-networking/uquic"
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/testutils"
"github.com/refraction-networking/uquic/internal/wire"
"github.com/refraction-networking/uquic/testutils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -32,7 +32,7 @@ var _ = Describe("MITM test", func() {
serverConfig *quic.Config
)
startServerAndProxy := func(delayCb quicproxy.DelayCallback, dropCb quicproxy.DropCallback) (proxyPort int, closeFn func()) {
startServerAndProxy := func(delayCb quicproxy.DelayCallback, dropCb quicproxy.DropCallback, forceAddressValidation bool) (proxyPort int, closeFn func()) {
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
c, err := net.ListenUDP("udp", addr)
@ -41,6 +41,10 @@ var _ = Describe("MITM test", func() {
Conn: c,
ConnectionIDLength: connIDLen,
}
addTracer(serverTransport)
if forceAddressValidation {
serverTransport.VerifySourceAddress = func(net.Addr) bool { return true }
}
ln, err := serverTransport.Listen(getTLSConfig(), serverConfig)
Expect(err).ToNot(HaveOccurred())
done := make(chan struct{})
@ -83,6 +87,7 @@ var _ = Describe("MITM test", func() {
Conn: clientUDPConn,
ConnectionIDLength: connIDLen,
}
addTracer(clientTransport)
})
Context("unsuccessful attacks", func() {
@ -153,7 +158,7 @@ var _ = Describe("MITM test", func() {
}
runTest := func(delayCb quicproxy.DelayCallback) {
proxyPort, closeFn := startServerAndProxy(delayCb, nil)
proxyPort, closeFn := startServerAndProxy(delayCb, nil, false)
defer closeFn()
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
Expect(err).ToNot(HaveOccurred())
@ -196,7 +201,7 @@ var _ = Describe("MITM test", func() {
})
runTest := func(dropCb quicproxy.DropCallback) {
proxyPort, closeFn := startServerAndProxy(nil, dropCb)
proxyPort, closeFn := startServerAndProxy(nil, dropCb, false)
defer closeFn()
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
Expect(err).ToNot(HaveOccurred())
@ -244,17 +249,17 @@ var _ = Describe("MITM test", func() {
Context("corrupting packets", func() {
const idleTimeout = time.Second
var numCorrupted, numPackets int32
var numCorrupted, numPackets atomic.Int32
BeforeEach(func() {
numCorrupted = 0
numPackets = 0
numCorrupted.Store(0)
numPackets.Store(0)
serverConfig.MaxIdleTimeout = idleTimeout
})
AfterEach(func() {
num := atomic.LoadInt32(&numCorrupted)
fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, atomic.LoadInt32(&numPackets))
num := numCorrupted.Load()
fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, numPackets.Load())
Expect(num).To(BeNumerically(">=", 1))
// If the packet containing the CONNECTION_CLOSE is corrupted,
// we have to wait for the connection to time out.
@ -266,13 +271,13 @@ var _ = Describe("MITM test", func() {
dropCb := func(dir quicproxy.Direction, raw []byte) bool {
defer GinkgoRecover()
if dir == quicproxy.DirectionIncoming {
atomic.AddInt32(&numPackets, 1)
numPackets.Add(1)
if rand.Intn(interval) == 0 {
pos := rand.Intn(len(raw))
raw[pos] = byte(rand.Intn(256))
_, err := clientTransport.WriteTo(raw, serverTransport.Conn.LocalAddr())
Expect(err).ToNot(HaveOccurred())
atomic.AddInt32(&numCorrupted, 1)
numCorrupted.Add(1)
return true
}
}
@ -286,13 +291,13 @@ var _ = Describe("MITM test", func() {
dropCb := func(dir quicproxy.Direction, raw []byte) bool {
defer GinkgoRecover()
if dir == quicproxy.DirectionOutgoing {
atomic.AddInt32(&numPackets, 1)
numPackets.Add(1)
if rand.Intn(interval) == 0 {
pos := rand.Intn(len(raw))
raw[pos] = byte(rand.Intn(256))
_, err := serverTransport.WriteTo(raw, clientTransport.Conn.LocalAddr())
Expect(err).ToNot(HaveOccurred())
atomic.AddInt32(&numCorrupted, 1)
numCorrupted.Add(1)
return true
}
}
@ -310,17 +315,16 @@ var _ = Describe("MITM test", func() {
const rtt = 20 * time.Millisecond
runTest := func(delayCb quicproxy.DelayCallback) (closeFn func(), err error) {
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil)
runTest := func(proxyPort int) (closeFn func(), err error) {
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
Expect(err).ToNot(HaveOccurred())
_, err = clientTransport.Dial(
context.Background(),
raddr,
getTLSClientConfig(),
getQuicConfig(&quic.Config{HandshakeIdleTimeout: 2 * time.Second}),
getQuicConfig(&quic.Config{HandshakeIdleTimeout: scaleDuration(200 * time.Millisecond)}),
)
return func() { clientTransport.Close(); serverCloseFn() }, err
return func() { clientTransport.Close() }, err
}
// fails immediately because client connection closes when it can't find compatible version
@ -338,7 +342,7 @@ var _ = Describe("MITM test", func() {
}
// Create fake version negotiation packet with no supported versions
versions := []protocol.VersionNumber{}
versions := []protocol.Version{}
packet := wire.ComposeVersionNegotiation(
protocol.ArbitraryLenConnectionID(hdr.SrcConnectionID.Bytes()),
protocol.ArbitraryLenConnectionID(hdr.DestConnectionID.Bytes()),
@ -352,7 +356,9 @@ var _ = Describe("MITM test", func() {
}
return rtt / 2
}
closeFn, err := runTest(delayCb)
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
defer serverCloseFn()
closeFn, err := runTest(proxyPort)
defer closeFn()
Expect(err).To(HaveOccurred())
vnErr := &quic.VersionNegotiationError{}
@ -363,8 +369,7 @@ var _ = Describe("MITM test", func() {
// times out, because client doesn't accept subsequent real retry packets from server
// as it has already accepted a retry.
// TODO: determine behavior when server does not send Retry packets
It("fails when a forged Retry packet with modified srcConnID is sent to client", func() {
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
It("fails when a forged Retry packet with modified Source Connection ID is sent to client", func() {
var initialPacketIntercepted bool
done := make(chan struct{})
delayCb := func(dir quicproxy.Direction, raw []byte) time.Duration {
@ -388,7 +393,9 @@ var _ = Describe("MITM test", func() {
}
return rtt / 2
}
closeFn, err := runTest(delayCb)
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, true)
defer serverCloseFn()
closeFn, err := runTest(proxyPort)
defer closeFn()
Expect(err).To(HaveOccurred())
Expect(err.(net.Error).Timeout()).To(BeTrue())
@ -412,13 +419,15 @@ var _ = Describe("MITM test", func() {
}
defer close(done)
injected = true
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.Version, hdr.DestConnectionID, nil)
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.DestConnectionID, nil, protocol.PerspectiveServer, hdr.Version)
_, err = serverTransport.WriteTo(initialPacket, clientTransport.Conn.LocalAddr())
Expect(err).ToNot(HaveOccurred())
}
return rtt
}
closeFn, err := runTest(delayCb)
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
defer serverCloseFn()
closeFn, err := runTest(proxyPort)
defer closeFn()
Expect(err).To(HaveOccurred())
Expect(err.(net.Error).Timeout()).To(BeTrue())
@ -442,13 +451,15 @@ var _ = Describe("MITM test", func() {
injected = true
// Fake Initial with ACK for packet 2 (unsent)
ack := &wire.AckFrame{AckRanges: []wire.AckRange{{Smallest: 2, Largest: 2}}}
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.Version, hdr.DestConnectionID, []wire.Frame{ack})
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.DestConnectionID, []wire.Frame{ack}, protocol.PerspectiveServer, hdr.Version)
_, err = serverTransport.WriteTo(initialPacket, clientTransport.Conn.LocalAddr())
Expect(err).ToNot(HaveOccurred())
}
return rtt
}
closeFn, err := runTest(delayCb)
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
defer serverCloseFn()
closeFn, err := runTest(proxyPort)
defer closeFn()
Expect(err).To(HaveOccurred())
var transportErr *quic.TransportError

View file

@ -2,9 +2,11 @@ package self_test
import (
"context"
"crypto/rand"
"io"
"net"
"runtime"
"sync/atomic"
"time"
. "github.com/onsi/ginkgo/v2"
@ -71,6 +73,7 @@ var _ = Describe("Multiplexing", func() {
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
tr := &quic.Transport{Conn: conn}
addTracer(tr)
done1 := make(chan struct{})
done2 := make(chan struct{})
@ -106,6 +109,7 @@ var _ = Describe("Multiplexing", func() {
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
tr := &quic.Transport{Conn: conn}
addTracer(tr)
done1 := make(chan struct{})
done2 := make(chan struct{})
@ -136,6 +140,7 @@ var _ = Describe("Multiplexing", func() {
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
tr := &quic.Transport{Conn: conn}
addTracer(tr)
server, err := tr.Listen(
getTLSConfig(),
getQuicConfig(nil),
@ -164,6 +169,7 @@ var _ = Describe("Multiplexing", func() {
Expect(err).ToNot(HaveOccurred())
defer conn1.Close()
tr1 := &quic.Transport{Conn: conn1}
addTracer(tr1)
addr2, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
@ -171,6 +177,7 @@ var _ = Describe("Multiplexing", func() {
Expect(err).ToNot(HaveOccurred())
defer conn2.Close()
tr2 := &quic.Transport{Conn: conn2}
addTracer(tr2)
server1, err := tr1.Listen(
getTLSConfig(),
@ -209,4 +216,72 @@ var _ = Describe("Multiplexing", func() {
})
}
})
It("sends and receives non-QUIC packets", func() {
addr1, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
conn1, err := net.ListenUDP("udp", addr1)
Expect(err).ToNot(HaveOccurred())
defer conn1.Close()
tr1 := &quic.Transport{Conn: conn1}
addTracer(tr1)
addr2, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
conn2, err := net.ListenUDP("udp", addr2)
Expect(err).ToNot(HaveOccurred())
defer conn2.Close()
tr2 := &quic.Transport{Conn: conn2}
addTracer(tr2)
server, err := tr1.Listen(getTLSConfig(), getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
runServer(server)
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var sentPackets, rcvdPackets atomic.Int64
const packetLen = 128
// send a non-QUIC packet every 100µs
go func() {
defer GinkgoRecover()
ticker := time.NewTicker(time.Millisecond / 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
case <-ctx.Done():
return
}
b := make([]byte, packetLen)
rand.Read(b[1:]) // keep the first byte set to 0, so it's not classified as a QUIC packet
_, err := tr1.WriteTo(b, tr2.Conn.LocalAddr())
if ctx.Err() != nil { // ctx canceled while Read was executing
return
}
Expect(err).ToNot(HaveOccurred())
sentPackets.Add(1)
}
}()
// receive and count non-QUIC packets
go func() {
defer GinkgoRecover()
for {
b := make([]byte, 1024)
n, addr, err := tr2.ReadNonQUICPacket(ctx, b)
if err != nil {
Expect(err).To(MatchError(context.Canceled))
return
}
Expect(addr).To(Equal(tr1.Conn.LocalAddr()))
Expect(n).To(Equal(packetLen))
rcvdPackets.Add(1)
}
}()
dial(tr2, server.Addr())
Eventually(func() int64 { return sentPackets.Load() }).Should(BeNumerically(">", 10))
Eventually(func() int64 { return rcvdPackets.Load() }).Should(BeNumerically(">=", sentPackets.Load()*4/5))
})
})

View file

@ -21,7 +21,7 @@ var _ = Describe("Packetization", func() {
It("bundles ACKs", func() {
const numMsg = 100
serverTracer := newPacketTracer()
serverCounter, serverTracer := newPacketTracer()
server, err := quic.ListenAddr(
"localhost:0",
getTLSConfig(),
@ -43,7 +43,7 @@ var _ = Describe("Packetization", func() {
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
clientTracer := newPacketTracer()
clientCounter, clientTracer := newPacketTracer()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
@ -104,8 +104,8 @@ var _ = Describe("Packetization", func() {
return
}
numBundledIncoming := countBundledPackets(clientTracer.getRcvdShortHeaderPackets())
numBundledOutgoing := countBundledPackets(serverTracer.getRcvdShortHeaderPackets())
numBundledIncoming := countBundledPackets(clientCounter.getRcvdShortHeaderPackets())
numBundledOutgoing := countBundledPackets(serverCounter.getRcvdShortHeaderPackets())
fmt.Fprintf(GinkgoWriter, "bundled incoming packets: %d / %d\n", numBundledIncoming, numMsg)
fmt.Fprintf(GinkgoWriter, "bundled outgoing packets: %d / %d\n", numBundledOutgoing, numMsg)
Expect(numBundledIncoming).To(And(

View file

@ -0,0 +1,90 @@
package self_test
import (
"context"
"os"
"path"
"regexp"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
quic "github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/qlog"
)
var _ = Describe("qlog dir tests", Serial, func() {
var originalQlogDirValue string
var tempTestDirPath string
BeforeEach(func() {
originalQlogDirValue = os.Getenv("QLOGDIR")
var err error
tempTestDirPath, err = os.MkdirTemp("", "temp_test_dir")
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
err := os.Setenv("QLOGDIR", originalQlogDirValue)
Expect(err).ToNot(HaveOccurred())
err = os.RemoveAll(tempTestDirPath)
Expect(err).ToNot(HaveOccurred())
})
handshake := func() {
serverStopped := make(chan struct{})
server, err := quic.ListenAddr(
"localhost:0",
getTLSConfig(),
&quic.Config{
Tracer: qlog.DefaultTracer,
},
)
Expect(err).ToNot(HaveOccurred())
go func() {
defer GinkgoRecover()
defer close(serverStopped)
for {
if _, err := server.Accept(context.Background()); err != nil {
return
}
}
}()
conn, err := quic.DialAddr(
context.Background(),
server.Addr().String(),
getTLSClientConfig(),
&quic.Config{
Tracer: qlog.DefaultTracer,
},
)
Expect(err).ToNot(HaveOccurred())
conn.CloseWithError(0, "")
server.Close()
<-serverStopped
}
It("environment variable is set", func() {
qlogDir := path.Join(tempTestDirPath, "qlogs")
err := os.Setenv("QLOGDIR", qlogDir)
Expect(err).ToNot(HaveOccurred())
handshake()
_, err = os.Stat(tempTestDirPath)
qlogDirCreated := !os.IsNotExist(err)
Expect(qlogDirCreated).To(BeTrue())
childs, err := os.ReadDir(qlogDir)
Expect(err).ToNot(HaveOccurred())
Expect(len(childs)).To(Equal(2))
odcids := make([]string, 0)
vantagePoints := make([]string, 0)
qlogFileNameRegexp := regexp.MustCompile(`^([0-f]+)_(client|server).qlog$`)
for _, child := range childs {
matches := qlogFileNameRegexp.FindStringSubmatch(child.Name())
odcids = append(odcids, matches[1])
vantagePoints = append(vantagePoints, matches[2])
}
Expect(odcids[0]).To(Equal(odcids[1]))
Expect(vantagePoints).To(ContainElements("client", "server"))
})
})

View file

@ -52,40 +52,42 @@ var _ = Describe("TLS session resumption", func() {
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
tlsConf := getTLSClientConfig()
tlsConf.ClientSessionCache = cache
conn, err := quic.DialAddr(
conn1, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
nil,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
defer conn1.CloseWithError(0, "")
var sessionKey string
Eventually(puts).Should(Receive(&sessionKey))
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
serverConn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
conn, err = quic.DialAddr(
conn2, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
nil,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Expect(gets).To(Receive(Equal(sessionKey)))
Expect(conn.ConnectionState().TLS.DidResume).To(BeTrue())
Expect(conn2.ConnectionState().TLS.DidResume).To(BeTrue())
serverConn, err = server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
conn2.CloseWithError(0, "")
})
It("doesn't use session resumption, if the config disables it", func() {
sConf := getTLSConfig()
sConf.SessionTicketsDisabled = true
server, err := quic.ListenAddr("localhost:0", sConf, nil)
server, err := quic.ListenAddr("localhost:0", sConf, getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
defer server.Close()
@ -94,15 +96,16 @@ var _ = Describe("TLS session resumption", func() {
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
tlsConf := getTLSClientConfig()
tlsConf.ClientSessionCache = cache
conn, err := quic.DialAddr(
conn1, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
nil,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
defer conn1.CloseWithError(0, "")
Consistently(puts).ShouldNot(Receive())
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
@ -110,14 +113,65 @@ var _ = Describe("TLS session resumption", func() {
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
conn, err = quic.DialAddr(
conn2, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
nil,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
Expect(conn2.ConnectionState().TLS.DidResume).To(BeFalse())
defer conn2.CloseWithError(0, "")
serverConn, err = server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
})
It("doesn't use session resumption, if the config returned by GetConfigForClient disables it", func() {
sConf := &tls.Config{
GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
conf := getTLSConfig()
conf.SessionTicketsDisabled = true
return conf, nil
},
}
server, err := quic.ListenAddr("localhost:0", sConf, getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
defer server.Close()
gets := make(chan string, 100)
puts := make(chan string, 100)
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
tlsConf := getTLSClientConfig()
tlsConf.ClientSessionCache = cache
conn1, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Consistently(puts).ShouldNot(Receive())
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
defer conn1.CloseWithError(0, "")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
serverConn, err := server.Accept(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
conn2, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Expect(conn2.ConnectionState().TLS.DidResume).To(BeFalse())
defer conn2.CloseWithError(0, "")
serverConn, err = server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())

View file

@ -87,10 +87,9 @@ var (
logBuf *syncedBuffer
versionParam string
qlogTracer func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer
enableQlog bool
version quic.VersionNumber
version quic.Version
tlsConfig *tls.Config
tlsConfigLongChain *tls.Config
tlsClientConfig *tls.Config
@ -139,9 +138,6 @@ func init() {
}
var _ = BeforeSuite(func() {
if enableQlog {
qlogTracer = tools.NewQlogger(GinkgoWriter)
}
switch versionParam {
case "1":
version = quic.Version1
@ -151,7 +147,7 @@ var _ = BeforeSuite(func() {
Fail(fmt.Sprintf("unknown QUIC version: %s", versionParam))
}
fmt.Printf("Using QUIC version: %s\n", version)
protocol.SupportedVersions = []quic.VersionNumber{version}
protocol.SupportedVersions = []quic.Version{version}
})
func getTLSConfig() *tls.Config {
@ -176,22 +172,48 @@ func getQuicConfig(conf *quic.Config) *quic.Config {
} else {
conf = conf.Clone()
}
if enableQlog {
if conf.Tracer == nil {
conf.Tracer = qlogTracer
} else if qlogTracer != nil {
origTracer := conf.Tracer
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
return logging.NewMultiplexedConnectionTracer(
qlogTracer(ctx, p, connID),
origTracer(ctx, p, connID),
)
}
if !enableQlog {
return conf
}
if conf.Tracer == nil {
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
return logging.NewMultiplexedConnectionTracer(
tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID),
// multiplex it with an empty tracer to check that we're correctly ignoring unset callbacks everywhere
&logging.ConnectionTracer{},
)
}
return conf
}
origTracer := conf.Tracer
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
return logging.NewMultiplexedConnectionTracer(
tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID),
origTracer(ctx, p, connID),
)
}
return conf
}
func addTracer(tr *quic.Transport) {
if !enableQlog {
return
}
if tr.Tracer == nil {
tr.Tracer = logging.NewMultiplexedTracer(
tools.QlogTracer(GinkgoWriter),
// multiplex it with an empty tracer to check that we're correctly ignoring unset callbacks everywhere
&logging.Tracer{},
)
return
}
origTracer := tr.Tracer
tr.Tracer = logging.NewMultiplexedTracer(
tools.QlogTracer(GinkgoWriter),
origTracer,
)
}
var _ = BeforeEach(func() {
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
@ -243,8 +265,8 @@ func scaleDuration(d time.Duration) time.Duration {
return time.Duration(scaleFactor) * d
}
func newTracer(tracer logging.ConnectionTracer) func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
return func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer { return tracer }
func newTracer(tracer *logging.ConnectionTracer) func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer { return tracer }
}
type packet struct {
@ -259,49 +281,46 @@ type shortHeaderPacket struct {
frames []logging.Frame
}
type packetTracer struct {
logging.NullConnectionTracer
type packetCounter struct {
closed chan struct{}
sentShortHdr, rcvdShortHdr []shortHeaderPacket
rcvdLongHdr []packet
}
func newPacketTracer() *packetTracer {
return &packetTracer{closed: make(chan struct{})}
}
func (t *packetTracer) ReceivedLongHeaderPacket(hdr *logging.ExtendedHeader, _ logging.ByteCount, frames []logging.Frame) {
t.rcvdLongHdr = append(t.rcvdLongHdr, packet{time: time.Now(), hdr: hdr, frames: frames})
}
func (t *packetTracer) ReceivedShortHeaderPacket(hdr *logging.ShortHeader, _ logging.ByteCount, frames []logging.Frame) {
t.rcvdShortHdr = append(t.rcvdShortHdr, shortHeaderPacket{time: time.Now(), hdr: hdr, frames: frames})
}
func (t *packetTracer) SentShortHeaderPacket(hdr *logging.ShortHeader, _ logging.ByteCount, ack *wire.AckFrame, frames []logging.Frame) {
if ack != nil {
frames = append(frames, ack)
}
t.sentShortHdr = append(t.sentShortHdr, shortHeaderPacket{time: time.Now(), hdr: hdr, frames: frames})
}
func (t *packetTracer) Close() { close(t.closed) }
func (t *packetTracer) getSentShortHeaderPackets() []shortHeaderPacket {
func (t *packetCounter) getSentShortHeaderPackets() []shortHeaderPacket {
<-t.closed
return t.sentShortHdr
}
func (t *packetTracer) getRcvdLongHeaderPackets() []packet {
func (t *packetCounter) getRcvdLongHeaderPackets() []packet {
<-t.closed
return t.rcvdLongHdr
}
func (t *packetTracer) getRcvdShortHeaderPackets() []shortHeaderPacket {
func (t *packetCounter) getRcvdShortHeaderPackets() []shortHeaderPacket {
<-t.closed
return t.rcvdShortHdr
}
func newPacketTracer() (*packetCounter, *logging.ConnectionTracer) {
c := &packetCounter{closed: make(chan struct{})}
return c, &logging.ConnectionTracer{
ReceivedLongHeaderPacket: func(hdr *logging.ExtendedHeader, _ logging.ByteCount, _ logging.ECN, frames []logging.Frame) {
c.rcvdLongHdr = append(c.rcvdLongHdr, packet{time: time.Now(), hdr: hdr, frames: frames})
},
ReceivedShortHeaderPacket: func(hdr *logging.ShortHeader, _ logging.ByteCount, _ logging.ECN, frames []logging.Frame) {
c.rcvdShortHdr = append(c.rcvdShortHdr, shortHeaderPacket{time: time.Now(), hdr: hdr, frames: frames})
},
SentShortHeaderPacket: func(hdr *logging.ShortHeader, _ logging.ByteCount, _ logging.ECN, ack *wire.AckFrame, frames []logging.Frame) {
if ack != nil {
frames = append(frames, ack)
}
c.sentShortHdr = append(c.sentShortHdr, shortHeaderPacket{time: time.Now(), hdr: hdr, frames: frames})
},
Close: func() { close(c.closed) },
}
}
func TestSelf(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Self integration tests")

View file

@ -22,12 +22,12 @@ type faultyConn struct {
net.PacketConn
MaxPackets int32
counter int32
counter atomic.Int32
}
func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(p)
counter := atomic.AddInt32(&c.counter, 1)
counter := c.counter.Add(1)
if counter <= c.MaxPackets {
return n, addr, err
}
@ -35,7 +35,7 @@ func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
}
func (c *faultyConn) WriteTo(p []byte, addr net.Addr) (int, error) {
counter := atomic.AddInt32(&c.counter, 1)
counter := c.counter.Add(1)
if counter <= c.MaxPackets {
return c.PacketConn.WriteTo(p, addr)
}
@ -57,7 +57,7 @@ var _ = Describe("Timeout tests", func() {
context.Background(),
"localhost:12345",
getTLSClientConfig(),
getQuicConfig(&quic.Config{HandshakeIdleTimeout: 10 * time.Millisecond}),
getQuicConfig(&quic.Config{HandshakeIdleTimeout: scaleDuration(50 * time.Millisecond)}),
)
errChan <- err
}()
@ -185,16 +185,18 @@ var _ = Describe("Timeout tests", func() {
Expect(err).ToNot(HaveOccurred())
defer server.Close()
serverConnChan := make(chan quic.Connection, 1)
serverConnClosed := make(chan struct{})
go func() {
defer GinkgoRecover()
conn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
serverConnChan <- conn
conn.AcceptStream(context.Background()) // blocks until the connection is closed
close(serverConnClosed)
}()
tr := newPacketTracer()
counter, tr := newPacketTracer()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
@ -215,7 +217,7 @@ var _ = Describe("Timeout tests", func() {
}()
Eventually(done, 2*idleTimeout).Should(BeClosed())
var lastAckElicitingPacketSentAt time.Time
for _, p := range tr.getSentShortHeaderPackets() {
for _, p := range counter.getSentShortHeaderPackets() {
var hasAckElicitingFrame bool
for _, f := range p.frames {
if _, ok := f.(*logging.AckFrame); ok {
@ -228,7 +230,7 @@ var _ = Describe("Timeout tests", func() {
lastAckElicitingPacketSentAt = p.time
}
}
rcvdPackets := tr.getRcvdShortHeaderPackets()
rcvdPackets := counter.getRcvdShortHeaderPackets()
lastPacketRcvdAt := rcvdPackets[len(rcvdPackets)-1].time
// We're ignoring here that only the first ack-eliciting packet sent resets the idle timeout.
// This is ok since we're dealing with a lossless connection here,
@ -240,7 +242,7 @@ var _ = Describe("Timeout tests", func() {
Consistently(serverConnClosed).ShouldNot(BeClosed())
// make the go routine return
Expect(server.Close()).To(Succeed())
(<-serverConnChan).CloseWithError(0, "")
Eventually(serverConnClosed).Should(BeClosed())
})
@ -266,11 +268,13 @@ var _ = Describe("Timeout tests", func() {
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
serverConnChan := make(chan quic.Connection, 1)
serverConnClosed := make(chan struct{})
go func() {
defer GinkgoRecover()
conn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
serverConnChan <- conn
<-conn.Context().Done() // block until the connection is closed
close(serverConnClosed)
}()
@ -309,7 +313,7 @@ var _ = Describe("Timeout tests", func() {
Consistently(serverConnClosed).ShouldNot(BeClosed())
// make the go routine return
Expect(server.Close()).To(Succeed())
(<-serverConnChan).CloseWithError(0, "")
Eventually(serverConnClosed).Should(BeClosed())
})
})
@ -325,11 +329,13 @@ var _ = Describe("Timeout tests", func() {
Expect(err).ToNot(HaveOccurred())
defer server.Close()
serverConnChan := make(chan quic.Connection, 1)
serverConnClosed := make(chan struct{})
go func() {
defer GinkgoRecover()
conn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
serverConnChan <- conn
conn.AcceptStream(context.Background()) // blocks until the connection is closed
close(serverConnClosed)
}()
@ -370,7 +376,7 @@ var _ = Describe("Timeout tests", func() {
_, err = str.Write([]byte("foobar"))
checkTimeoutError(err)
Expect(server.Close()).To(Succeed())
(<-serverConnChan).CloseWithError(0, "")
Eventually(serverConnClosed).Should(BeClosed())
})

View file

@ -19,32 +19,32 @@ import (
. "github.com/onsi/gomega"
)
var _ = Describe("Handshake tests", func() {
var _ = Describe("Tracer tests", func() {
addTracers := func(pers protocol.Perspective, conf *quic.Config) *quic.Config {
enableQlog := mrand.Int()%3 != 0
enableCustomTracer := mrand.Int()%3 != 0
fmt.Fprintf(GinkgoWriter, "%s using qlog: %t, custom: %t\n", pers, enableQlog, enableCustomTracer)
var tracerConstructors []func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer
var tracerConstructors []func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer
if enableQlog {
tracerConstructors = append(tracerConstructors, func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
tracerConstructors = append(tracerConstructors, func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
if mrand.Int()%2 == 0 { // simulate that a qlog collector might only want to log some connections
fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %x\n", p, connID)
fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %s\n", p, connID)
return nil
}
fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %x\n", p, connID)
fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %s\n", p, connID)
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(&bytes.Buffer{}), io.NopCloser(nil)), p, connID)
})
}
if enableCustomTracer {
tracerConstructors = append(tracerConstructors, func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
return logging.NullConnectionTracer{}
tracerConstructors = append(tracerConstructors, func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return &logging.ConnectionTracer{}
})
}
c := conf.Clone()
c.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
tracers := make([]logging.ConnectionTracer, 0, len(tracerConstructors))
c.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
tracers := make([]*logging.ConnectionTracer, 0, len(tracerConstructors))
for _, c := range tracerConstructors {
if tr := c(ctx, p, connID); tr != nil {
tracers = append(tracers, tr)

View file

@ -142,5 +142,6 @@ var _ = Describe("Unidirectional Streams", func() {
runReceivingPeer(client)
<-done1
<-done2
client.CloseWithError(0, "")
})
})

View file

@ -1,805 +0,0 @@
//go:build !go1.21
package self_test
import (
"context"
"fmt"
"io"
mrand "math/rand"
"net"
"sync"
"sync/atomic"
"time"
"github.com/refraction-networking/uquic"
tls "github.com/refraction-networking/utls"
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/wire"
"github.com/refraction-networking/uquic/logging"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("0-RTT", func() {
rtt := scaleDuration(5 * time.Millisecond)
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
var num0RTTPackets uint32 // to be used as an atomic
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
for len(data) > 0 {
if !wire.IsLongHeaderPacket(data[0]) {
break
}
hdr, _, rest, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
atomic.AddUint32(&num0RTTPackets, 1)
break
}
data = rest
}
return rtt / 2
},
})
Expect(err).ToNot(HaveOccurred())
return proxy, &num0RTTPackets
}
dialAndReceiveSessionTicket := func(serverConf *quic.Config) (*tls.Config, *tls.Config) {
tlsConf := getTLSConfig()
if serverConf == nil {
serverConf = getQuicConfig(nil)
}
serverConf.Allow0RTT = true
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
serverConf,
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { return rtt / 2 },
})
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
// dial the first connection in order to receive a session ticket
done := make(chan struct{})
go func() {
defer GinkgoRecover()
defer close(done)
conn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
<-conn.Context().Done()
}()
clientConf := getTLSClientConfig()
gets := make(chan string, 100)
puts := make(chan string, 100)
clientConf.ClientSessionCache = newClientSessionCache(tls.NewLRUClientSessionCache(100), gets, puts)
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Eventually(puts).Should(Receive())
// received the session ticket. We're done here.
Expect(conn.CloseWithError(0, "")).To(Succeed())
Eventually(done).Should(BeClosed())
return tlsConf, clientConf
}
transfer0RTTData := func(
ln *quic.EarlyListener,
proxyPort int,
connIDLen int,
clientTLSConf *tls.Config,
clientConf *quic.Config,
testdata []byte, // data to transfer
) {
// accept the second connection, and receive the data sent in 0-RTT
done := make(chan struct{})
go func() {
defer GinkgoRecover()
conn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
str, err := conn.AcceptStream(context.Background())
Expect(err).ToNot(HaveOccurred())
data, err := io.ReadAll(str)
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal(testdata))
Expect(str.Close()).To(Succeed())
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
<-conn.Context().Done()
close(done)
}()
if clientConf == nil {
clientConf = getQuicConfig(nil)
}
var conn quic.EarlyConnection
if connIDLen == 0 {
var err error
conn, err = quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxyPort),
clientTLSConf,
clientConf,
)
Expect(err).ToNot(HaveOccurred())
} else {
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", addr)
Expect(err).ToNot(HaveOccurred())
defer udpConn.Close()
tr := &quic.Transport{
Conn: udpConn,
ConnectionIDLength: connIDLen,
}
defer tr.Close()
conn, err = tr.DialEarly(
context.Background(),
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: proxyPort},
clientTLSConf,
clientConf,
)
Expect(err).ToNot(HaveOccurred())
}
defer conn.CloseWithError(0, "")
str, err := conn.OpenStream()
Expect(err).ToNot(HaveOccurred())
_, err = str.Write(testdata)
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
<-conn.HandshakeComplete()
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
io.ReadAll(str) // wait for the EOF from the server to arrive before closing the conn
conn.CloseWithError(0, "")
Eventually(done).Should(BeClosed())
Eventually(conn.Context().Done()).Should(BeClosed())
}
check0RTTRejected := func(
ln *quic.EarlyListener,
proxyPort int,
clientConf *tls.Config,
) {
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxyPort),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
str, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
_, err = str.Write(make([]byte, 3000))
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
// make sure the server doesn't process the data
ctx, cancel := context.WithTimeout(context.Background(), scaleDuration(50*time.Millisecond))
defer cancel()
serverConn, err := ln.Accept(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
_, err = serverConn.AcceptUniStream(ctx)
Expect(err).To(Equal(context.DeadlineExceeded))
Expect(serverConn.CloseWithError(0, "")).To(Succeed())
Eventually(conn.Context().Done()).Should(BeClosed())
}
// can be used to extract 0-RTT from a packetTracer
get0RTTPackets := func(packets []packet) []protocol.PacketNumber {
var zeroRTTPackets []protocol.PacketNumber
for _, p := range packets {
if p.hdr.Type == protocol.PacketType0RTT {
zeroRTTPackets = append(zeroRTTPackets, p.hdr.PacketNumber)
}
}
return zeroRTTPackets
}
for _, l := range []int{0, 15} {
connIDLen := l
It(fmt.Sprintf("transfers 0-RTT data, with %d byte connection IDs", connIDLen), func() {
tlsConf, clientTLSConf := dialAndReceiveSessionTicket(nil)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
transfer0RTTData(
ln,
proxy.LocalPort(),
connIDLen,
clientTLSConf,
getQuicConfig(nil),
PRData,
)
var numNewConnIDs int
for _, p := range tracer.getRcvdLongHeaderPackets() {
for _, f := range p.frames {
if _, ok := f.(*logging.NewConnectionIDFrame); ok {
numNewConnIDs++
}
}
}
if connIDLen == 0 {
Expect(numNewConnIDs).To(BeZero())
} else {
Expect(numNewConnIDs).ToNot(BeZero())
}
num0RTT := atomic.LoadUint32(num0RTTPackets)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
Expect(zeroRTTPackets).To(ContainElement(protocol.PacketNumber(0)))
})
}
// Test that data intended to be sent with 1-RTT protection is not sent in 0-RTT packets.
It("waits for a connection until the handshake is done", func() {
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
zeroRTTData := GeneratePRData(5 << 10)
oneRTTData := PRData
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
// now accept the second connection, and receive the 0-RTT data
go func() {
defer GinkgoRecover()
conn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
str, err := conn.AcceptUniStream(context.Background())
Expect(err).ToNot(HaveOccurred())
data, err := io.ReadAll(str)
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal(zeroRTTData))
str, err = conn.AcceptUniStream(context.Background())
Expect(err).ToNot(HaveOccurred())
data, err = io.ReadAll(str)
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal(oneRTTData))
Expect(conn.CloseWithError(0, "")).To(Succeed())
}()
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
firstStr, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
_, err = firstStr.Write(zeroRTTData)
Expect(err).ToNot(HaveOccurred())
Expect(firstStr.Close()).To(Succeed())
// wait for the handshake to complete
Eventually(conn.HandshakeComplete()).Should(BeClosed())
str, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
_, err = str.Write(PRData)
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
<-conn.Context().Done()
// check that 0-RTT packets only contain STREAM frames for the first stream
var num0RTT int
for _, p := range tracer.getRcvdLongHeaderPackets() {
if p.hdr.Header.Type != protocol.PacketType0RTT {
continue
}
for _, f := range p.frames {
sf, ok := f.(*logging.StreamFrame)
if !ok {
continue
}
num0RTT++
Expect(sf.StreamID).To(Equal(firstStr.StreamID()))
}
}
fmt.Fprintf(GinkgoWriter, "received %d STREAM frames in 0-RTT packets\n", num0RTT)
Expect(num0RTT).ToNot(BeZero())
})
It("transfers 0-RTT data, when 0-RTT packets are lost", func() {
var (
num0RTTPackets uint32 // to be used as an atomic
num0RTTDropped uint32
)
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
if wire.IsLongHeaderPacket(data[0]) {
hdr, _, _, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
atomic.AddUint32(&num0RTTPackets, 1)
}
}
return rtt / 2
},
DropPacket: func(_ quicproxy.Direction, data []byte) bool {
if !wire.IsLongHeaderPacket(data[0]) {
return false
}
hdr, _, _, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
// drop 25% of the 0-RTT packets
drop := mrand.Intn(4) == 0
if drop {
atomic.AddUint32(&num0RTTDropped, 1)
}
return drop
}
return false
},
})
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
num0RTT := atomic.LoadUint32(&num0RTTPackets)
numDropped := atomic.LoadUint32(&num0RTTDropped)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped)
Expect(numDropped).ToNot(BeZero())
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).ToNot(BeEmpty())
})
It("retransmits all 0-RTT data when the server performs a Retry", func() {
var mutex sync.Mutex
var firstConnID, secondConnID *protocol.ConnectionID
var firstCounter, secondCounter protocol.ByteCount
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
countZeroRTTBytes := func(data []byte) (n protocol.ByteCount) {
for len(data) > 0 {
hdr, _, rest, err := wire.ParsePacket(data)
if err != nil {
return
}
data = rest
if hdr.Type == protocol.PacketType0RTT {
n += hdr.Length - 16 /* AEAD tag */
}
}
return
}
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
RequireAddressValidation: func(net.Addr) bool { return true },
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
DelayPacket: func(dir quicproxy.Direction, data []byte) time.Duration {
connID, err := wire.ParseConnectionID(data, 0)
Expect(err).ToNot(HaveOccurred())
mutex.Lock()
defer mutex.Unlock()
if zeroRTTBytes := countZeroRTTBytes(data); zeroRTTBytes > 0 {
if firstConnID == nil {
firstConnID = &connID
firstCounter += zeroRTTBytes
} else if firstConnID != nil && *firstConnID == connID {
Expect(secondConnID).To(BeNil())
firstCounter += zeroRTTBytes
} else if secondConnID == nil {
secondConnID = &connID
secondCounter += zeroRTTBytes
} else if secondConnID != nil && *secondConnID == connID {
secondCounter += zeroRTTBytes
} else {
Fail("received 3 connection IDs on 0-RTT packets")
}
}
return rtt / 2
},
})
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, GeneratePRData(5000)) // ~5 packets
mutex.Lock()
defer mutex.Unlock()
Expect(firstCounter).To(BeNumerically("~", 5000+100 /* framing overhead */, 100)) // the FIN bit might be sent extra
Expect(secondCounter).To(BeNumerically("~", firstCounter, 20))
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">=", 5))
Expect(zeroRTTPackets[0]).To(BeNumerically(">=", protocol.PacketNumber(5)))
})
It("doesn't reject 0-RTT when the server's transport stream limit increased", func() {
const maxStreams = 1
tlsConf, clientConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
MaxIncomingUniStreams: maxStreams,
}))
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
MaxIncomingUniStreams: maxStreams + 1,
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
str, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
_, err = str.Write([]byte("foobar"))
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
// The client remembers the old limit and refuses to open a new stream.
_, err = conn.OpenUniStream()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("too many open streams"))
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = conn.OpenUniStreamSync(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
Expect(conn.CloseWithError(0, "")).To(Succeed())
})
It("rejects 0-RTT when the server's stream limit decreased", func() {
const maxStreams = 42
tlsConf, clientConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
MaxIncomingStreams: maxStreams,
}))
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
MaxIncomingStreams: maxStreams - 1,
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
})
It("rejects 0-RTT when the ALPN changed", func() {
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
// now close the listener and dial new connection with a different ALPN
clientConf.NextProtos = []string{"new-alpn"}
tlsConf.NextProtos = []string{"new-alpn"}
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
})
It("rejects 0-RTT when the application doesn't allow it", func() {
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
// now close the listener and dial new connection with a different ALPN
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: false, // application rejects 0-RTT
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
})
DescribeTable("flow control limits",
func(addFlowControlLimit func(*quic.Config, uint64)) {
tracer := newPacketTracer()
firstConf := getQuicConfig(&quic.Config{Allow0RTT: true})
addFlowControlLimit(firstConf, 3)
tlsConf, clientConf := dialAndReceiveSessionTicket(firstConf)
secondConf := getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
})
addFlowControlLimit(secondConf, 100)
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
secondConf,
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
str, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
written := make(chan struct{})
go func() {
defer GinkgoRecover()
defer close(written)
_, err := str.Write([]byte("foobar"))
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
}()
Eventually(written).Should(BeClosed())
serverConn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
rstr, err := serverConn.AcceptUniStream(context.Background())
Expect(err).ToNot(HaveOccurred())
data, err := io.ReadAll(rstr)
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal([]byte("foobar")))
Expect(serverConn.ConnectionState().Used0RTT).To(BeTrue())
Expect(serverConn.CloseWithError(0, "")).To(Succeed())
Eventually(conn.Context().Done()).Should(BeClosed())
var processedFirst bool
for _, p := range tracer.getRcvdLongHeaderPackets() {
for _, f := range p.frames {
if sf, ok := f.(*logging.StreamFrame); ok {
if !processedFirst {
// The first STREAM should have been sent in a 0-RTT packet.
// Due to the flow control limit, the STREAM frame was limit to the first 3 bytes.
Expect(p.hdr.Type).To(Equal(protocol.PacketType0RTT))
Expect(sf.Length).To(BeEquivalentTo(3))
processedFirst = true
} else {
Fail("STREAM was shouldn't have been sent in 0-RTT")
}
}
}
}
},
Entry("doesn't reject 0-RTT when the server's transport stream flow control limit increased", func(c *quic.Config, limit uint64) { c.InitialStreamReceiveWindow = limit }),
Entry("doesn't reject 0-RTT when the server's transport connection flow control limit increased", func(c *quic.Config, limit uint64) { c.InitialConnectionReceiveWindow = limit }),
)
for _, l := range []int{0, 15} {
connIDLen := l
It(fmt.Sprintf("correctly deals with 0-RTT rejections, for %d byte connection IDs", connIDLen), func() {
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
// now dial new connection with different transport parameters
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
MaxIncomingUniStreams: 1,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
// The client remembers that it was allowed to open 2 uni-directional streams.
firstStr, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
written := make(chan struct{}, 2)
go func() {
defer GinkgoRecover()
defer func() { written <- struct{}{} }()
_, err := firstStr.Write([]byte("first flight"))
Expect(err).ToNot(HaveOccurred())
}()
secondStr, err := conn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
go func() {
defer GinkgoRecover()
defer func() { written <- struct{}{} }()
_, err := secondStr.Write([]byte("first flight"))
Expect(err).ToNot(HaveOccurred())
}()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = conn.AcceptStream(ctx)
Expect(err).To(MatchError(quic.Err0RTTRejected))
Eventually(written).Should(Receive())
Eventually(written).Should(Receive())
_, err = firstStr.Write([]byte("foobar"))
Expect(err).To(MatchError(quic.Err0RTTRejected))
_, err = conn.OpenUniStream()
Expect(err).To(MatchError(quic.Err0RTTRejected))
_, err = conn.AcceptStream(ctx)
Expect(err).To(Equal(quic.Err0RTTRejected))
newConn := conn.NextConnection()
str, err := newConn.OpenUniStream()
Expect(err).ToNot(HaveOccurred())
_, err = newConn.OpenUniStream()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("too many open streams"))
_, err = str.Write([]byte("second flight"))
Expect(err).ToNot(HaveOccurred())
Expect(str.Close()).To(Succeed())
Expect(conn.CloseWithError(0, "")).To(Succeed())
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
})
}
It("queues 0-RTT packets, if the Initial is delayed", func() {
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: ln.Addr().String(),
DelayPacket: func(dir quicproxy.Direction, data []byte) time.Duration {
if dir == quicproxy.DirectionIncoming && wire.IsLongHeaderPacket(data[0]) && data[0]&0x30>>4 == 0 { // Initial packet from client
return rtt/2 + rtt
}
return rtt / 2
},
})
Expect(err).ToNot(HaveOccurred())
defer proxy.Close()
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
Expect(tracer.getRcvdLongHeaderPackets()[0].hdr.Type).To(Equal(protocol.PacketTypeInitial))
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
Expect(zeroRTTPackets[0]).To(Equal(protocol.PacketNumber(0)))
})
})

View file

@ -1,12 +1,9 @@
//go:build go1.21
package self_test
import (
"context"
"fmt"
"io"
mrand "math/rand"
"net"
"sync"
"sync/atomic"
@ -57,8 +54,8 @@ func (m metadataClientSessionCache) Put(key string, session *tls.ClientSessionSt
var _ = Describe("0-RTT", func() {
rtt := scaleDuration(5 * time.Millisecond)
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
var num0RTTPackets uint32 // to be used as an atomic
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *atomic.Uint32) {
var num0RTTPackets atomic.Uint32
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
@ -69,7 +66,7 @@ var _ = Describe("0-RTT", func() {
hdr, _, rest, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
atomic.AddUint32(&num0RTTPackets, 1)
num0RTTPackets.Add(1)
break
}
data = rest
@ -179,6 +176,7 @@ var _ = Describe("0-RTT", func() {
Conn: udpConn,
ConnectionIDLength: connIDLen,
}
addTracer(tr)
defer tr.Close()
conn, err = tr.DialEarly(
context.Background(),
@ -233,7 +231,7 @@ var _ = Describe("0-RTT", func() {
Eventually(conn.Context().Done()).Should(BeClosed())
}
// can be used to extract 0-RTT from a packetTracer
// can be used to extract 0-RTT from a packetCounter
get0RTTPackets := func(packets []packet) []protocol.PacketNumber {
var zeroRTTPackets []protocol.PacketNumber
for _, p := range packets {
@ -252,7 +250,7 @@ var _ = Describe("0-RTT", func() {
clientTLSConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, nil, clientTLSConf)
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -277,7 +275,7 @@ var _ = Describe("0-RTT", func() {
)
var numNewConnIDs int
for _, p := range tracer.getRcvdLongHeaderPackets() {
for _, p := range counter.getRcvdLongHeaderPackets() {
for _, f := range p.frames {
if _, ok := f.(*logging.NewConnectionIDFrame); ok {
numNewConnIDs++
@ -290,10 +288,10 @@ var _ = Describe("0-RTT", func() {
Expect(numNewConnIDs).ToNot(BeZero())
}
num0RTT := atomic.LoadUint32(num0RTTPackets)
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
Expect(zeroRTTPackets).To(ContainElement(protocol.PacketNumber(0)))
})
@ -308,7 +306,7 @@ var _ = Describe("0-RTT", func() {
zeroRTTData := GeneratePRData(5 << 10)
oneRTTData := PRData
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -365,7 +363,7 @@ var _ = Describe("0-RTT", func() {
// check that 0-RTT packets only contain STREAM frames for the first stream
var num0RTT int
for _, p := range tracer.getRcvdLongHeaderPackets() {
for _, p := range counter.getRcvdLongHeaderPackets() {
if p.hdr.Header.Type != protocol.PacketType0RTT {
continue
}
@ -383,16 +381,13 @@ var _ = Describe("0-RTT", func() {
})
It("transfers 0-RTT data, when 0-RTT packets are lost", func() {
var (
num0RTTPackets uint32 // to be used as an atomic
num0RTTDropped uint32
)
var num0RTTPackets, numDropped atomic.Uint32
tlsConf := getTLSConfig()
clientConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, nil, clientConf)
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -405,17 +400,8 @@ var _ = Describe("0-RTT", func() {
defer ln.Close()
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
if wire.IsLongHeaderPacket(data[0]) {
hdr, _, _, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
atomic.AddUint32(&num0RTTPackets, 1)
}
}
return rtt / 2
},
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { return rtt / 2 },
DropPacket: func(_ quicproxy.Direction, data []byte) bool {
if !wire.IsLongHeaderPacket(data[0]) {
return false
@ -423,10 +409,11 @@ var _ = Describe("0-RTT", func() {
hdr, _, _, err := wire.ParsePacket(data)
Expect(err).ToNot(HaveOccurred())
if hdr.Type == protocol.PacketType0RTT {
count := num0RTTPackets.Add(1)
// drop 25% of the 0-RTT packets
drop := mrand.Intn(4) == 0
drop := count%4 == 0
if drop {
atomic.AddUint32(&num0RTTDropped, 1)
numDropped.Add(1)
}
return drop
}
@ -438,12 +425,11 @@ var _ = Describe("0-RTT", func() {
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
num0RTT := atomic.LoadUint32(&num0RTTPackets)
numDropped := atomic.LoadUint32(&num0RTTDropped)
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped)
Expect(numDropped).ToNot(BeZero())
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped.Load())
Expect(numDropped.Load()).ToNot(BeZero())
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).ToNot(BeEmpty())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).ToNot(BeEmpty())
})
It("retransmits all 0-RTT data when the server performs a Retry", func() {
@ -469,15 +455,21 @@ var _ = Describe("0-RTT", func() {
return
}
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
counter, tracer := newPacketTracer()
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", laddr)
Expect(err).ToNot(HaveOccurred())
defer udpConn.Close()
tr := &quic.Transport{
Conn: udpConn,
VerifySourceAddress: func(net.Addr) bool { return true },
}
addTracer(tr)
defer tr.Close()
ln, err := tr.ListenEarly(
tlsConf,
getQuicConfig(&quic.Config{
RequireAddressValidation: func(net.Addr) bool { return true },
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
getQuicConfig(&quic.Config{Allow0RTT: true, Tracer: newTracer(tracer)}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
@ -519,11 +511,44 @@ var _ = Describe("0-RTT", func() {
defer mutex.Unlock()
Expect(firstCounter).To(BeNumerically("~", 5000+100 /* framing overhead */, 100)) // the FIN bit might be sent extra
Expect(secondCounter).To(BeNumerically("~", firstCounter, 20))
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">=", 5))
Expect(zeroRTTPackets[0]).To(BeNumerically(">=", protocol.PacketNumber(5)))
})
It("doesn't use 0-RTT when Dial is used for the resumed connection", func() {
tlsConf := getTLSConfig()
clientConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, getQuicConfig(nil), clientConf)
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{Allow0RTT: true}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
defer conn.CloseWithError(0, "")
Expect(conn.ConnectionState().TLS.DidResume).To(BeTrue())
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
Expect(num0RTTPackets.Load()).To(BeZero())
serverConn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
})
It("doesn't reject 0-RTT when the server's transport stream limit increased", func() {
const maxStreams = 1
tlsConf := getTLSConfig()
@ -532,14 +557,12 @@ var _ = Describe("0-RTT", func() {
MaxIncomingUniStreams: maxStreams,
}), clientConf)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
MaxIncomingUniStreams: maxStreams + 1,
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
@ -579,7 +602,7 @@ var _ = Describe("0-RTT", func() {
MaxIncomingStreams: maxStreams,
}), clientConf)
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -597,10 +620,10 @@ var _ = Describe("0-RTT", func() {
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
})
It("rejects 0-RTT when the ALPN changed", func() {
@ -613,7 +636,7 @@ var _ = Describe("0-RTT", func() {
// Append to the client's ALPN.
// crypto/tls will attempt to resume with the ALPN from the original connection
clientConf.NextProtos = append(clientConf.NextProtos, "new-alpn")
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -630,10 +653,10 @@ var _ = Describe("0-RTT", func() {
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
})
It("rejects 0-RTT when the application doesn't allow it", func() {
@ -642,7 +665,7 @@ var _ = Describe("0-RTT", func() {
dialAndReceiveSessionTicket(tlsConf, nil, clientConf)
// now close the listener and dial new connection with a different ALPN
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -659,15 +682,58 @@ var _ = Describe("0-RTT", func() {
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
})
It("doesn't use 0-RTT, if the server didn't enable it", func() {
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
Expect(err).ToNot(HaveOccurred())
defer server.Close()
gets := make(chan string, 100)
puts := make(chan string, 100)
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
tlsConf := getTLSClientConfig()
tlsConf.ClientSessionCache = cache
conn1, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
defer conn1.CloseWithError(0, "")
var sessionKey string
Eventually(puts).Should(Receive(&sessionKey))
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
serverConn, err := server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
conn2, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
tlsConf,
getQuicConfig(nil),
)
Expect(err).ToNot(HaveOccurred())
Expect(gets).To(Receive(Equal(sessionKey)))
Expect(conn2.ConnectionState().TLS.DidResume).To(BeTrue())
serverConn, err = server.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
conn2.CloseWithError(0, "")
})
DescribeTable("flow control limits",
func(addFlowControlLimit func(*quic.Config, uint64)) {
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
firstConf := getQuicConfig(&quic.Config{Allow0RTT: true})
addFlowControlLimit(firstConf, 3)
tlsConf := getTLSConfig()
@ -721,7 +787,7 @@ var _ = Describe("0-RTT", func() {
Eventually(conn.Context().Done()).Should(BeClosed())
var processedFirst bool
for _, p := range tracer.getRcvdLongHeaderPackets() {
for _, p := range counter.getRcvdLongHeaderPackets() {
for _, f := range p.frames {
if sf, ok := f.(*logging.StreamFrame); ok {
if !processedFirst {
@ -749,7 +815,7 @@ var _ = Describe("0-RTT", func() {
clientConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, nil, clientConf)
// now dial new connection with different transport parameters
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -815,10 +881,10 @@ var _ = Describe("0-RTT", func() {
Expect(conn.CloseWithError(0, "")).To(Succeed())
// The client should send 0-RTT packets, but the server doesn't process them.
num0RTT := atomic.LoadUint32(num0RTTPackets)
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(tracer.getRcvdLongHeaderPackets())).To(BeEmpty())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
})
}
@ -827,7 +893,7 @@ var _ = Describe("0-RTT", func() {
clientConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, nil, clientConf)
tracer := newPacketTracer()
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
@ -852,8 +918,8 @@ var _ = Describe("0-RTT", func() {
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
Expect(tracer.getRcvdLongHeaderPackets()[0].hdr.Type).To(Equal(protocol.PacketTypeInitial))
zeroRTTPackets := get0RTTPackets(tracer.getRcvdLongHeaderPackets())
Expect(counter.getRcvdLongHeaderPackets()[0].hdr.Type).To(Equal(protocol.PacketTypeInitial))
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
Expect(zeroRTTPackets[0]).To(Equal(protocol.PacketNumber(0)))
})
@ -879,14 +945,10 @@ var _ = Describe("0-RTT", func() {
clientTLSConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, nil, clientTLSConf)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
getQuicConfig(&quic.Config{Allow0RTT: true}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
@ -917,14 +979,10 @@ var _ = Describe("0-RTT", func() {
}
dialAndReceiveSessionTicket(tlsConf, nil, clientTLSConf)
tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
Tracer: newTracer(tracer),
}),
getQuicConfig(&quic.Config{Allow0RTT: true}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
@ -939,4 +997,119 @@ var _ = Describe("0-RTT", func() {
)
Expect(restored).To(BeTrue())
})
It("sends 0-RTT datagrams", func() {
tlsConf := getTLSConfig()
clientTLSConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, getQuicConfig(&quic.Config{
EnableDatagrams: true,
}), clientTLSConf)
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
EnableDatagrams: true,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
// second connection
sentMessage := GeneratePRData(100)
var receivedMessage []byte
received := make(chan struct{})
go func() {
defer GinkgoRecover()
defer close(received)
conn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
receivedMessage, err = conn.ReceiveDatagram(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
}()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientTLSConf,
getQuicConfig(&quic.Config{
EnableDatagrams: true,
}),
)
Expect(err).ToNot(HaveOccurred())
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
Expect(conn.SendDatagram(sentMessage)).To(Succeed())
<-conn.HandshakeComplete()
<-received
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
Expect(conn.CloseWithError(0, "")).To(Succeed())
Expect(receivedMessage).To(Equal(sentMessage))
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
Expect(zeroRTTPackets).To(HaveLen(1))
})
It("rejects 0-RTT datagrams when the server doesn't support datagrams anymore", func() {
tlsConf := getTLSConfig()
clientTLSConf := getTLSClientConfig()
dialAndReceiveSessionTicket(tlsConf, getQuicConfig(&quic.Config{
EnableDatagrams: true,
}), clientTLSConf)
counter, tracer := newPacketTracer()
ln, err := quic.ListenAddrEarly(
"localhost:0",
tlsConf,
getQuicConfig(&quic.Config{
Allow0RTT: true,
EnableDatagrams: false,
Tracer: newTracer(tracer),
}),
)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
defer proxy.Close()
// second connection
go func() {
defer GinkgoRecover()
conn, err := ln.Accept(context.Background())
Expect(err).ToNot(HaveOccurred())
_, err = conn.ReceiveDatagram(context.Background())
Expect(err.Error()).To(Equal("datagram support disabled"))
<-conn.HandshakeComplete()
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
}()
conn, err := quic.DialAddrEarly(
context.Background(),
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
clientTLSConf,
getQuicConfig(&quic.Config{
EnableDatagrams: true,
}),
)
Expect(err).ToNot(HaveOccurred())
// the client can temporarily send datagrams but the server doesn't process them.
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed())
<-conn.HandshakeComplete()
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
Expect(conn.CloseWithError(0, "")).To(Succeed())
num0RTT := num0RTTPackets.Load()
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
Expect(num0RTT).ToNot(BeZero())
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
})
})

View file

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"net"
"runtime"
"runtime/pprof"
"strconv"
"strings"
@ -68,7 +69,11 @@ var _ = Describe("QUIC Proxy", func() {
addr, err := net.ResolveUDPAddr("udp", "localhost:"+strconv.Itoa(proxy.LocalPort()))
Expect(err).ToNot(HaveOccurred())
_, err = net.ListenUDP("udp", addr)
Expect(err).To(MatchError(fmt.Sprintf("listen udp 127.0.0.1:%d: bind: address already in use", proxy.LocalPort())))
if runtime.GOOS == "windows" {
Expect(err).To(MatchError(fmt.Sprintf("listen udp 127.0.0.1:%d: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.", proxy.LocalPort())))
} else {
Expect(err).To(MatchError(fmt.Sprintf("listen udp 127.0.0.1:%d: bind: address already in use", proxy.LocalPort())))
}
Expect(proxy.Close()).To(Succeed()) // stopping is tested in the next test
})
@ -136,7 +141,7 @@ var _ = Describe("QUIC Proxy", func() {
Context("Proxy tests", func() {
var (
serverConn *net.UDPConn
serverNumPacketsSent int32
serverNumPacketsSent atomic.Int32
serverReceivedPackets chan packetData
clientConn *net.UDPConn
proxy *QuicProxy
@ -154,9 +159,9 @@ var _ = Describe("QUIC Proxy", func() {
BeforeEach(func() {
stoppedReading = make(chan struct{})
serverReceivedPackets = make(chan packetData, 100)
atomic.StoreInt32(&serverNumPacketsSent, 0)
serverNumPacketsSent.Store(0)
// setup a dump UDP server
// set up a dump UDP server
// in production this would be a QUIC server
raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
Expect(err).ToNot(HaveOccurred())
@ -176,7 +181,7 @@ var _ = Describe("QUIC Proxy", func() {
data := buf[0:n]
serverReceivedPackets <- packetData(data)
// echo the packet
atomic.AddInt32(&serverNumPacketsSent, 1)
serverNumPacketsSent.Add(1)
serverConn.WriteToUDP(data, addr)
}
}()
@ -231,7 +236,7 @@ var _ = Describe("QUIC Proxy", func() {
}()
Eventually(serverReceivedPackets).Should(HaveLen(2))
Expect(atomic.LoadInt32(&serverNumPacketsSent)).To(BeEquivalentTo(2))
Expect(serverNumPacketsSent.Load()).To(BeEquivalentTo(2))
Eventually(clientReceivedPackets).Should(HaveLen(2))
Expect(string(<-clientReceivedPackets)).To(ContainSubstring("foobar"))
Expect(string(<-clientReceivedPackets)).To(ContainSubstring("decafbad"))
@ -240,14 +245,14 @@ var _ = Describe("QUIC Proxy", func() {
Context("Drop Callbacks", func() {
It("drops incoming packets", func() {
var counter int32
var counter atomic.Int32
opts := &Opts{
RemoteAddr: serverConn.LocalAddr().String(),
DropPacket: func(d Direction, _ []byte) bool {
if d != DirectionIncoming {
return false
}
return atomic.AddInt32(&counter, 1)%2 == 1
return counter.Add(1)%2 == 1
},
}
startProxy(opts)
@ -262,14 +267,14 @@ var _ = Describe("QUIC Proxy", func() {
It("drops outgoing packets", func() {
const numPackets = 6
var counter int32
var counter atomic.Int32
opts := &Opts{
RemoteAddr: serverConn.LocalAddr().String(),
DropPacket: func(d Direction, _ []byte) bool {
if d != DirectionOutgoing {
return false
}
return atomic.AddInt32(&counter, 1)%2 == 1
return counter.Add(1)%2 == 1
},
}
startProxy(opts)
@ -310,7 +315,7 @@ var _ = Describe("QUIC Proxy", func() {
}
It("delays incoming packets", func() {
var counter int32
var counter atomic.Int32
opts := &Opts{
RemoteAddr: serverConn.LocalAddr().String(),
// delay packet 1 by 200 ms
@ -320,7 +325,7 @@ var _ = Describe("QUIC Proxy", func() {
if d == DirectionOutgoing {
return 0
}
p := atomic.AddInt32(&counter, 1)
p := counter.Add(1)
return time.Duration(p) * delay
},
}
@ -344,7 +349,7 @@ var _ = Describe("QUIC Proxy", func() {
})
It("handles reordered packets", func() {
var counter int32
var counter atomic.Int32
opts := &Opts{
RemoteAddr: serverConn.LocalAddr().String(),
// delay packet 1 by 600 ms
@ -354,7 +359,7 @@ var _ = Describe("QUIC Proxy", func() {
if d == DirectionOutgoing {
return 0
}
p := atomic.AddInt32(&counter, 1)
p := counter.Add(1)
return 600*time.Millisecond - time.Duration(p-1)*delay
},
}
@ -402,7 +407,7 @@ var _ = Describe("QUIC Proxy", func() {
It("delays outgoing packets", func() {
const numPackets = 3
var counter int32
var counter atomic.Int32
opts := &Opts{
RemoteAddr: serverConn.LocalAddr().String(),
// delay packet 1 by 200 ms
@ -412,7 +417,7 @@ var _ = Describe("QUIC Proxy", func() {
if d == DirectionIncoming {
return 0
}
p := atomic.AddInt32(&counter, 1)
p := counter.Add(1)
return time.Duration(p) * delay
},
}

View file

@ -7,6 +7,7 @@ import (
"io"
"log"
"os"
"time"
quic "github.com/refraction-networking/uquic"
"github.com/refraction-networking/uquic/internal/utils"
@ -14,13 +15,21 @@ import (
"github.com/refraction-networking/uquic/qlog"
)
func NewQlogger(logger io.Writer) func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
return func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
role := "server"
if p == logging.PerspectiveClient {
role = "client"
}
filename := fmt.Sprintf("log_%x_%s.qlog", connID.Bytes(), role)
func QlogTracer(logger io.Writer) *logging.Tracer {
filename := fmt.Sprintf("log_%s_transport.qlog", time.Now().Format("2006-01-02T15:04:05"))
fmt.Fprintf(logger, "Creating %s.\n", filename)
f, err := os.Create(filename)
if err != nil {
log.Fatalf("failed to create qlog file: %s", err)
return nil
}
bw := bufio.NewWriter(f)
return qlog.NewTracer(utils.NewBufferedWriteCloser(bw, f))
}
func NewQlogConnectionTracer(logger io.Writer) func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
filename := fmt.Sprintf("log_%s_%s.qlog", connID, p.String())
fmt.Fprintf(logger, "Creating %s.\n", filename)
f, err := os.Create(filename)
if err != nil {

View file

@ -2,8 +2,10 @@ package versionnegotiation
import (
"context"
"errors"
"fmt"
"net"
"time"
tls "github.com/refraction-networking/utls"
@ -17,32 +19,32 @@ import (
)
type versioner interface {
GetVersion() protocol.VersionNumber
GetVersion() protocol.Version
}
type versionNegotiationTracer struct {
logging.NullConnectionTracer
type result struct {
loggedVersions bool
receivedVersionNegotiation bool
chosen logging.VersionNumber
clientVersions, serverVersions []logging.VersionNumber
}
var _ logging.ConnectionTracer = &versionNegotiationTracer{}
func (t *versionNegotiationTracer) NegotiatedVersion(chosen logging.VersionNumber, clientVersions, serverVersions []logging.VersionNumber) {
if t.loggedVersions {
Fail("only expected one call to NegotiatedVersions")
func newVersionNegotiationTracer() (*result, *logging.ConnectionTracer) {
r := &result{}
return r, &logging.ConnectionTracer{
NegotiatedVersion: func(chosen logging.VersionNumber, clientVersions, serverVersions []logging.VersionNumber) {
if r.loggedVersions {
Fail("only expected one call to NegotiatedVersions")
}
r.loggedVersions = true
r.chosen = chosen
r.clientVersions = clientVersions
r.serverVersions = serverVersions
},
ReceivedVersionNegotiationPacket: func(dest, src logging.ArbitraryLenConnectionID, _ []logging.VersionNumber) {
r.receivedVersionNegotiation = true
},
}
t.loggedVersions = true
t.chosen = chosen
t.clientVersions = clientVersions
t.serverVersions = serverVersions
}
func (t *versionNegotiationTracer) ReceivedVersionNegotiationPacket(dest, src logging.ArbitraryLenConnectionID, _ []logging.VersionNumber) {
t.receivedVersionNegotiation = true
}
var _ = Describe("Handshake tests", func() {
@ -67,11 +69,11 @@ var _ = Describe("Handshake tests", func() {
}
}
var supportedVersions []protocol.VersionNumber
var supportedVersions []protocol.Version
BeforeEach(func() {
supportedVersions = append([]quic.VersionNumber{}, protocol.SupportedVersions...)
protocol.SupportedVersions = append(protocol.SupportedVersions, []protocol.VersionNumber{7, 8, 9, 10}...)
supportedVersions = append([]quic.Version{}, protocol.SupportedVersions...)
protocol.SupportedVersions = append(protocol.SupportedVersions, []protocol.Version{7, 8, 9, 10}...)
})
AfterEach(func() {
@ -84,55 +86,55 @@ var _ = Describe("Handshake tests", func() {
// the server doesn't support the highest supported version, which is the first one the client will try
// but it supports a bunch of versions that the client doesn't speak
serverConfig := &quic.Config{}
serverConfig.Versions = []protocol.VersionNumber{7, 8, protocol.SupportedVersions[0], 9}
serverTracer := &versionNegotiationTracer{}
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
serverConfig.Versions = []protocol.Version{7, 8, protocol.SupportedVersions[0], 9}
serverResult, serverTracer := newVersionNegotiationTracer()
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return serverTracer
}
server, cl := startServer(getTLSConfig(), serverConfig)
defer cl()
clientTracer := &versionNegotiationTracer{}
clientResult, clientTracer := newVersionNegotiationTracer()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
getTLSClientConfig(),
maybeAddQLOGTracer(&quic.Config{Tracer: func(ctx context.Context, perspective logging.Perspective, id quic.ConnectionID) logging.ConnectionTracer {
maybeAddQLOGTracer(&quic.Config{Tracer: func(ctx context.Context, perspective logging.Perspective, id quic.ConnectionID) *logging.ConnectionTracer {
return clientTracer
}}),
)
Expect(err).ToNot(HaveOccurred())
Expect(conn.(versioner).GetVersion()).To(Equal(expectedVersion))
Expect(conn.CloseWithError(0, "")).To(Succeed())
Expect(clientTracer.chosen).To(Equal(expectedVersion))
Expect(clientTracer.receivedVersionNegotiation).To(BeFalse())
Expect(clientTracer.clientVersions).To(Equal(protocol.SupportedVersions))
Expect(clientTracer.serverVersions).To(BeEmpty())
Expect(serverTracer.chosen).To(Equal(expectedVersion))
Expect(serverTracer.serverVersions).To(Equal(serverConfig.Versions))
Expect(serverTracer.clientVersions).To(BeEmpty())
Expect(clientResult.chosen).To(Equal(expectedVersion))
Expect(clientResult.receivedVersionNegotiation).To(BeFalse())
Expect(clientResult.clientVersions).To(Equal(protocol.SupportedVersions))
Expect(clientResult.serverVersions).To(BeEmpty())
Expect(serverResult.chosen).To(Equal(expectedVersion))
Expect(serverResult.serverVersions).To(Equal(serverConfig.Versions))
Expect(serverResult.clientVersions).To(BeEmpty())
})
It("when the client supports more versions than the server supports", func() {
expectedVersion := protocol.SupportedVersions[0]
// the server doesn't support the highest supported version, which is the first one the client will try
// The server doesn't support the highest supported version, which is the first one the client will try,
// but it supports a bunch of versions that the client doesn't speak
serverTracer := &versionNegotiationTracer{}
serverResult, serverTracer := newVersionNegotiationTracer()
serverConfig := &quic.Config{}
serverConfig.Versions = supportedVersions
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return serverTracer
}
server, cl := startServer(getTLSConfig(), serverConfig)
defer cl()
clientVersions := []protocol.VersionNumber{7, 8, 9, protocol.SupportedVersions[0], 10}
clientTracer := &versionNegotiationTracer{}
clientVersions := []protocol.Version{7, 8, 9, protocol.SupportedVersions[0], 10}
clientResult, clientTracer := newVersionNegotiationTracer()
conn, err := quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
getTLSClientConfig(),
maybeAddQLOGTracer(&quic.Config{
Versions: clientVersions,
Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) logging.ConnectionTracer {
Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return clientTracer
},
}),
@ -140,13 +142,53 @@ var _ = Describe("Handshake tests", func() {
Expect(err).ToNot(HaveOccurred())
Expect(conn.(versioner).GetVersion()).To(Equal(protocol.SupportedVersions[0]))
Expect(conn.CloseWithError(0, "")).To(Succeed())
Expect(clientTracer.chosen).To(Equal(expectedVersion))
Expect(clientTracer.receivedVersionNegotiation).To(BeTrue())
Expect(clientTracer.clientVersions).To(Equal(clientVersions))
Expect(clientTracer.serverVersions).To(ContainElements(supportedVersions)) // may contain greased versions
Expect(serverTracer.chosen).To(Equal(expectedVersion))
Expect(serverTracer.serverVersions).To(Equal(serverConfig.Versions))
Expect(serverTracer.clientVersions).To(BeEmpty())
Expect(clientResult.chosen).To(Equal(expectedVersion))
Expect(clientResult.receivedVersionNegotiation).To(BeTrue())
Expect(clientResult.clientVersions).To(Equal(clientVersions))
Expect(clientResult.serverVersions).To(ContainElements(supportedVersions)) // may contain greased versions
Expect(serverResult.chosen).To(Equal(expectedVersion))
Expect(serverResult.serverVersions).To(Equal(serverConfig.Versions))
Expect(serverResult.clientVersions).To(BeEmpty())
})
It("fails if the server disables version negotiation", func() {
// The server doesn't support the highest supported version, which is the first one the client will try,
// but it supports a bunch of versions that the client doesn't speak
_, serverTracer := newVersionNegotiationTracer()
serverConfig := &quic.Config{}
serverConfig.Versions = supportedVersions
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return serverTracer
}
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
Expect(err).ToNot(HaveOccurred())
tr := &quic.Transport{
Conn: conn,
DisableVersionNegotiationPackets: true,
}
ln, err := tr.Listen(getTLSConfig(), serverConfig)
Expect(err).ToNot(HaveOccurred())
defer ln.Close()
clientVersions := []protocol.Version{7, 8, 9, protocol.SupportedVersions[0], 10}
clientResult, clientTracer := newVersionNegotiationTracer()
_, err = quic.DialAddr(
context.Background(),
fmt.Sprintf("localhost:%d", conn.LocalAddr().(*net.UDPAddr).Port),
getTLSClientConfig(),
maybeAddQLOGTracer(&quic.Config{
Versions: clientVersions,
Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return clientTracer
},
HandshakeIdleTimeout: 100 * time.Millisecond,
}),
)
Expect(err).To(HaveOccurred())
var nerr net.Error
Expect(errors.As(err, &nerr)).To(BeTrue())
Expect(nerr.Timeout()).To(BeTrue())
Expect(clientResult.receivedVersionNegotiation).To(BeFalse())
})
}
})

View file

@ -65,12 +65,12 @@ func maybeAddQLOGTracer(c *quic.Config) *quic.Config {
if !enableQlog {
return c
}
qlogger := tools.NewQlogger(GinkgoWriter)
qlogger := tools.NewQlogConnectionTracer(GinkgoWriter)
if c.Tracer == nil {
c.Tracer = qlogger
} else if qlogger != nil {
origTracer := c.Tracer
c.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
c.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
return logging.NewMultiplexedConnectionTracer(
qlogger(ctx, p, connID),
origTracer(ctx, p, connID),

View file

@ -17,8 +17,12 @@ import (
// The StreamID is the ID of a QUIC stream.
type StreamID = protocol.StreamID
// A Version is a QUIC version number.
type Version = protocol.Version
// A VersionNumber is a QUIC version number.
type VersionNumber = protocol.VersionNumber
// Deprecated: VersionNumber was renamed to Version.
type VersionNumber = Version
const (
// Version1 is RFC 9000
@ -160,6 +164,9 @@ type Connection interface {
OpenStream() (Stream, error)
// OpenStreamSync opens a new bidirectional QUIC stream.
// It blocks until a new stream can be opened.
// There is no signaling to the peer about new streams:
// The peer can only accept the stream after data has been sent on the stream,
// or the stream has been reset or closed.
// If the error is non-nil, it satisfies the net.Error interface.
// If the connection was closed due to a timeout, Timeout() will be true.
OpenStreamSync(context.Context) (Stream, error)
@ -188,10 +195,14 @@ type Connection interface {
// Warning: This API should not be considered stable and might change soon.
ConnectionState() ConnectionState
// SendMessage sends a message as a datagram, as specified in RFC 9221.
SendMessage([]byte) error
// ReceiveMessage gets a message received in a datagram, as specified in RFC 9221.
ReceiveMessage(context.Context) ([]byte, error)
// SendDatagram sends a message using a QUIC datagram, as specified in RFC 9221.
// There is no delivery guarantee for DATAGRAM frames, they are not retransmitted if lost.
// The payload of the datagram needs to fit into a single QUIC packet.
// In addition, a datagram may be dropped before being sent out if the available packet size suddenly decreases.
// If the payload is too large to be sent at the current time, a DatagramTooLargeError is returned.
SendDatagram(payload []byte) error
// ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221.
ReceiveDatagram(context.Context) ([]byte, error)
}
// An EarlyConnection is a connection that is handshaking.
@ -213,6 +224,9 @@ type EarlyConnection interface {
// StatelessResetKey is a key used to derive stateless reset tokens.
type StatelessResetKey [32]byte
// TokenGeneratorKey is a key used to encrypt session resumption tokens.
type TokenGeneratorKey = handshake.TokenProtectorKey
// A ConnectionID is a QUIC Connection ID, as defined in RFC 9000.
// It is not able to handle QUIC Connection IDs longer than 20 bytes,
// as they are allowed by RFC 8999.
@ -249,9 +263,10 @@ type Config struct {
GetConfigForClient func(info *ClientHelloInfo) (*Config, error)
// The QUIC versions that can be negotiated.
// If not set, it uses all versions available.
Versions []VersionNumber
Versions []Version
// HandshakeIdleTimeout is the idle timeout before completion of the handshake.
// Specifically, if we don't receive any packet from the peer within this time, the connection attempt is aborted.
// If we don't receive any packet from the peer within this time, the connection attempt is aborted.
// Additionally, if the handshake doesn't complete in twice this time, the connection attempt is also aborted.
// If this value is zero, the timeout is set to 5 seconds.
HandshakeIdleTimeout time.Duration
// MaxIdleTimeout is the maximum duration that may pass without any incoming network activity.
@ -260,18 +275,6 @@ type Config struct {
// If the timeout is exceeded, the connection is closed.
// If this value is zero, the timeout is set to 30 seconds.
MaxIdleTimeout time.Duration
// RequireAddressValidation determines if a QUIC Retry packet is sent.
// This allows the server to verify the client's address, at the cost of increasing the handshake latency by 1 RTT.
// See https://datatracker.ietf.org/doc/html/rfc9000#section-8 for details.
// If not set, every client is forced to prove its remote address.
RequireAddressValidation func(net.Addr) bool
// MaxRetryTokenAge is the maximum age of a Retry token.
// If not set, it defaults to 5 seconds. Only valid for a server.
MaxRetryTokenAge time.Duration
// MaxTokenAge is the maximum age of the token presented during the handshake,
// for tokens that were issued on a previous connection.
// If not set, it defaults to 24 hours. Only valid for a server.
MaxTokenAge time.Duration
// The TokenStore stores tokens received from the server.
// Tokens are used to skip address validation on future connection attempts.
// The key used to store tokens is the ServerName from the tls.Config, if set
@ -323,20 +326,23 @@ type Config struct {
// Path MTU discovery is only available on systems that allow setting of the Don't Fragment (DF) bit.
// If unavailable or disabled, packets will be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
DisablePathMTUDiscovery bool
// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
// This can be useful if version information is exchanged out-of-band.
// It has no effect for a client.
DisableVersionNegotiationPackets bool
// Allow0RTT allows the application to decide if a 0-RTT connection attempt should be accepted.
// Only valid for the server.
Allow0RTT bool
// Enable QUIC datagram support (RFC 9221).
EnableDatagrams bool
Tracer func(context.Context, logging.Perspective, ConnectionID) logging.ConnectionTracer
Tracer func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer
}
// ClientHelloInfo contains information about an incoming connection attempt.
type ClientHelloInfo struct {
// RemoteAddr is the remote address on the Initial packet.
// Unless AddrVerified is set, the address is not yet verified, and could be a spoofed IP address.
RemoteAddr net.Addr
// AddrVerified says if the remote address was verified using QUIC's Retry mechanism.
// Note that the Retry mechanism costs one network roundtrip,
// and is not performed unless Transport.MaxUnvalidatedHandshakes is surpassed.
AddrVerified bool
}
// ConnectionState records basic details about a QUIC connection
@ -346,10 +352,12 @@ type ConnectionState struct {
// SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated.
// This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams).
// If datagram support was negotiated, datagrams can be sent and received using the
// SendMessage and ReceiveMessage methods on the Connection.
// SendDatagram and ReceiveDatagram methods on the Connection.
SupportsDatagrams bool
// Used0RTT says if 0-RTT resumption was used.
Used0RTT bool
// Version is the QUIC version of the QUIC connection.
Version VersionNumber
Version Version
// GSO says if generic segmentation offload is used
GSO bool
}

View file

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

View file

@ -3,9 +3,9 @@ package ackhandler
import (
"testing"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
func TestCrypto(t *testing.T) {

296
internal/ackhandler/ecn.go Normal file
View file

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

View file

@ -0,0 +1,272 @@
package ackhandler
import (
mocklogging "github.com/refraction-networking/uquic/internal/mocks/logging"
"github.com/refraction-networking/uquic/internal/protocol"
"github.com/refraction-networking/uquic/internal/utils"
"github.com/refraction-networking/uquic/logging"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("ECN tracker", func() {
var ecnTracker *ecnTracker
var tracer *mocklogging.MockConnectionTracer
getAckedPackets := func(pns ...protocol.PacketNumber) []*packet {
var packets []*packet
for _, p := range pns {
packets = append(packets, &packet{PacketNumber: p})
}
return packets
}
BeforeEach(func() {
var tr *logging.ConnectionTracer
tr, tracer = mocklogging.NewMockConnectionTracer(mockCtrl)
ecnTracker = newECNTracker(utils.DefaultLogger, tr)
})
It("sends exactly 10 testing packets", func() {
tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
for i := 0; i < 9; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
// Do this twice to make sure only sent packets are counted
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(10+i), protocol.ECT0)
}
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
ecnTracker.SentPacket(20, protocol.ECT0)
// In unknown state, packets shouldn't be ECN-marked.
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
})
sendAllTestingPackets := func() {
tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
for i := 0; i < 10; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
}
}
It("fails ECN validation if all ECN testing packets are lost", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
for i := 0; i < 9; i++ {
ecnTracker.LostPacket(protocol.PacketNumber(i))
}
// We don't care about the loss of non-testing packets
ecnTracker.LostPacket(15)
// Now lose the last testing packet.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets)
ecnTracker.LostPacket(9)
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
// We still don't care about more non-testing packets being lost
ecnTracker.LostPacket(16)
})
It("only detects ECN mangling after sending all testing packets", func() {
tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
for i := 0; i < 9; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
ecnTracker.LostPacket(protocol.PacketNumber(i))
}
// Send the last testing packet, and receive a
tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(9, protocol.ECT0)
// Now lose the last testing packet.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets)
ecnTracker.LostPacket(9)
})
It("passes ECN validation when a testing packet is acknowledged, while still in testing state", func() {
tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
for i := 0; i < 5; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
}
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(3), 1, 0, 0)).To(BeFalse())
// make sure we continue sending ECT(0) packets
for i := 5; i < 100; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
}
})
It("passes ECN validation when a testing packet is acknowledged, while in unknown state", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// Lose some packets to make sure this doesn't influence the outcome.
for i := 0; i < 5; i++ {
ecnTracker.LostPacket(protocol.PacketNumber(i))
}
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked([]*packet{{PacketNumber: 7}}, 1, 0, 0)).To(BeFalse())
})
It("fails ECN validation when the ACK contains more ECN counts than we sent packets", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// only 10 ECT(0) packets were sent, but the ACK claims to have received 12 of them
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 12, 0, 0)).To(BeFalse())
})
It("fails ECN validation when the ACK contains ECN counts for the wrong code point", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// We sent ECT(0), but this ACK acknowledges ECT(1).
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 0, 1, 0)).To(BeFalse())
})
It("fails ECN validation when the ACK doesn't contain ECN counts", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// First only acknowledge packets sent without ECN marks.
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(12, 13, 14), 0, 0, 0)).To(BeFalse())
// Now acknowledge some packets sent with ECN marks.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedNoECNCounts)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 15), 0, 0, 0)).To(BeFalse())
})
It("fails ECN validation when an ACK decreases ECN counts", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 3, 0, 0)).To(BeFalse())
// Now acknowledge some more packets, but decrease the ECN counts. Obviously, this doesn't make any sense.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedDecreasedECNCounts)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 13), 2, 0, 0)).To(BeFalse())
// make sure that new ACKs are ignored
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 14), 5, 0, 0)).To(BeFalse())
})
// This can happen if ACK are lost / reordered.
It("doesn't fail validation if the ACK contains more ECN counts than it acknowledges packets", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 8, 0, 0)).To(BeFalse())
})
It("fails ECN validation when the ACK doesn't contain enough ECN counts", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// First only acknowledge some packets sent with ECN marks.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 2, 0, 1)).To(BeTrue())
// Now acknowledge some more packets sent with ECN marks, but don't increase the counters enough.
// This ACK acknowledges 3 more ECN-marked packets, but the counters only increase by 2.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 15), 3, 0, 2)).To(BeFalse())
})
It("detects ECN mangling if all testing packets are marked CE", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3), 0, 0, 4)).To(BeFalse())
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 10, 11, 12), 0, 0, 7)).To(BeFalse())
// With the next ACK, all testing packets will now have been marked CE.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 13), 0, 0, 10)).To(BeFalse())
})
It("only detects ECN mangling after sending all testing packets", func() {
tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
for i := 0; i < 9; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(protocol.PacketNumber(i)), 0, 0, int64(i+1))).To(BeFalse())
}
// Send the last testing packet, and receive a
tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
ecnTracker.SentPacket(9, protocol.ECT0)
// This ACK now reports the last testing packets as CE as well.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(9), 0, 0, 10)).To(BeFalse())
})
It("detects ECN mangling, if some testing packets are marked CE, and then others are lost", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3), 0, 0, 4)).To(BeFalse())
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(6, 7, 8, 9), 0, 0, 8)).To(BeFalse())
// Lose one of the two unacknowledged packets.
ecnTracker.LostPacket(4)
// By losing the last unacknowledged testing packets, we should detect the mangling.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
ecnTracker.LostPacket(5)
})
It("detects ECN mangling, if some testing packets are lost, and then others are marked CE", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// Lose a few packets.
ecnTracker.LostPacket(0)
ecnTracker.LostPacket(1)
ecnTracker.LostPacket(2)
// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(3, 4, 5, 6, 7, 8), 0, 0, 6)).To(BeFalse())
// By CE-marking the last unacknowledged testing packets, we should detect the mangling.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(9), 0, 0, 7)).To(BeFalse())
})
It("declares congestion", func() {
sendAllTestingPackets()
for i := 10; i < 20; i++ {
Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
}
// Receive one CE count.
tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 2, 0, 1)).To(BeTrue())
// No increase in CE. No congestion.
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 13), 5, 0, 1)).To(BeFalse())
// Increase in CE. More congestion.
Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 14), 7, 0, 2)).To(BeTrue())
})
})

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