mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 04:37:36 +03:00
Compare commits
264 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a157e2d9de | ||
|
bbc1fe07d7 | ||
|
98f5a5debc | ||
|
cc7f02d9b9 | ||
|
cb2c7f1296 | ||
|
9178bdb6a5 | ||
|
164729a701 | ||
|
b24875057d | ||
|
8a76d80568 | ||
|
5e504393a9 | ||
|
bd4a3dff02 | ||
|
0018ce1ff4 | ||
|
05c12945b8 | ||
|
b66e1ed5f5 | ||
|
4973374ea5 | ||
|
d40dde9b9b | ||
|
b5b59e42e9 | ||
|
4a99b816ae | ||
|
9971fedd42 | ||
|
497d3f58a5 | ||
|
ca787d6f00 | ||
|
f1476390f2 | ||
|
06b421411d | ||
|
5fd5d7770d | ||
|
30e01b9524 | ||
|
55c05aceed | ||
|
aff90a6ffa | ||
|
3a7a53fdb9 | ||
|
2abbd41806 | ||
|
ac1268911e | ||
|
a70419b49f | ||
|
71f5ae5ecb | ||
|
f856163f1e | ||
|
067e7db750 | ||
|
dbbb6ca736 | ||
|
5a70d18c77 | ||
|
b21bd58281 | ||
|
60b4a9c630 | ||
|
d6269b71af | ||
|
ba1fbbe964 | ||
|
d41c0b68cd | ||
|
c5f7096f00 | ||
|
9813766373 | ||
|
0405634108 | ||
|
c786a46f42 | ||
|
7b8ceaa264 | ||
|
69fe37885f | ||
|
07a17ffffb | ||
|
0a7823c991 | ||
|
284996e13c | ||
|
4790797b58 | ||
|
02e4506c3b | ||
|
f54a32ec28 | ||
|
229ff4fa4c | ||
|
8e93770dd3 | ||
|
013949cda3 | ||
|
43baf2db7a | ||
|
ab17a5df6a | ||
|
c22a3c8e6f | ||
|
dc49f5673b | ||
|
198de32ef6 | ||
|
07ec3245bd | ||
|
b675e34254 | ||
|
225d2a3926 | ||
|
0344401de5 | ||
|
f3c4be6b01 | ||
|
2fbe713bb6 | ||
|
0582e931a5 | ||
|
fbaa941ea1 | ||
|
69ba7acb9f | ||
|
72c79dbdf5 | ||
|
86441f1fdc | ||
|
e65e99f1d6 | ||
|
da25787a3d | ||
|
d330d2e30d | ||
|
be4838bd64 | ||
|
34c4d89e8b | ||
|
03ba124241 | ||
|
940feef063 | ||
|
9b83ac230b | ||
|
c82c37a31c | ||
|
808f849ca2 | ||
|
d3974e1674 | ||
|
baeec0f41c | ||
|
2a7a11f4c0 | ||
|
a968e254a1 | ||
|
a2cf43d75c | ||
|
892851eb8c | ||
|
bda5b7e6dc | ||
|
4684e2dde1 | ||
|
4407c60f04 | ||
|
594440b04c | ||
|
cb1775a08a | ||
|
b3eb375bc1 | ||
|
d3c2020ecd | ||
|
3449ace5a6 | ||
|
13327521a5 | ||
|
2cd9ed38f1 | ||
|
1e874896cd | ||
|
fa047fddb7 | ||
|
0a922b4e7d | ||
|
3ff50295ce | ||
|
f1b3bdbcb0 | ||
|
54d6f7dc51 | ||
|
8cad3d2ea5 | ||
|
59ed51704a | ||
|
1083d1fb8f | ||
|
22b7f7744e | ||
|
1fce81f8bb | ||
|
7f080dd54b | ||
|
d6e3f3229f | ||
|
a937959fbf | ||
|
22411e16d5 | ||
|
18c591c75a | ||
|
d795250479 | ||
|
31a677cacd | ||
|
2243fdefbf | ||
|
1c8dd085f9 | ||
|
d3c5f389d4 | ||
|
5d6bf7e206 | ||
|
e0bf13be01 | ||
|
06c6a8449b | ||
|
6ffb9054a2 | ||
|
048940927c | ||
|
ff6d575ee3 | ||
|
574dc84c0c | ||
|
a7a66f6437 | ||
|
659da8ca08 | ||
|
f162b948db | ||
|
d234d62d52 | ||
|
38eafe4ad8 | ||
|
9b40c50a73 | ||
|
45922f76d6 | ||
|
87ef8ec48d | ||
|
7b9d21fbe6 | ||
|
2d7ea37672 | ||
|
771d136fa9 | ||
|
3bf2e19d0d | ||
|
96ab48eb7d | ||
|
427f53328b | ||
|
740119b144 | ||
|
a14017fc59 | ||
|
9414ea4910 | ||
|
d4ab27de1f | ||
|
f23da7da47 | ||
|
a3603549ee | ||
|
6eb0caca1a | ||
|
5311f8178c | ||
|
dda63b90eb | ||
|
e3be11b1e8 | ||
|
ef800d6f71 | ||
|
92311cd441 | ||
|
d309060cde | ||
|
77691ccce8 | ||
|
7c77243b04 | ||
|
e2622bfad8 | ||
|
746290b78a | ||
|
1bcec70978 | ||
|
30f9c0139f | ||
|
6239effc7a | ||
|
1c631cf9cb | ||
|
7884f87f82 | ||
|
4c357c8f76 | ||
|
5314d90b9f | ||
|
36f7fe7d07 | ||
|
a263164d9f | ||
|
b344940f06 | ||
|
2b290742f6 | ||
|
262cf0a592 | ||
|
f49944b737 | ||
|
6b99ab6f6e | ||
|
1c92f0caab | ||
|
49e588a6a9 | ||
|
ae2ef95fa3 | ||
|
348042ee4c | ||
|
9a397abc17 | ||
|
4bdff39ff0 | ||
|
4a046185b7 | ||
|
c12f425803 | ||
|
9010cfd2bb | ||
|
22fb59ee6f | ||
|
55eebd49ff | ||
|
1affe38703 | ||
|
9b82196578 | ||
|
d8cc4cb3ef | ||
|
ab1c1be9a9 | ||
|
22eac50276 | ||
|
5b25d8b5be | ||
|
c1ce4a8e92 | ||
|
862e64c7b9 | ||
|
37a3c417a7 | ||
|
7599f81faf | ||
|
d52e9f35bc | ||
|
2a8dc12a53 | ||
|
d1f6ea997c | ||
|
1f25153884 | ||
|
d6ac6300a4 | ||
|
797e275293 | ||
|
b6ce91bfe7 | ||
|
f9cfa248da | ||
|
ffe6546833 | ||
|
ad63e2a40a | ||
|
bed8ebbd4c | ||
|
8df7624c07 | ||
|
b73a4de7ea | ||
|
5dd6d91c11 | ||
|
f919473598 | ||
|
a7f807856c | ||
|
abfe1ef548 | ||
|
e1fcac3e46 | ||
|
54b76ceb3e | ||
|
dc0369cad4 | ||
|
6cac231f6a | ||
|
591d864e5e | ||
|
96b1943cf5 | ||
|
6cde43785f | ||
|
090e505aa9 | ||
|
d7334c16e7 | ||
|
66db44d7c8 | ||
|
9b03bc282c | ||
|
856bc02b8f | ||
|
3d09353796 | ||
|
2797f85fc0 | ||
|
8f34488c76 | ||
|
8963306987 | ||
|
e058f56643 | ||
|
d22854641a | ||
|
f633dca488 | ||
|
8d91ad9fcd | ||
|
824fd8a2f2 | ||
|
ced65c0ddc | ||
|
f689a5d023 | ||
|
fe3c4f271d | ||
|
6880f88089 | ||
|
443c6148b6 | ||
|
5c5db8cc59 | ||
|
501cc21c4b | ||
|
f7f4872bb9 | ||
|
3822dae9bb | ||
|
5200f27dcf | ||
|
3a3169551b | ||
|
4122eb7a7d | ||
|
83c00a574d | ||
|
51d257d608 | ||
|
ca3842d6c8 | ||
|
4f696569a2 | ||
|
2e7ea9119c | ||
|
2cf9a8b684 | ||
|
d55cdaf54d | ||
|
7cc5b8918f | ||
|
bda01bc489 | ||
|
1d848392bc | ||
|
70f3f44a09 | ||
|
b65ed61fea | ||
|
6d525c0e31 | ||
|
edf00ef67a | ||
|
05db808f72 | ||
|
10d1114962 | ||
|
571d3adef4 | ||
|
aab4d4e410 | ||
|
95ab7bdc9a | ||
|
26c6fcc549 | ||
|
18d3846d4f | ||
|
f9f6b9df6e |
327 changed files with 18289 additions and 7926 deletions
21
.clusterfuzzlite/Dockerfile
Normal file
21
.clusterfuzzlite/Dockerfile
Normal 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
9
.clusterfuzzlite/build.sh
Executable 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
|
1
.clusterfuzzlite/project.yaml
Normal file
1
.clusterfuzzlite/project.yaml
Normal file
|
@ -0,0 +1 @@
|
|||
language: go
|
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal 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
15
.github/dependabot.yml
vendored
Normal 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"
|
28
.github/workflows/ginkgo_test.yml
vendored
28
.github/workflows/ginkgo_test.yml
vendored
|
@ -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
|
9
.github/workflows/go_build.yml
vendored
9
.github/workflows/go_build.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
54
README.md
54
README.md
|
@ -1,9 +1,12 @@
|
|||
#  <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>
|
||||
|
||||
[](https://github.com/refraction-networking/uquic/actions/workflows/go_build.yml)
|
||||
[](https://github.com/refraction-networking/uquic/actions/workflows/ginkgo_test.yml)
|
||||
[](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())
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
26
client.go
26
client.go
|
@ -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:
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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})
|
||||
|
|
11
codecov.yml
11
codecov.yml
|
@ -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:
|
||||
|
|
62
config.go
62
config.go
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
314
connection.go
314
connection.go
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
BIN
docs/uQUIC.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
BIN
docs/uQUIC_nopadding.png
Normal file
BIN
docs/uQUIC_nopadding.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
12
errors.go
12
errors.go
|
@ -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" }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
69
framer.go
69
framer.go
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
44
go.mod
|
@ -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
113
go.sum
|
@ -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
104
http3/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# HTTP/3
|
||||
|
||||
[](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).
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
58
http3/error.go
Normal 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
|
||||
}
|
|
@ -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
40
http3/error_test.go
Normal 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)"))
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
})
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 => ../../
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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(
|
||||
|
|
90
integrationtests/self/qlog_dir_test.go
Normal file
90
integrationtests/self/qlog_dir_test.go
Normal 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"))
|
||||
})
|
||||
})
|
|
@ -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())
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -142,5 +142,6 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
runReceivingPeer(client)
|
||||
<-done1
|
||||
<-done2
|
||||
client.CloseWithError(0, "")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)))
|
||||
})
|
||||
})
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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),
|
||||
|
|
60
interface.go
60
interface.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
296
internal/ackhandler/ecn.go
Normal 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)
|
||||
}
|
272
internal/ackhandler/ecn_test.go
Normal file
272
internal/ackhandler/ecn_test.go
Normal 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
Loading…
Add table
Add a link
Reference in a new issue