├── .cargo └── config.toml ├── .codecov.yml ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── book.yml │ ├── codecov.yml │ ├── rust-android-run-tests-on-emulator.sh │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bench ├── Cargo.toml └── src │ ├── bin │ └── bulk.rs │ ├── lib.rs │ └── stats.rs ├── codecov.yml ├── deny.toml ├── docs ├── book │ ├── .gitignore │ ├── Cargo.toml │ ├── book.toml │ └── src │ │ ├── SUMMARY.md │ │ ├── bin │ │ ├── certificate.rs │ │ ├── data-transfer.rs │ │ └── set-up-connection.rs │ │ ├── images │ │ ├── hol.gif │ │ └── tcp-handshake.svg.png │ │ ├── networking-introduction.md │ │ ├── quic.md │ │ ├── quinn.md │ │ └── quinn │ │ ├── certificate.md │ │ ├── data-transfer.md │ │ └── set-up-connection.md ├── quin-logo.psd └── thumbnail.svg ├── fuzz ├── .cargo │ └── config.toml ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── packet.rs │ ├── streamid.rs │ └── streams.rs ├── perf ├── Cargo.toml └── src │ ├── bin │ ├── perf_client.rs │ └── perf_server.rs │ ├── lib.rs │ ├── noprotection.rs │ └── stats.rs ├── quinn-proto ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── bloom_token_log.rs │ ├── cid_generator.rs │ ├── cid_queue.rs │ ├── coding.rs │ ├── config │ ├── mod.rs │ └── transport.rs │ ├── congestion.rs │ ├── congestion │ ├── bbr │ │ ├── bw_estimation.rs │ │ ├── min_max.rs │ │ └── mod.rs │ ├── cubic.rs │ └── new_reno.rs │ ├── connection │ ├── ack_frequency.rs │ ├── assembler.rs │ ├── cid_state.rs │ ├── datagrams.rs │ ├── mod.rs │ ├── mtud.rs │ ├── pacing.rs │ ├── packet_builder.rs │ ├── packet_crypto.rs │ ├── paths.rs │ ├── send_buffer.rs │ ├── spaces.rs │ ├── stats.rs │ ├── streams │ │ ├── mod.rs │ │ ├── recv.rs │ │ ├── send.rs │ │ └── state.rs │ └── timer.rs │ ├── constant_time.rs │ ├── crypto.rs │ ├── crypto │ ├── ring_like.rs │ └── rustls.rs │ ├── endpoint.rs │ ├── frame.rs │ ├── lib.rs │ ├── packet.rs │ ├── range_set │ ├── array_range_set.rs │ ├── btree_range_set.rs │ ├── mod.rs │ └── tests.rs │ ├── shared.rs │ ├── tests │ ├── mod.rs │ ├── token.rs │ └── util.rs │ ├── token.rs │ ├── token_memory_cache.rs │ ├── transport_error.rs │ ├── transport_parameters.rs │ └── varint.rs ├── quinn-udp ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── benches │ └── throughput.rs ├── build.rs ├── src │ ├── cmsg │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── fallback.rs │ ├── lib.rs │ ├── unix.rs │ └── windows.rs └── tests │ └── tests.rs ├── quinn ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── benches │ └── bench.rs ├── build.rs ├── examples │ ├── README.md │ ├── client.rs │ ├── common │ │ └── mod.rs │ ├── connection.rs │ ├── insecure_connection.rs │ ├── server.rs │ └── single_socket.rs ├── src │ ├── connection.rs │ ├── endpoint.rs │ ├── incoming.rs │ ├── lib.rs │ ├── mutex.rs │ ├── recv_stream.rs │ ├── runtime.rs │ ├── runtime │ │ ├── async_io.rs │ │ └── tokio.rs │ ├── send_stream.rs │ ├── tests.rs │ └── work_limiter.rs └── tests │ └── many_connections.rs └── rustfmt.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | runner = "wasm-bindgen-test-runner" 3 | rustflags = ["--cfg", 'getrandom_backend="wasm_js"'] 4 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Do not notify until at least three results have been uploaded from the CI pipeline. 2 | # (Corresponds to the three platforms we check coverage on: Linux, macOS, and Windows.) 3 | codecov: 4 | notify: 5 | after_n_builds: 3 6 | comment: 7 | after_n_builds: 3 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @djc @Ralith @gretchenfrage 2 | /quinn-udp @djc @Ralith @gretchenfrage @mxinden 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: quinn-rs 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "13:00" 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: Build book 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup mdBook 14 | uses: peaceiris/actions-mdbook@v2 15 | with: 16 | mdbook-version: 'latest' 17 | - run: mdbook build ./docs/book 18 | - name: Deploy 19 | uses: peaceiris/actions-gh-pages@v4 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: ./docs/book/book 23 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | push: 5 | branches: ['main', '0.8.x'] 6 | 7 | jobs: 8 | coverage: 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@stable 16 | - uses: taiki-e/install-action@cargo-llvm-cov 17 | - shell: bash 18 | # Run llvm-cov _without_ the "aws-lc-rs-fips" or "rustls-aws-lc-rs-fips" features, since 19 | # they have complex build requirements: 20 | # https://github.com/aws/aws-lc/blob/3263ce2a553e4e917217fb487f8c6f488fcb1866/BUILDING.md#build-prerequisites 21 | # 22 | # This list of features was determined using: 23 | # cargo metadata --format-version 1 --no-deps \ 24 | # | jq -r ' .packages[].features | keys[]' \ 25 | # | sort -u \ 26 | # | grep -vFx -e 'default' -e 'aws-lc-rs-fips' -e 'rustls-aws-lc-rs-fips' \ 27 | # | paste -sd ',' - 28 | run: | 29 | cargo llvm-cov \ 30 | --features="arbitrary,async-io,async-std,aws-lc-rs,bloom,direct-log,fast-apple-datapath,futures-io,json-output,lock_tracking,log,platform-verifier,ring,runtime-async-std,runtime-smol,runtime-tokio,rustls,rustls-aws-lc-rs,rustls-log,rustls-ring,serde,serde_json,smol,tracing" \ 31 | --workspace --lcov --output-path lcov.info 32 | - name: Upload coverage to Codecov 33 | uses: codecov/codecov-action@v5 34 | with: 35 | token: ${{ secrets.CODECOV_TOKEN }} 36 | files: lcov.info 37 | fail_ci_if_error: true 38 | -------------------------------------------------------------------------------- /.github/workflows/rust-android-run-tests-on-emulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | adb wait-for-device 5 | while [[ -z "$(adb shell getprop sys.boot_completed | tr -d '\r')" ]]; do sleep 1; done 6 | 7 | any_failures=0 8 | for test in $(find target/$TARGET/debug/deps/ -type f -executable ! -name "*.so" -name "*-*"); do 9 | adb push "$test" /data/local/tmp/ 10 | adb shell chmod +x /data/local/tmp/$(basename "$test") 11 | adb shell API_LEVEL=$API_LEVEL /data/local/tmp/$(basename "$test") || any_failures=1 12 | done 13 | 14 | exit $any_failures 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | **/target/ 3 | **/*.rs.bk 4 | 5 | .idea 6 | .DS_Store 7 | .vscode 8 | .zed 9 | 10 | cargo-test-* 11 | tarpaulin-report.html 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf", "fuzz", "docs/book"] 3 | default-members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf"] 4 | resolver = "2" 5 | 6 | [workspace.package] 7 | rust-version = "1.71" 8 | edition = "2021" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/quinn-rs/quinn" 11 | keywords = ["quic"] 12 | categories = ["network-programming", "asynchronous"] 13 | 14 | [workspace.dependencies] 15 | anyhow = "1.0.22" 16 | arbitrary = { version = "1.0.1", features = ["derive"] } 17 | async-io = "2" 18 | async-std = "1.11" 19 | assert_matches = "1.1" 20 | aws-lc-rs = { version = "1.9", default-features = false } 21 | bencher = "0.1.5" 22 | bytes = "1" 23 | clap = { version = "4", features = ["derive"] } 24 | crc = "3" 25 | directories-next = "2" 26 | fastbloom = "0.9" 27 | futures-io = "0.3.19" 28 | getrandom = { version = "0.3", default-features = false } 29 | hdrhistogram = { version = "7.2", default-features = false } 30 | hex-literal = "0.4" 31 | lru-slab = "0.1.2" 32 | lazy_static = "1" 33 | log = "0.4" 34 | once_cell = "1.19" 35 | pin-project-lite = "0.2" 36 | rand = "0.9" 37 | rcgen = "0.13" 38 | ring = "0.17" 39 | rustc-hash = "2" 40 | rustls = { version = "0.23.5", default-features = false, features = ["std"] } 41 | rustls-pemfile = "2" 42 | rustls-platform-verifier = "0.5" 43 | rustls-pki-types = "1.7" 44 | serde = { version = "1.0", features = ["derive"] } 45 | serde_json = "1" 46 | slab = "0.4.6" 47 | smol = "2" 48 | socket2 = "0.5" 49 | thiserror = "2.0.3" 50 | tinyvec = { version = "1.1", features = ["alloc"] } 51 | tokio = { version = "1.28.1", features = ["sync"] } 52 | tracing = { version = "0.1.10", default-features = false, features = ["std"] } 53 | tracing-futures = { version = "0.2.0", default-features = false, features = ["std-future"] } 54 | tracing-subscriber = { version = "0.3.0", default-features = false, features = ["env-filter", "fmt", "ansi", "time", "local-time"] } 55 | url = "2" 56 | wasm-bindgen-test = { version = "0.3.45" } 57 | web-time = "1" 58 | windows-sys = { version = ">=0.52, <=0.59", features = ["Win32_Foundation", "Win32_System_IO", "Win32_Networking_WinSock"] } 59 | cfg_aliases = "0.2" 60 | 61 | [profile.bench] 62 | debug = true 63 | 64 | [profile.release] 65 | debug = true 66 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The quinn Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![Documentation](https://docs.rs/quinn/badge.svg)](https://docs.rs/quinn/) 4 | [![Crates.io](https://img.shields.io/crates/v/quinn.svg)](https://crates.io/crates/quinn) 5 | [![Build status](https://github.com/quinn-rs/quinn/workflows/CI/badge.svg)](https://github.com/djc/quinn/actions?query=workflow%3ACI) 6 | [![codecov](https://codecov.io/gh/quinn-rs/quinn/branch/main/graph/badge.svg)](https://codecov.io/gh/quinn-rs/quinn) 7 | [![Chat](https://img.shields.io/badge/chat-%23quinn:matrix.org-%2346BC99?logo=matrix)](https://matrix.to/#/#quinn:matrix.org) 8 | [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/SGPEcDfVzh) 9 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT) 10 | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE-APACHE) 11 | 12 | Quinn is a pure-Rust, async-compatible implementation of the IETF [QUIC][quic] transport protocol. 13 | The project was founded by [Dirkjan Ochtman](https://github.com/djc) and 14 | [Benjamin Saunders](https://github.com/Ralith) as a side project in 2018, and has seen more than 15 | 30 releases since then. If you're using Quinn in a commercial setting, please consider 16 | [sponsoring](https://opencollective.com/quinn-rs) the project. 17 | 18 | ## Features 19 | 20 | - Simultaneous client/server operation 21 | - Ordered and unordered stream reads for improved performance 22 | - Works on stable Rust, tested on Linux, macOS and Windows 23 | - Pluggable cryptography, with a standard implementation backed by 24 | [rustls][rustls] and [*ring*][ring] 25 | - Application-layer datagrams for small, unreliable messages 26 | - Future-based async API 27 | - Minimum supported Rust version of 1.71 28 | 29 | ## Overview 30 | 31 | - **quinn:** High-level async API based on tokio, see [examples][examples] for usage. This will be used by most developers. (Basic benchmarks are included.) 32 | - **quinn-proto:** Deterministic state machine of the protocol which performs [**no** I/O][sans-io] internally and is suitable for use with custom event loops (and potentially a C or C++ API). 33 | - **quinn-udp:** UDP sockets with ECN information tuned for the protocol. 34 | - **bench:** Benchmarks without any framework. 35 | - **fuzz:** Fuzz tests. 36 | 37 | # Getting Started 38 | 39 | **Examples** 40 | 41 | ```sh 42 | $ cargo run --example server ./ 43 | $ cargo run --example client https://localhost:4433/Cargo.toml 44 | ``` 45 | 46 | This launches an HTTP 0.9 server on the loopback address serving the current 47 | working directory, with the client fetching `./Cargo.toml`. By default, the 48 | server generates a self-signed certificate and stores it to disk, where the 49 | client will automatically find and trust it. 50 | 51 | **Links** 52 | 53 | - Talk at [RustFest Paris (May 2018) presentation][talk]; [slides][slides]; [YouTube][youtube] 54 | - Usage [examples][examples] 55 | - Guide [book][documentation] 56 | 57 | ## Usage Notes 58 | 59 |
60 | 61 | Click to show the notes 62 | 63 | 64 | ### Buffers 65 | 66 | A Quinn endpoint corresponds to a single UDP socket, no matter how many 67 | connections are in use. Handling high aggregate data rates on a single endpoint 68 | can require a larger UDP buffer than is configured by default in most 69 | environments. If you observe erratic latency and/or throughput over a stable 70 | network link, consider increasing the buffer sizes used. For example, you could 71 | adjust the `SO_SNDBUF` and `SO_RCVBUF` options of the UDP socket to be used 72 | before passing it in to Quinn. Note that some platforms (e.g. Linux) require 73 | elevated privileges or modified system configuration for a process to increase 74 | its UDP buffer sizes. 75 | 76 | ### Certificates 77 | 78 | By default, Quinn clients validate the cryptographic identity of servers they 79 | connect to. This prevents an active, on-path attacker from intercepting 80 | messages, but requires trusting some certificate authority. For many purposes, 81 | this can be accomplished by using certificates from [Let's Encrypt][letsencrypt] 82 | for servers, and relying on the default configuration for clients. 83 | 84 | For some cases, including peer-to-peer, trust-on-first-use, deliberately 85 | insecure applications, or any case where servers are not identified by domain 86 | name, this isn't practical. Arbitrary certificate validation logic can be 87 | implemented by enabling the `dangerous_configuration` feature of `rustls` and 88 | constructing a Quinn `ClientConfig` with an overridden certificate verifier by 89 | hand. 90 | 91 | When operating your own certificate authority doesn't make sense, [rcgen][rcgen] 92 | can be used to generate self-signed certificates on demand. To support 93 | trust-on-first-use, servers that automatically generate self-signed certificates 94 | should write their generated certificate to persistent storage and reuse it on 95 | future runs. 96 | 97 |
98 |

99 | 100 | ## Contribution 101 | 102 | All feedback welcome. Feel free to file bugs, requests for documentation and 103 | any other feedback to the [issue tracker][issues]. 104 | 105 | The quinn-proto test suite uses simulated IO for reproducibility and to avoid 106 | long sleeps in certain timing-sensitive tests. If the `SSLKEYLOGFILE` 107 | environment variable is set, the tests will emit UDP packets for inspection 108 | using external protocol analyzers like Wireshark, and NSS-compatible key logs 109 | for the client side of each connection will be written to the path specified in 110 | the variable. 111 | 112 | The minimum supported Rust version for published releases of our 113 | crates will always be at least 6 months old at the time of release. 114 | 115 | [quic]: https://quicwg.github.io/ 116 | [issues]: https://github.com/djc/quinn/issues 117 | [rustls]: https://github.com/ctz/rustls 118 | [ring]: https://github.com/briansmith/ring 119 | [talk]: https://paris.rustfest.eu/sessions/a-quic-future-in-rust 120 | [slides]: https://github.com/djc/talks/blob/ff760845b51ba4836cce82e7f2c640ecb5fd59fa/2018-05-26%20A%20QUIC%20future%20in%20Rust/Quinn-Speaker.pdf 121 | [animation]: https://dirkjan.ochtman.nl/files/head-of-line-blocking.html 122 | [youtube]: https://www.youtube.com/watch?v=EHgyY5DNdvI 123 | [letsencrypt]: https://letsencrypt.org/ 124 | [rcgen]: https://crates.io/crates/rcgen 125 | [examples]: https://github.com/djc/quinn/tree/main/quinn/examples 126 | [documentation]: https://quinn-rs.github.io/quinn/networking-introduction.html 127 | [sans-io]: https://sans-io.readthedocs.io/how-to-sans-io.html 128 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | bytes = { workspace = true } 11 | clap = { workspace = true } 12 | hdrhistogram = { workspace = true } 13 | quinn = { path = "../quinn", features = ["ring"] } 14 | rcgen = { workspace = true } 15 | rustls = { workspace = true } 16 | tokio = { workspace = true, features = ["rt"] } 17 | tracing = { workspace = true } 18 | tracing-subscriber = { workspace = true } 19 | -------------------------------------------------------------------------------- /bench/src/stats.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use hdrhistogram::Histogram; 4 | 5 | #[derive(Default)] 6 | pub struct Stats { 7 | pub total_size: u64, 8 | pub total_duration: Duration, 9 | pub streams: usize, 10 | pub stream_stats: StreamStats, 11 | } 12 | 13 | impl Stats { 14 | pub fn stream_finished(&mut self, stream_result: TransferResult) { 15 | self.total_size += stream_result.size; 16 | self.streams += 1; 17 | 18 | self.stream_stats 19 | .duration_hist 20 | .record(stream_result.duration.as_millis() as u64) 21 | .unwrap(); 22 | self.stream_stats 23 | .throughput_hist 24 | .record(stream_result.throughput as u64) 25 | .unwrap(); 26 | } 27 | 28 | pub fn print(&self, stat_name: &str) { 29 | println!("Overall {stat_name} stats:\n"); 30 | println!( 31 | "Transferred {} bytes on {} streams in {:4.2?} ({:.2} MiB/s)\n", 32 | self.total_size, 33 | self.streams, 34 | self.total_duration, 35 | throughput_bps(self.total_duration, self.total_size) / 1024.0 / 1024.0 36 | ); 37 | 38 | println!("Stream {stat_name} metrics:\n"); 39 | 40 | println!(" │ Throughput │ Duration "); 41 | println!("──────┼───────────────┼──────────"); 42 | 43 | let print_metric = |label: &'static str, get_metric: fn(&Histogram) -> u64| { 44 | println!( 45 | " {} │ {:7.2} MiB/s │ {:>9.2?}", 46 | label, 47 | get_metric(&self.stream_stats.throughput_hist) as f64 / 1024.0 / 1024.0, 48 | Duration::from_millis(get_metric(&self.stream_stats.duration_hist)) 49 | ); 50 | }; 51 | 52 | print_metric("AVG ", |hist| hist.mean() as u64); 53 | print_metric("P0 ", |hist| hist.value_at_quantile(0.00)); 54 | print_metric("P10 ", |hist| hist.value_at_quantile(0.10)); 55 | print_metric("P50 ", |hist| hist.value_at_quantile(0.50)); 56 | print_metric("P90 ", |hist| hist.value_at_quantile(0.90)); 57 | print_metric("P100", |hist| hist.value_at_quantile(1.00)); 58 | } 59 | } 60 | 61 | pub struct StreamStats { 62 | pub duration_hist: Histogram, 63 | pub throughput_hist: Histogram, 64 | } 65 | 66 | impl Default for StreamStats { 67 | fn default() -> Self { 68 | Self { 69 | duration_hist: Histogram::::new(3).unwrap(), 70 | throughput_hist: Histogram::::new(3).unwrap(), 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug)] 76 | pub struct TransferResult { 77 | pub duration: Duration, 78 | pub size: u64, 79 | pub throughput: f64, 80 | } 81 | 82 | impl TransferResult { 83 | pub fn new(duration: Duration, size: u64) -> Self { 84 | let throughput = throughput_bps(duration, size); 85 | TransferResult { 86 | duration, 87 | size, 88 | throughput, 89 | } 90 | } 91 | } 92 | 93 | pub fn throughput_bps(duration: Duration, size: u64) -> f64 { 94 | (size as f64) / (duration.as_secs_f64()) 95 | } 96 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off 4 | project: off 5 | range: "50..100" 6 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = [ 3 | "Apache-2.0", 4 | "BSD-2-Clause", 5 | "BSD-3-Clause", 6 | "ISC", 7 | "MIT", 8 | "MPL-2.0", 9 | "NCSA", 10 | "OpenSSL", 11 | "Unicode-3.0", 12 | ] 13 | private = { ignore = true } 14 | 15 | [[licenses.clarify]] 16 | name = "ring" 17 | expression = "ISC AND MIT AND OpenSSL" 18 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 19 | 20 | [advisories] 21 | ignore = [ 22 | # `paste` is unmaintained 23 | "RUSTSEC-2024-0436", 24 | ] 25 | -------------------------------------------------------------------------------- /docs/book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/book/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "book" 3 | version = "0.1.0" 4 | rust-version.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | 11 | [dependencies] 12 | anyhow.workspace = true 13 | bytes = { workspace = true } 14 | quinn = { version = "0.11.7", path = "../../quinn" } 15 | rcgen.workspace = true 16 | rustls.workspace = true 17 | -------------------------------------------------------------------------------- /docs/book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Timon Post"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Quinn" 7 | -------------------------------------------------------------------------------- /docs/book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Networking introduction](networking-introduction.md) 4 | - [QUIC introduction](quic.md) 5 | - [QUINN Introduction](quinn.md) 6 | - [Certificate Configuration](quinn/certificate.md) 7 | - [Connection Setup](quinn/set-up-connection.md) 8 | - [Data Transfer](quinn/data-transfer.md) 9 | -------------------------------------------------------------------------------- /docs/book/src/bin/certificate.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, sync::Arc}; 2 | 3 | use quinn::{ 4 | ClientConfig, 5 | crypto::rustls::{NoInitialCipherSuite, QuicClientConfig}, 6 | }; 7 | use rustls::{ 8 | DigitallySignedStruct, SignatureScheme, 9 | client::danger, 10 | crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature}, 11 | pki_types::{ 12 | CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, UnixTime, pem::PemObject, 13 | }, 14 | }; 15 | 16 | #[allow(unused_variables)] 17 | fn main() { 18 | let (self_signed_certs, self_signed_key) = generate_self_signed_cert().unwrap(); 19 | let (certs, key) = read_certs_from_file().unwrap(); 20 | let server_config = quinn::ServerConfig::with_single_cert(certs, key); 21 | let client_config = quinn::ClientConfig::with_platform_verifier(); 22 | } 23 | 24 | #[allow(dead_code)] // Included in `certificate.md` 25 | fn configure_client() -> Result { 26 | let crypto = rustls::ClientConfig::builder() 27 | .dangerous() 28 | .with_custom_certificate_verifier(SkipServerVerification::new()) 29 | .with_no_client_auth(); 30 | 31 | Ok(ClientConfig::new(Arc::new(QuicClientConfig::try_from( 32 | crypto, 33 | )?))) 34 | } 35 | 36 | // Implementation of `ServerCertVerifier` that verifies everything as trustworthy. 37 | #[derive(Debug)] 38 | struct SkipServerVerification(Arc); 39 | 40 | impl SkipServerVerification { 41 | fn new() -> Arc { 42 | Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider()))) 43 | } 44 | } 45 | 46 | impl danger::ServerCertVerifier for SkipServerVerification { 47 | fn verify_server_cert( 48 | &self, 49 | _end_entity: &CertificateDer<'_>, 50 | _intermediates: &[CertificateDer<'_>], 51 | _server_name: &ServerName<'_>, 52 | _ocsp: &[u8], 53 | _now: UnixTime, 54 | ) -> Result { 55 | Ok(danger::ServerCertVerified::assertion()) 56 | } 57 | fn verify_tls12_signature( 58 | &self, 59 | message: &[u8], 60 | cert: &CertificateDer<'_>, 61 | dss: &DigitallySignedStruct, 62 | ) -> Result { 63 | verify_tls12_signature( 64 | message, 65 | cert, 66 | dss, 67 | &self.0.signature_verification_algorithms, 68 | ) 69 | } 70 | 71 | fn verify_tls13_signature( 72 | &self, 73 | message: &[u8], 74 | cert: &CertificateDer<'_>, 75 | dss: &DigitallySignedStruct, 76 | ) -> Result { 77 | verify_tls13_signature( 78 | message, 79 | cert, 80 | dss, 81 | &self.0.signature_verification_algorithms, 82 | ) 83 | } 84 | 85 | fn supported_verify_schemes(&self) -> Vec { 86 | self.0.signature_verification_algorithms.supported_schemes() 87 | } 88 | } 89 | 90 | fn generate_self_signed_cert() 91 | -> Result<(CertificateDer<'static>, PrivatePkcs8KeyDer<'static>), Box> { 92 | let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?; 93 | let cert_der = CertificateDer::from(cert.cert); 94 | let key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); 95 | Ok((cert_der, key)) 96 | } 97 | 98 | fn read_certs_from_file() 99 | -> Result<(Vec>, PrivateKeyDer<'static>), Box> { 100 | let certs = CertificateDer::pem_file_iter("./fullchain.pem") 101 | .unwrap() 102 | .map(|cert| cert.unwrap()) 103 | .collect(); 104 | let key = PrivateKeyDer::from_pem_file("./privkey.pem").unwrap(); 105 | Ok((certs, key)) 106 | } 107 | -------------------------------------------------------------------------------- /docs/book/src/bin/data-transfer.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use quinn::Connection; 3 | 4 | fn main() {} 5 | 6 | #[allow(dead_code, unused_variables)] // Included in `data-transfer.md` 7 | async fn open_bidirectional_stream(connection: Connection) -> anyhow::Result<()> { 8 | let (mut send, mut recv) = connection.open_bi().await?; 9 | send.write_all(b"test").await?; 10 | send.finish()?; 11 | let received = recv.read_to_end(10).await?; 12 | Ok(()) 13 | } 14 | 15 | #[allow(dead_code)] // Included in `data-transfer.md` 16 | async fn receive_bidirectional_stream(connection: Connection) -> anyhow::Result<()> { 17 | while let Ok((mut send, mut recv)) = connection.accept_bi().await { 18 | // Because it is a bidirectional stream, we can both send and receive. 19 | println!("request: {:?}", recv.read_to_end(50).await?); 20 | send.write_all(b"response").await?; 21 | send.finish()?; 22 | } 23 | Ok(()) 24 | } 25 | 26 | #[allow(dead_code)] // Included in `data-transfer.md` 27 | async fn open_unidirectional_stream(connection: Connection) -> anyhow::Result<()> { 28 | let mut send = connection.open_uni().await?; 29 | send.write_all(b"test").await?; 30 | send.finish()?; 31 | Ok(()) 32 | } 33 | 34 | #[allow(dead_code)] // Included in `data-transfer.md` 35 | async fn receive_unidirectional_stream(connection: Connection) -> anyhow::Result<()> { 36 | while let Ok(mut recv) = connection.accept_uni().await { 37 | // Because it is a unidirectional stream, we can only receive not send back. 38 | println!("{:?}", recv.read_to_end(50).await?); 39 | } 40 | Ok(()) 41 | } 42 | 43 | #[allow(dead_code)] // Included in `data-transfer.md` 44 | async fn send_unreliable(connection: Connection) -> anyhow::Result<()> { 45 | connection.send_datagram(Bytes::from(&b"test"[..]))?; 46 | Ok(()) 47 | } 48 | 49 | #[allow(dead_code)] // Included in `data-transfer.md` 50 | async fn receive_datagram(connection: Connection) -> anyhow::Result<()> { 51 | while let Ok(received_bytes) = connection.read_datagram().await { 52 | // Because it is a unidirectional stream, we can only receive not send back. 53 | println!("request: {:?}", received_bytes); 54 | } 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /docs/book/src/bin/set-up-connection.rs: -------------------------------------------------------------------------------- 1 | use quinn::{Endpoint, ServerConfig}; 2 | use std::error::Error; 3 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 4 | 5 | fn main() {} 6 | 7 | #[allow(dead_code, unused_variables)] // Included in `set-up-connection.md` 8 | async fn server(config: ServerConfig) -> Result<(), Box> { 9 | // Bind this endpoint to a UDP socket on the given server address. 10 | let endpoint = Endpoint::server(config, SERVER_ADDR)?; 11 | 12 | // Start iterating over incoming connections. 13 | while let Some(conn) = endpoint.accept().await { 14 | let connection = conn.await?; 15 | 16 | // Save connection somewhere, start transferring, receiving data, see DataTransfer tutorial. 17 | } 18 | 19 | Ok(()) 20 | } 21 | 22 | #[allow(dead_code, unused_variables)] // Included in `set-up-connection.md` 23 | async fn client() -> Result<(), Box> { 24 | // Bind this endpoint to a UDP socket on the given client address. 25 | let endpoint = Endpoint::client(CLIENT_ADDR)?; 26 | 27 | // Connect to the server passing in the server name which is supposed to be in the server certificate. 28 | let connection = endpoint.connect(SERVER_ADDR, SERVER_NAME)?.await?; 29 | 30 | // Start transferring, receiving data, see data transfer page. 31 | 32 | Ok(()) 33 | } 34 | 35 | const SERVER_NAME: &str = "localhost"; 36 | const LOCALHOST_V4: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); 37 | const CLIENT_ADDR: SocketAddr = SocketAddr::new(LOCALHOST_V4, 5000); 38 | const SERVER_ADDR: SocketAddr = SocketAddr::new(LOCALHOST_V4, 5001); 39 | -------------------------------------------------------------------------------- /docs/book/src/images/hol.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinn-rs/quinn/e8dc5a2eda57163bfbaba52ba57bf5b7a0027e22/docs/book/src/images/hol.gif -------------------------------------------------------------------------------- /docs/book/src/images/tcp-handshake.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinn-rs/quinn/e8dc5a2eda57163bfbaba52ba57bf5b7a0027e22/docs/book/src/images/tcp-handshake.svg.png -------------------------------------------------------------------------------- /docs/book/src/networking-introduction.md: -------------------------------------------------------------------------------- 1 | # Networking Introduction 2 | 3 | In this chapter, you will find a very short introduction to various networking concepts. 4 | These concepts are important to understanding when to use QUIC. 5 | 6 | ## 1. TCP/IP and UDP Comparison 7 | 8 | Let's compare TCP, UDP, and QUIC. 9 | 10 | - **unreliable**: Transport packets are not assured of arrival and ordering. 11 | - **reliable**: Transport packets are assured of arrival and ordering. 12 | 13 | | Feature | TCP | UDP | QUIC 14 | | :-------------: | :-------------: | :-------------: | :-------------: | 15 | | [Connection-Oriented][6] | Yes | No | Yes 16 | | Transport Guarantees | Reliable | Unreliable | Reliable ('a) 17 | | Packet Transfer | [Stream-based][4] | Message based | Stream based 18 | | Header Size | ~20 bytes | 8 bytes | ~16 bytes (depending on connection id) 19 | | [Control Flow, Congestion Avoidance/Control][5] | Yes | No | Yes ('b) 20 | | Based On | [IP][3] | [IP][3] | UDP 21 | 22 | 'a. Unreliable is supported as an extension. 23 | 'b. QUIC control flow/congestion implementations will run in userspace whereas in TCP it's running in kernel space, 24 | however, there might be a kernel implementation for QUIC in the future. 25 | 26 | ## 2. Issues with TCP 27 | 28 | TCP has been around for a long time and was not designed with the modern internet in mind. 29 | It has several difficulties that QUIC tries to resolve. 30 | 31 | ### Head-of-line Blocking 32 | 33 | One of the biggest issues with TCP is that of Head-of-line blocking. 34 | It is a convenient feature because it ensures that all packages are sent and arrive in order. 35 | However, in cases of high throughput (multiplayer game networking) and big load in a short time (web page load), this can severely impact latency. 36 | 37 | The issue is demonstrated in the following animation: 38 | 39 | ![Head of line blocking][animation] 40 | 41 | This animation shows that if a certain packet drops in transmission, all packets have to wait at the transport layer until it is resent by the other end. Once the delayed packet arrives at its destination, all later packets are passed on to the destination application together. 42 | 43 | Let's look at two areas where head-of-line blocking causes problems. 44 | 45 | **Web Networking** 46 | 47 | As websites increasingly need a larger number of HTTP requests (HTML, CSS, JavaScript, images) to display all content, the impact of head-of-line blocking has also increased. 48 | To improve on this, HTTP 2 introduced request multiplexing within a TCP data stream, which allows servers to stream multiple responses at the same time. 49 | However, data loss of a single packet will still block all response streams because they exist within the context of a single TCP stream. 50 | 51 | ### Connection Setup Duration 52 | 53 | In the usual TCP + TLS + HTTP stack, TCP needs 6 handshake messages to set up a session between server and client. TLS performs its own, sending 4 messages for setting up an initial connection over TLS 1.3. By integrating the transport protocol and TLS handshakes, QUIC can make connection setup more efficient. 54 | 55 | [animation]: ./images/hol.gif 56 | 57 | [1]: https://en.wikipedia.org/wiki/Packet_loss 58 | [2]: https://observersupport.viavisolutions.com/html_doc/current/index.html#page/gigastor_hw/packet_deduplicating.html 59 | [3]: https://nl.wikipedia.org/wiki/Internetprotocol 60 | [4]: https://en.wikipedia.org/wiki/Stream_(computing) 61 | [5]: https://en.wikipedia.org/wiki/TCP_congestion_control 62 | [6]: https://en.wikipedia.org/wiki/Connection-oriented_communication 63 | [7]: https://en.wikipedia.org/wiki/Internet_protocol_suite 64 | [8]: https://en.wikipedia.org/wiki/IP_fragmentation 65 | -------------------------------------------------------------------------------- /docs/book/src/quic.md: -------------------------------------------------------------------------------- 1 | # The QUIC protocol 2 | [QUIC] is a general-purpose network protocol built on top of UDP, 3 | and [standardized][rfc] by the [IETF]. Although QUIC is still relatively new, 4 | the protocol is used for all connections from Chrome web browsers to the Google servers. 5 | 6 | QUIC solves a number of transport-layer and application-layer problems experienced by modern web applications. 7 | It is very similar to TCP+TLS+HTTP2, but implemented on top of UDP. 8 | Having QUIC as a self-contained protocol allows innovations which aren’t 9 | possible with existing protocols as they are hampered by legacy clients and middleboxes. 10 | 11 | Key advantages of QUIC over TCP+TLS+HTTP2 include: 12 | * Improved connection establishment speed (0-rtt). 13 | * Improved congestion control by moving congestion control algorithms into the user space at both endpoints. 14 | * Improved bandwidth estimation in each direction to avoid congestion. 15 | * Improved multiplexing without head-of-line blocking. 16 | * Contains forward error correction (FEC). 17 | 18 | While QUIC's intentions are originally web-oriented, it offers interesting opportunities in other areas like game networking. 19 | One thing is for sure, QUIC has many great potentials and will serve us in the future with HTTP/3. 20 | 21 | In the upcoming chapter we will be discussing various aspects of QUIC also in relation to Quinn. 22 | 23 | [rfc]: https://www.rfc-editor.org/rfc/rfc9000.html 24 | [IETF]: https://www.ietf.org/ 25 | [QUIC]: https://en.wikipedia.org/wiki/QUIC 26 | -------------------------------------------------------------------------------- /docs/book/src/quinn.md: -------------------------------------------------------------------------------- 1 | {{#include ../../../README.md}} -------------------------------------------------------------------------------- /docs/book/src/quinn/certificate.md: -------------------------------------------------------------------------------- 1 | # Certificates 2 | 3 | In this chapter, we discuss the configuration of the certificates that are **required** for a working Quinn connection. 4 | 5 | As QUIC uses TLS 1.3 for authentication of connections, the server needs to provide the client with a certificate confirming its identity, and the client must be configured to trust the certificates it receives from the server. 6 | 7 | ## Insecure Connection 8 | 9 | For our example use case, the easiest way to allow the client to trust our server is to disable certificate verification (don't do this in production!). 10 | When the [rustls][3] `dangerous_configuration` feature flag is enabled, a client can be configured to trust any server. 11 | 12 | Start by adding a [rustls][3] dependency with the `dangerous_configuration` feature flag to your `Cargo.toml` file. 13 | 14 | ```toml 15 | quinn = "0.11" 16 | rustls = "0.23" 17 | ``` 18 | 19 | Then, allow the client to skip the certificate validation by implementing [ServerCertVerifier][ServerCertVerifier] and letting it assert verification for any server. 20 | 21 | ```rust 22 | {{#include ../bin/certificate.rs:36:88}} 23 | ``` 24 | 25 | After that, modify the [ClientConfig][ClientConfig] to use this [ServerCertVerifier][ServerCertVerifier] implementation. 26 | 27 | ```rust 28 | {{#include ../bin/certificate.rs:25:34}} 29 | ``` 30 | 31 | Finally, if you plug this [ClientConfig][ClientConfig] into the [Endpoint::set_default_client_config()][set_default_client_config] your client endpoint should verify all connections as trustworthy. 32 | 33 | ## Using Certificates 34 | 35 | In this section, we look at certifying an endpoint with a certificate. 36 | The certificate can be signed with its key, or with a certificate authority's key. 37 | 38 | ### Self Signed Certificates 39 | 40 | Relying on [self-signed][5] certificates means that clients allow servers to sign their certificates. 41 | This is simpler because no third party is involved in signing the server's certificate. 42 | However, self-signed certificates do not protect users from person-in-the-middle attacks, because an interceptor can trivially replace the certificate with one that it has signed. Self-signed certificates, among other options, can be created using the [rcgen][4] crate or the openssl binary. 43 | This example uses [rcgen][4] to generate a certificate. 44 | 45 | Let's look at an example: 46 | 47 | ```rust 48 | {{#include ../bin/certificate.rs:90:96}} 49 | ``` 50 | 51 | _Note that [generate_simple_self_signed][generate_simple_self_signed] returns a [Certificate][2] that can be serialized to both `.der` and `.pem` formats._ 52 | 53 | ### Non-self-signed Certificates 54 | 55 | For this example, we use [Let's Encrypt][6], a well-known Certificate Authority ([CA][1]) (certificate issuer) which distributes certificates for free. 56 | 57 | **Generate Certificate** 58 | 59 | [certbot][7] can be used with Let's Encrypt to generate certificates; its website comes with clear instructions. 60 | Because we're generating a certificate for an internal test server, the process used will be slightly different compared to what you would do when generating certificates for an existing (public) website. 61 | 62 | On the certbot website, select that you do not have a public web server and follow the given installation instructions. 63 | certbot must answer a cryptographic challenge of the Let's Encrypt API to prove that you control the domain. 64 | It needs to listen on port 80 (HTTP) or 443 (HTTPS) to achieve this. Open the appropriate port in your firewall and router. 65 | 66 | If certbot is installed, run `certbot certonly --standalone`, this command will start a web server in the background and start the challenge. 67 | certbot asks for the required data and writes the certificates to `fullchain.pem` and the private key to `privkey.pem`. 68 | These files can then be referenced in code. 69 | 70 | ```rust 71 | {{#include ../bin/certificate.rs:98:106}} 72 | ``` 73 | 74 | ### Configuring Certificates 75 | 76 | Now that you have a valid certificate, the client and server need to be configured to use it. 77 | After configuring plug the configuration into the `Endpoint`. 78 | 79 | **Configure Server** 80 | 81 | ```rust 82 | {{#include ../bin/certificate.rs:20}} 83 | ``` 84 | 85 | This is the only thing you need to do for your server to be secured. 86 | 87 | **Configure Client** 88 | 89 | ```rust 90 | {{#include ../bin/certificate.rs:21}} 91 | ``` 92 | 93 | This is the only thing you need to do for your client to trust a server certificate signed by a conventional certificate authority. 94 | 95 |

96 | 97 | [Next](set-up-connection.md), let's have a look at how to set up a connection. 98 | 99 | [1]: https://en.wikipedia.org/wiki/Certificate_authority 100 | [2]: https://en.wikipedia.org/wiki/Public_key_certificate 101 | [3]: https://github.com/ctz/rustls 102 | [4]: https://github.com/est31/rcgen 103 | [5]: https://en.wikipedia.org/wiki/Self-signed_certificate#:~:text=In%20cryptography%20and%20computer%20security,a%20CA%20aim%20to%20provide. 104 | [6]: https://letsencrypt.org/getting-started/ 105 | [7]: https://certbot.eff.org/instructions 106 | [ClientConfig]: https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html 107 | [ServerCertVerifier]: https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html 108 | [set_default_client_config]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.set_default_client_config 109 | [generate_simple_self_signed]: https://docs.rs/rcgen/latest/rcgen/fn.generate_simple_self_signed.html 110 | [Certificate]: https://docs.rs/rcgen/latest/rcgen/struct.Certificate.html 111 | -------------------------------------------------------------------------------- /docs/book/src/quinn/data-transfer.md: -------------------------------------------------------------------------------- 1 | # Data Transfer 2 | 3 | The [previous chapter](set-up-connection.md) explained how to set up an [Endpoint][Endpoint] 4 | and then get access to a [Connection][Connection]. 5 | This chapter continues with the subject of sending data over this connection. 6 | 7 | ## Multiplexing 8 | 9 | Multiplexing is the act of combining data from multiple streams into a single stream. 10 | This can have a significant positive effect on the performance of the application. 11 | With QUIC, the programmer is in full control over the stream allocation. 12 | 13 | ## Stream Types 14 | 15 | QUIC provides support for both stream and message-based communication. 16 | Streams and messages can be initiated both on the client and server. 17 | 18 | | Type | Description | Reference | 19 | | :----------------------------------- | :-------------------------------------- | :--------------------------------- | 20 | | **Bidirectional Stream** | two way stream communication. | see [open_bi][open_bi] | 21 | | **Unidirectional Stream** | one way stream communication. | see [open_uni][open_uni] | 22 | | **Unreliable Messaging (extension)** | message based unreliable communication. | see [send_datagram][send_datagram] | 23 | 24 | ## How to Use 25 | 26 | New streams can be created with [Connection][Connection]'s [open_bi()][open_bi] and 27 | [open_uni()][open_uni] methods. 28 | 29 | ## Bidirectional Streams 30 | 31 | With bidirectional streams, data can be sent in both directions. 32 | For example, from the connection initiator to the peer and the other way around. 33 | 34 | _open bidirectional stream_ 35 | 36 | ```rust 37 | {{#include ../bin/data-transfer.rs:7:13}} 38 | ``` 39 | 40 | _iterate incoming bidirectional stream(s)_ 41 | 42 | ```rust 43 | {{#include ../bin/data-transfer.rs:16:24}} 44 | ``` 45 | 46 | ## Unidirectional Streams 47 | 48 | With unidirectional streams, you can carry data only in one direction: from the initiator of the stream to its peer. 49 | It is possible to get reliability without ordering (so no head-of-line blocking) by opening a new stream for each packet. 50 | 51 | _open unidirectional stream_ 52 | 53 | ```rust 54 | {{#include ../bin/data-transfer.rs:27:32}} 55 | ``` 56 | 57 | _iterating incoming unidirectional stream(s)_ 58 | 59 | ```rust 60 | {{#include ../bin/data-transfer.rs:35:41}} 61 | ``` 62 | 63 | ## Unreliable Messaging 64 | 65 | With unreliable messaging, you can transfer data without reliability. 66 | This could be useful if data arrival isn't essential or when high throughput is important. 67 | 68 | _send datagram_ 69 | 70 | ```rust 71 | {{#include ../bin/data-transfer.rs:44:47}} 72 | ``` 73 | 74 | _iterating datagram stream(s)_ 75 | 76 | ```rust 77 | {{#include ../bin/data-transfer.rs:50:56}} 78 | ``` 79 | 80 | [Endpoint]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html 81 | [Connection]: https://docs.rs/quinn/latest/quinn/struct.Connection.html 82 | [open_bi]: https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_bi 83 | [open_uni]: https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_uni 84 | [send_datagram]: https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.send_datagram 85 | -------------------------------------------------------------------------------- /docs/book/src/quinn/set-up-connection.md: -------------------------------------------------------------------------------- 1 | # Connection Setup 2 | 3 | In the [previous chapter](certificate.md) we looked at how to configure a certificate. 4 | This aspect is omitted in this chapter to prevent duplication. 5 | But **remember** that this is required to get your [Endpoint][Endpoint] up and running. 6 | This chapter explains how to set up a connection and prepare it for data transfer. 7 | 8 | It all starts with the [Endpoint][Endpoint] struct, this is the entry point of the library. 9 | 10 | ## Example 11 | 12 | Let's start by defining some constants. 13 | 14 | ```rust 15 | {{#include ../bin/set-up-connection.rs:35:38}} 16 | ``` 17 | 18 | **Server** 19 | 20 | First, the server endpoint should be bound to a socket. 21 | The [server()][server] method, which can be used for this, returns the `Endpoint` type. 22 | `Endpoint` is used to start outgoing connections and accept incoming connections. 23 | 24 | ```rust 25 | {{#include ../bin/set-up-connection.rs:8:20}} 26 | ``` 27 | 28 | **Client** 29 | 30 | The [client()][client] returns only a `Endpoint` type. 31 | The client needs to connect to the server using the [connect(server_name)][connect] method. 32 | The `SERVER_NAME` argument is the DNS name, matching the certificate configured in the server. 33 | 34 | ```rust 35 | {{#include ../bin/set-up-connection.rs:23:33}} 36 | ``` 37 | 38 |

39 | 40 | [Next up](data-transfer.md), let's have a look at sending data over this connection. 41 | 42 | [Endpoint]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html 43 | [server]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.server 44 | [client]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.client 45 | [connect]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.connect 46 | -------------------------------------------------------------------------------- /docs/quin-logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quinn-rs/quinn/e8dc5a2eda57163bfbaba52ba57bf5b7a0027e22/docs/quin-logo.psd -------------------------------------------------------------------------------- /docs/thumbnail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 48 | -------------------------------------------------------------------------------- /fuzz/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "fuzzing"] 3 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.1.0" 4 | publish = false 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | arbitrary = { version = "1.0.1", features = ["derive"] } 13 | libfuzzer-sys = "0.4.2" 14 | 15 | [dependencies.proto] 16 | features = ["arbitrary"] 17 | path = "../quinn-proto" 18 | package = "quinn-proto" 19 | 20 | [[bin]] 21 | name = "streams" 22 | path = "fuzz_targets/streams.rs" 23 | test = false 24 | doc = false 25 | 26 | [[bin]] 27 | name = "streamid" 28 | path = "fuzz_targets/streamid.rs" 29 | test = false 30 | doc = false 31 | 32 | [[bin]] 33 | name = "packet" 34 | path = "fuzz_targets/packet.rs" 35 | test = false 36 | doc = false 37 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/packet.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | extern crate proto; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | use proto::{ 7 | DEFAULT_SUPPORTED_VERSIONS, FixedLengthConnectionIdParser, 8 | fuzzing::{PacketParams, PartialDecode}, 9 | }; 10 | 11 | fuzz_target!(|data: PacketParams| { 12 | let len = data.buf.len(); 13 | let supported_versions = DEFAULT_SUPPORTED_VERSIONS.to_vec(); 14 | if let Ok(decoded) = PartialDecode::new( 15 | data.buf, 16 | &FixedLengthConnectionIdParser::new(data.local_cid_len), 17 | &supported_versions, 18 | data.grease_quic_bit, 19 | ) { 20 | match decoded.1 { 21 | Some(x) => assert_eq!(len, decoded.0.len() + x.len()), 22 | None => assert_eq!(len, decoded.0.len()), 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/streamid.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use arbitrary::Arbitrary; 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | extern crate proto; 6 | use proto::{Dir, Side, StreamId}; 7 | 8 | #[derive(Arbitrary, Debug)] 9 | struct StreamIdParams { 10 | side: Side, 11 | dir: Dir, 12 | index: u64, 13 | } 14 | 15 | fuzz_target!(|data: StreamIdParams| { 16 | let s = StreamId::new(data.side, data.dir, data.index); 17 | assert_eq!(s.initiator(), data.side); 18 | assert_eq!(s.dir(), data.dir); 19 | }); 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/streams.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use arbitrary::Arbitrary; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | extern crate proto; 7 | use proto::fuzzing::{ConnectionState, ResetStream, Retransmits, StreamsState}; 8 | use proto::{Dir, Side, StreamId, VarInt}; 9 | use proto::{SendStream, Streams}; 10 | 11 | #[derive(Arbitrary, Debug)] 12 | struct StreamParams { 13 | side: Side, 14 | max_remote_uni: u16, 15 | max_remote_bi: u16, 16 | send_window: u16, 17 | receive_window: u16, 18 | stream_receive_window: u16, 19 | dir: Dir, 20 | } 21 | 22 | #[derive(Arbitrary, Debug)] 23 | enum Operation { 24 | Open, 25 | Accept(Dir), 26 | Finish(StreamId), 27 | ReceivedStopSending(StreamId, VarInt), 28 | ReceivedReset(ResetStream), 29 | Reset(StreamId), 30 | } 31 | 32 | fuzz_target!(|input: (StreamParams, Vec)| { 33 | let (params, operations) = input; 34 | let (mut pending, conn_state) = (Retransmits::default(), ConnectionState::Established); 35 | let mut state = StreamsState::new( 36 | params.side, 37 | params.max_remote_uni.into(), 38 | params.max_remote_bi.into(), 39 | params.send_window.into(), 40 | params.receive_window.into(), 41 | params.stream_receive_window.into(), 42 | ); 43 | 44 | for operation in operations { 45 | match operation { 46 | Operation::Open => { 47 | Streams::new(&mut state, &conn_state).open(params.dir); 48 | } 49 | Operation::Accept(dir) => { 50 | Streams::new(&mut state, &conn_state).accept(dir); 51 | } 52 | Operation::Finish(id) => { 53 | let _ = SendStream::new(id, &mut state, &mut pending, &conn_state).finish(); 54 | } 55 | Operation::ReceivedStopSending(sid, err_code) => { 56 | Streams::new(&mut state, &conn_state) 57 | .state() 58 | .received_stop_sending(sid, err_code); 59 | } 60 | Operation::ReceivedReset(rs) => { 61 | let _ = Streams::new(&mut state, &conn_state) 62 | .state() 63 | .received_reset(rs); 64 | } 65 | Operation::Reset(id) => { 66 | let _ = 67 | SendStream::new(id, &mut state, &mut pending, &conn_state).reset(0u32.into()); 68 | } 69 | } 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /perf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "perf" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [features] 9 | # NOTE: Please keep this in sync with the feature list in `.github/workflows/codecov.yml`, see 10 | # comment in that file for more information. 11 | default = ["json-output"] 12 | # Allow for json output from the perf client 13 | json-output = ["serde", "serde_json"] 14 | 15 | [dependencies] 16 | anyhow = { workspace = true } 17 | bytes = { workspace = true } 18 | clap = { workspace = true } 19 | hdrhistogram = { workspace = true } 20 | quinn = { path = "../quinn" } 21 | quinn-proto = { path = "../quinn-proto" } 22 | rcgen = { workspace = true } 23 | rustls = { workspace = true } 24 | rustls-pemfile = { workspace = true } 25 | serde = { workspace = true, optional = true } 26 | serde_json = { workspace = true, optional = true } 27 | socket2 = { workspace = true } 28 | tokio = { workspace = true, features = ["rt", "macros", "signal", "net"] } 29 | tracing = { workspace = true } 30 | tracing-subscriber = { workspace = true } 31 | -------------------------------------------------------------------------------- /perf/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::{Context, Result}; 4 | use quinn::udp::UdpSocketState; 5 | use rustls::crypto::ring::cipher_suite; 6 | use socket2::{Domain, Protocol, Socket, Type}; 7 | use tracing::warn; 8 | 9 | #[cfg_attr(not(feature = "json-output"), allow(dead_code))] 10 | pub mod stats; 11 | 12 | pub mod noprotection; 13 | 14 | pub fn bind_socket( 15 | addr: SocketAddr, 16 | send_buffer_size: usize, 17 | recv_buffer_size: usize, 18 | ) -> Result { 19 | let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP)) 20 | .context("create socket")?; 21 | 22 | if addr.is_ipv6() { 23 | socket.set_only_v6(false).context("set_only_v6")?; 24 | } 25 | 26 | socket 27 | .bind(&socket2::SockAddr::from(addr)) 28 | .context("binding endpoint")?; 29 | 30 | let socket_state = UdpSocketState::new((&socket).into())?; 31 | socket_state 32 | .set_send_buffer_size((&socket).into(), send_buffer_size) 33 | .context("send buffer size")?; 34 | socket_state 35 | .set_recv_buffer_size((&socket).into(), recv_buffer_size) 36 | .context("recv buffer size")?; 37 | 38 | let buf_size = socket_state 39 | .send_buffer_size((&socket).into()) 40 | .context("send buffer size")?; 41 | if buf_size < send_buffer_size { 42 | warn!( 43 | "Unable to set desired send buffer size. Desired: {}, Actual: {}", 44 | send_buffer_size, buf_size 45 | ); 46 | } 47 | 48 | let buf_size = socket_state 49 | .recv_buffer_size((&socket).into()) 50 | .context("recv buffer size")?; 51 | if buf_size < recv_buffer_size { 52 | warn!( 53 | "Unable to set desired recv buffer size. Desired: {}, Actual: {}", 54 | recv_buffer_size, buf_size 55 | ); 56 | } 57 | 58 | Ok(socket.into()) 59 | } 60 | 61 | pub static PERF_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[ 62 | cipher_suite::TLS13_AES_128_GCM_SHA256, 63 | cipher_suite::TLS13_AES_256_GCM_SHA384, 64 | cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, 65 | ]; 66 | -------------------------------------------------------------------------------- /perf/src/noprotection.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::BytesMut; 4 | 5 | use quinn_proto::{ 6 | ConnectionId, Side, TransportError, 7 | crypto::{ 8 | self, CryptoError, 9 | rustls::{QuicClientConfig, QuicServerConfig}, 10 | }, 11 | transport_parameters, 12 | }; 13 | 14 | /// A rustls TLS session which does not perform packet encryption/decryption (for debugging purpose) 15 | struct NoProtectionSession { 16 | inner: Box, 17 | } 18 | 19 | impl NoProtectionSession { 20 | fn new(tls: Box) -> Self { 21 | Self { inner: tls } 22 | } 23 | 24 | /// Wraps the provided keys in `NoProtectionPacketKey` to disable packet encryption / decryption 25 | fn wrap_packet_keys( 26 | keys: crypto::KeyPair>, 27 | ) -> crypto::KeyPair> { 28 | crypto::KeyPair { 29 | local: Box::new(NoProtectionPacketKey::new(keys.local)), 30 | remote: Box::new(NoProtectionPacketKey::new(keys.remote)), 31 | } 32 | } 33 | } 34 | 35 | struct NoProtectionPacketKey { 36 | inner: Box, 37 | } 38 | 39 | impl NoProtectionPacketKey { 40 | fn new(key: Box) -> Self { 41 | Self { inner: key } 42 | } 43 | } 44 | 45 | pub struct NoProtectionClientConfig { 46 | inner: Arc, 47 | } 48 | 49 | impl NoProtectionClientConfig { 50 | pub fn new(config: Arc) -> Self { 51 | Self { inner: config } 52 | } 53 | } 54 | 55 | pub struct NoProtectionServerConfig { 56 | inner: Arc, 57 | } 58 | 59 | impl NoProtectionServerConfig { 60 | pub fn new(config: Arc) -> Self { 61 | Self { inner: config } 62 | } 63 | } 64 | 65 | // forward all calls to inner except those related to packet encryption/decryption 66 | impl crypto::Session for NoProtectionSession { 67 | fn initial_keys(&self, dst_cid: &ConnectionId, side: Side) -> crypto::Keys { 68 | self.inner.initial_keys(dst_cid, side) 69 | } 70 | 71 | fn handshake_data(&self) -> Option> { 72 | self.inner.handshake_data() 73 | } 74 | 75 | fn peer_identity(&self) -> Option> { 76 | self.inner.peer_identity() 77 | } 78 | 79 | fn early_crypto(&self) -> Option<(Box, Box)> { 80 | let (hkey, pkey) = self.inner.early_crypto()?; 81 | 82 | // use wrapper type to disable packet encryption/decryption 83 | Some((hkey, Box::new(NoProtectionPacketKey::new(pkey)))) 84 | } 85 | 86 | fn early_data_accepted(&self) -> Option { 87 | self.inner.early_data_accepted() 88 | } 89 | 90 | fn is_handshaking(&self) -> bool { 91 | self.inner.is_handshaking() 92 | } 93 | 94 | fn read_handshake(&mut self, buf: &[u8]) -> Result { 95 | self.inner.read_handshake(buf) 96 | } 97 | 98 | fn transport_parameters( 99 | &self, 100 | ) -> Result, TransportError> { 101 | self.inner.transport_parameters() 102 | } 103 | 104 | fn write_handshake(&mut self, buf: &mut Vec) -> Option { 105 | let keys = self.inner.write_handshake(buf)?; 106 | 107 | Some(crypto::Keys { 108 | header: keys.header, 109 | packet: Self::wrap_packet_keys(keys.packet), 110 | }) 111 | } 112 | 113 | fn next_1rtt_keys(&mut self) -> Option>> { 114 | let keys = self.inner.next_1rtt_keys()?; 115 | Some(Self::wrap_packet_keys(keys)) 116 | } 117 | 118 | fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool { 119 | self.inner.is_valid_retry(orig_dst_cid, header, payload) 120 | } 121 | 122 | fn export_keying_material( 123 | &self, 124 | output: &mut [u8], 125 | label: &[u8], 126 | context: &[u8], 127 | ) -> Result<(), crypto::ExportKeyingMaterialError> { 128 | self.inner.export_keying_material(output, label, context) 129 | } 130 | } 131 | 132 | impl crypto::ClientConfig for NoProtectionClientConfig { 133 | fn start_session( 134 | self: std::sync::Arc, 135 | version: u32, 136 | server_name: &str, 137 | params: &transport_parameters::TransportParameters, 138 | ) -> Result, quinn::ConnectError> { 139 | let tls = self 140 | .inner 141 | .clone() 142 | .start_session(version, server_name, params)?; 143 | 144 | Ok(Box::new(NoProtectionSession::new(tls))) 145 | } 146 | } 147 | 148 | impl crypto::ServerConfig for NoProtectionServerConfig { 149 | fn initial_keys( 150 | &self, 151 | version: u32, 152 | dst_cid: &ConnectionId, 153 | ) -> Result { 154 | self.inner.initial_keys(version, dst_cid) 155 | } 156 | 157 | fn retry_tag(&self, version: u32, orig_dst_cid: &ConnectionId, packet: &[u8]) -> [u8; 16] { 158 | self.inner.retry_tag(version, orig_dst_cid, packet) 159 | } 160 | 161 | fn start_session( 162 | self: Arc, 163 | version: u32, 164 | params: &transport_parameters::TransportParameters, 165 | ) -> Box { 166 | let tls = self.inner.clone().start_session(version, params); 167 | 168 | Box::new(NoProtectionSession::new(tls)) 169 | } 170 | } 171 | 172 | // forward all calls to inner except those related to packet encryption/decryption 173 | impl crypto::PacketKey for NoProtectionPacketKey { 174 | fn encrypt(&self, _packet: u64, buf: &mut [u8], header_len: usize) { 175 | let (_header, payload_tag) = buf.split_at_mut(header_len); 176 | let (_payload, tag_storage) = 177 | payload_tag.split_at_mut(payload_tag.len() - self.inner.tag_len()); 178 | // packet = identity(packet) 179 | tag_storage.fill(42); 180 | } 181 | 182 | fn decrypt( 183 | &self, 184 | _packet: u64, 185 | _header: &[u8], 186 | payload: &mut BytesMut, 187 | ) -> Result<(), CryptoError> { 188 | let plain_len = payload.len() - self.inner.tag_len(); 189 | payload.truncate(plain_len); 190 | Ok(()) 191 | } 192 | 193 | fn tag_len(&self) -> usize { 194 | self.inner.tag_len() 195 | } 196 | 197 | fn confidentiality_limit(&self) -> u64 { 198 | self.inner.confidentiality_limit() 199 | } 200 | 201 | fn integrity_limit(&self) -> u64 { 202 | self.inner.integrity_limit() 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /quinn-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quinn-proto" 3 | version = "0.11.12" 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "State machine for the QUIC transport protocol" 9 | keywords.workspace = true 10 | categories.workspace = true 11 | workspace = ".." 12 | 13 | [features] 14 | # NOTE: Please keep this in sync with the feature list in `.github/workflows/codecov.yml`, see 15 | # comment in that file for more information. 16 | default = ["rustls-ring", "log", "bloom"] 17 | aws-lc-rs = ["dep:aws-lc-rs", "aws-lc-rs?/aws-lc-sys", "aws-lc-rs?/prebuilt-nasm"] 18 | aws-lc-rs-fips = ["aws-lc-rs", "aws-lc-rs?/fips"] 19 | # Enables BloomTokenLog, and uses it by default 20 | bloom = ["dep:fastbloom"] 21 | # For backwards compatibility, `rustls` forwards to `rustls-ring` 22 | rustls = ["rustls-ring"] 23 | # Enable rustls with the `aws-lc-rs` crypto provider 24 | rustls-aws-lc-rs = ["dep:rustls", "rustls?/aws-lc-rs", "aws-lc-rs"] 25 | rustls-aws-lc-rs-fips = ["rustls-aws-lc-rs", "aws-lc-rs-fips"] 26 | # Enable rustls with the `ring` crypto provider 27 | rustls-ring = ["dep:rustls", "rustls?/ring", "ring"] 28 | ring = ["dep:ring"] 29 | # Enable rustls ring provider and direct ring usage 30 | # Provides `ClientConfig::with_platform_verifier()` convenience method 31 | platform-verifier = ["dep:rustls-platform-verifier"] 32 | # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. 33 | log = ["tracing/log"] 34 | # Enable rustls logging 35 | rustls-log = ["rustls?/logging"] 36 | 37 | [dependencies] 38 | arbitrary = { workspace = true, optional = true } 39 | aws-lc-rs = { workspace = true, optional = true } 40 | bytes = { workspace = true } 41 | fastbloom = { workspace = true, optional = true } 42 | lru-slab = { workspace = true } 43 | rustc-hash = { workspace = true } 44 | rand = { workspace = true } 45 | ring = { workspace = true, optional = true } 46 | rustls = { workspace = true, optional = true } 47 | rustls-platform-verifier = { workspace = true, optional = true } 48 | slab = { workspace = true } 49 | thiserror = { workspace = true } 50 | tinyvec = { workspace = true, features = ["alloc"] } 51 | tracing = { workspace = true } 52 | 53 | # Feature flags & dependencies for wasm 54 | # wasm-bindgen is assumed for a wasm*-*-unknown target 55 | [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 56 | ring = { workspace = true, features = ["wasm32_unknown_unknown_js"] } 57 | getrandom = { workspace = true, features = ["wasm_js"] } 58 | rustls-pki-types = { workspace = true, features = ["web"] } # only added as dependency to enforce the `web` feature for this target 59 | web-time = { workspace = true } 60 | 61 | [dev-dependencies] 62 | assert_matches = { workspace = true } 63 | hex-literal = { workspace = true } 64 | rand_pcg = "0.9" 65 | rcgen = { workspace = true } 66 | tracing-subscriber = { workspace = true } 67 | lazy_static = "1" 68 | wasm-bindgen-test = { workspace = true } 69 | 70 | [lints.rust] 71 | # https://rust-fuzz.github.io/book/cargo-fuzz/guide.html#cfgfuzzing 72 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 73 | 74 | [package.metadata.docs.rs] 75 | # all non-default features except fips (cannot build on docs.rs environment) 76 | features = ["rustls-aws-lc-rs", "rustls-ring", "platform-verifier", "log", "rustls-log"] 77 | -------------------------------------------------------------------------------- /quinn-proto/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /quinn-proto/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /quinn-proto/src/cid_generator.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hasher; 2 | 3 | use rand::{Rng, RngCore}; 4 | 5 | use crate::Duration; 6 | use crate::MAX_CID_SIZE; 7 | use crate::shared::ConnectionId; 8 | 9 | /// Generates connection IDs for incoming connections 10 | pub trait ConnectionIdGenerator: Send + Sync { 11 | /// Generates a new CID 12 | /// 13 | /// Connection IDs MUST NOT contain any information that can be used by 14 | /// an external observer (that is, one that does not cooperate with the 15 | /// issuer) to correlate them with other connection IDs for the same 16 | /// connection. They MUST have high entropy, e.g. due to encrypted data 17 | /// or cryptographic-grade random data. 18 | fn generate_cid(&mut self) -> ConnectionId; 19 | 20 | /// Quickly determine whether `cid` could have been generated by this generator 21 | /// 22 | /// False positives are permitted, but increase the cost of handling invalid packets. 23 | fn validate(&self, _cid: &ConnectionId) -> Result<(), InvalidCid> { 24 | Ok(()) 25 | } 26 | 27 | /// Returns the length of a CID for connections created by this generator 28 | fn cid_len(&self) -> usize; 29 | /// Returns the lifetime of generated Connection IDs 30 | /// 31 | /// Connection IDs will be retired after the returned `Duration`, if any. Assumed to be constant. 32 | fn cid_lifetime(&self) -> Option; 33 | } 34 | 35 | /// The connection ID was not recognized by the [`ConnectionIdGenerator`] 36 | #[derive(Debug, Copy, Clone)] 37 | pub struct InvalidCid; 38 | 39 | /// Generates purely random connection IDs of a specified length 40 | /// 41 | /// Random CIDs can be smaller than those produced by [`HashedConnectionIdGenerator`], but cannot be 42 | /// usefully [`validate`](ConnectionIdGenerator::validate)d. 43 | #[derive(Debug, Clone, Copy)] 44 | pub struct RandomConnectionIdGenerator { 45 | cid_len: usize, 46 | lifetime: Option, 47 | } 48 | 49 | impl Default for RandomConnectionIdGenerator { 50 | fn default() -> Self { 51 | Self { 52 | cid_len: 8, 53 | lifetime: None, 54 | } 55 | } 56 | } 57 | 58 | impl RandomConnectionIdGenerator { 59 | /// Initialize Random CID generator with a fixed CID length 60 | /// 61 | /// The given length must be less than or equal to MAX_CID_SIZE. 62 | pub fn new(cid_len: usize) -> Self { 63 | debug_assert!(cid_len <= MAX_CID_SIZE); 64 | Self { 65 | cid_len, 66 | ..Self::default() 67 | } 68 | } 69 | 70 | /// Set the lifetime of CIDs created by this generator 71 | pub fn set_lifetime(&mut self, d: Duration) -> &mut Self { 72 | self.lifetime = Some(d); 73 | self 74 | } 75 | } 76 | 77 | impl ConnectionIdGenerator for RandomConnectionIdGenerator { 78 | fn generate_cid(&mut self) -> ConnectionId { 79 | let mut bytes_arr = [0; MAX_CID_SIZE]; 80 | rand::rng().fill_bytes(&mut bytes_arr[..self.cid_len]); 81 | 82 | ConnectionId::new(&bytes_arr[..self.cid_len]) 83 | } 84 | 85 | /// Provide the length of dst_cid in short header packet 86 | fn cid_len(&self) -> usize { 87 | self.cid_len 88 | } 89 | 90 | fn cid_lifetime(&self) -> Option { 91 | self.lifetime 92 | } 93 | } 94 | 95 | /// Generates 8-byte connection IDs that can be efficiently 96 | /// [`validate`](ConnectionIdGenerator::validate)d 97 | /// 98 | /// This generator uses a non-cryptographic hash and can therefore still be spoofed, but nonetheless 99 | /// helps prevents Quinn from responding to non-QUIC packets at very low cost. 100 | pub struct HashedConnectionIdGenerator { 101 | key: u64, 102 | lifetime: Option, 103 | } 104 | 105 | impl HashedConnectionIdGenerator { 106 | /// Create a generator with a random key 107 | pub fn new() -> Self { 108 | Self::from_key(rand::rng().random()) 109 | } 110 | 111 | /// Create a generator with a specific key 112 | /// 113 | /// Allows [`validate`](ConnectionIdGenerator::validate) to recognize a consistent set of 114 | /// connection IDs across restarts 115 | pub fn from_key(key: u64) -> Self { 116 | Self { 117 | key, 118 | lifetime: None, 119 | } 120 | } 121 | 122 | /// Set the lifetime of CIDs created by this generator 123 | pub fn set_lifetime(&mut self, d: Duration) -> &mut Self { 124 | self.lifetime = Some(d); 125 | self 126 | } 127 | } 128 | 129 | impl Default for HashedConnectionIdGenerator { 130 | fn default() -> Self { 131 | Self::new() 132 | } 133 | } 134 | 135 | impl ConnectionIdGenerator for HashedConnectionIdGenerator { 136 | fn generate_cid(&mut self) -> ConnectionId { 137 | let mut bytes_arr = [0; NONCE_LEN + SIGNATURE_LEN]; 138 | rand::rng().fill_bytes(&mut bytes_arr[..NONCE_LEN]); 139 | let mut hasher = rustc_hash::FxHasher::default(); 140 | hasher.write_u64(self.key); 141 | hasher.write(&bytes_arr[..NONCE_LEN]); 142 | bytes_arr[NONCE_LEN..].copy_from_slice(&hasher.finish().to_le_bytes()[..SIGNATURE_LEN]); 143 | ConnectionId::new(&bytes_arr) 144 | } 145 | 146 | fn validate(&self, cid: &ConnectionId) -> Result<(), InvalidCid> { 147 | let (nonce, signature) = cid.split_at(NONCE_LEN); 148 | let mut hasher = rustc_hash::FxHasher::default(); 149 | hasher.write_u64(self.key); 150 | hasher.write(nonce); 151 | let expected = hasher.finish().to_le_bytes(); 152 | match expected[..SIGNATURE_LEN] == signature[..] { 153 | true => Ok(()), 154 | false => Err(InvalidCid), 155 | } 156 | } 157 | 158 | fn cid_len(&self) -> usize { 159 | NONCE_LEN + SIGNATURE_LEN 160 | } 161 | 162 | fn cid_lifetime(&self) -> Option { 163 | self.lifetime 164 | } 165 | } 166 | 167 | const NONCE_LEN: usize = 3; // Good for more than 16 million connections 168 | const SIGNATURE_LEN: usize = 8 - NONCE_LEN; // 8-byte total CID length 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use super::*; 173 | 174 | #[test] 175 | fn validate_keyed_cid() { 176 | let mut generator = HashedConnectionIdGenerator::new(); 177 | let cid = generator.generate_cid(); 178 | generator.validate(&cid).unwrap(); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /quinn-proto/src/coding.rs: -------------------------------------------------------------------------------- 1 | //! Coding related traits. 2 | 3 | use std::net::{Ipv4Addr, Ipv6Addr}; 4 | 5 | use bytes::{Buf, BufMut}; 6 | use thiserror::Error; 7 | 8 | use crate::VarInt; 9 | 10 | /// Error indicating that the provided buffer was too small 11 | #[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] 12 | #[error("unexpected end of buffer")] 13 | pub struct UnexpectedEnd; 14 | 15 | /// Coding result type 16 | pub type Result = ::std::result::Result; 17 | 18 | /// Infallible encoding and decoding of QUIC primitives 19 | pub trait Codec: Sized { 20 | /// Decode a `Self` from the provided buffer, if the buffer is large enough 21 | fn decode(buf: &mut B) -> Result; 22 | /// Append the encoding of `self` to the provided buffer 23 | fn encode(&self, buf: &mut B); 24 | } 25 | 26 | impl Codec for u8 { 27 | fn decode(buf: &mut B) -> Result { 28 | if buf.remaining() < 1 { 29 | return Err(UnexpectedEnd); 30 | } 31 | Ok(buf.get_u8()) 32 | } 33 | fn encode(&self, buf: &mut B) { 34 | buf.put_u8(*self); 35 | } 36 | } 37 | 38 | impl Codec for u16 { 39 | fn decode(buf: &mut B) -> Result { 40 | if buf.remaining() < 2 { 41 | return Err(UnexpectedEnd); 42 | } 43 | Ok(buf.get_u16()) 44 | } 45 | fn encode(&self, buf: &mut B) { 46 | buf.put_u16(*self); 47 | } 48 | } 49 | 50 | impl Codec for u32 { 51 | fn decode(buf: &mut B) -> Result { 52 | if buf.remaining() < 4 { 53 | return Err(UnexpectedEnd); 54 | } 55 | Ok(buf.get_u32()) 56 | } 57 | fn encode(&self, buf: &mut B) { 58 | buf.put_u32(*self); 59 | } 60 | } 61 | 62 | impl Codec for u64 { 63 | fn decode(buf: &mut B) -> Result { 64 | if buf.remaining() < 8 { 65 | return Err(UnexpectedEnd); 66 | } 67 | Ok(buf.get_u64()) 68 | } 69 | fn encode(&self, buf: &mut B) { 70 | buf.put_u64(*self); 71 | } 72 | } 73 | 74 | impl Codec for Ipv4Addr { 75 | fn decode(buf: &mut B) -> Result { 76 | if buf.remaining() < 4 { 77 | return Err(UnexpectedEnd); 78 | } 79 | let mut octets = [0; 4]; 80 | buf.copy_to_slice(&mut octets); 81 | Ok(octets.into()) 82 | } 83 | fn encode(&self, buf: &mut B) { 84 | buf.put_slice(&self.octets()); 85 | } 86 | } 87 | 88 | impl Codec for Ipv6Addr { 89 | fn decode(buf: &mut B) -> Result { 90 | if buf.remaining() < 16 { 91 | return Err(UnexpectedEnd); 92 | } 93 | let mut octets = [0; 16]; 94 | buf.copy_to_slice(&mut octets); 95 | Ok(octets.into()) 96 | } 97 | fn encode(&self, buf: &mut B) { 98 | buf.put_slice(&self.octets()); 99 | } 100 | } 101 | 102 | pub(crate) trait BufExt { 103 | fn get(&mut self) -> Result; 104 | fn get_var(&mut self) -> Result; 105 | } 106 | 107 | impl BufExt for T { 108 | fn get(&mut self) -> Result { 109 | U::decode(self) 110 | } 111 | 112 | fn get_var(&mut self) -> Result { 113 | Ok(VarInt::decode(self)?.into_inner()) 114 | } 115 | } 116 | 117 | pub(crate) trait BufMutExt { 118 | fn write(&mut self, x: T); 119 | fn write_var(&mut self, x: u64); 120 | } 121 | 122 | impl BufMutExt for T { 123 | fn write(&mut self, x: U) { 124 | x.encode(self); 125 | } 126 | 127 | fn write_var(&mut self, x: u64) { 128 | VarInt::from_u64(x).unwrap().encode(self); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /quinn-proto/src/congestion.rs: -------------------------------------------------------------------------------- 1 | //! Logic for controlling the rate at which data is sent 2 | 3 | use crate::Instant; 4 | use crate::connection::RttEstimator; 5 | use std::any::Any; 6 | use std::sync::Arc; 7 | 8 | mod bbr; 9 | mod cubic; 10 | mod new_reno; 11 | 12 | pub use bbr::{Bbr, BbrConfig}; 13 | pub use cubic::{Cubic, CubicConfig}; 14 | pub use new_reno::{NewReno, NewRenoConfig}; 15 | 16 | /// Common interface for different congestion controllers 17 | pub trait Controller: Send + Sync { 18 | /// One or more packets were just sent 19 | #[allow(unused_variables)] 20 | fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) {} 21 | 22 | /// Packet deliveries were confirmed 23 | /// 24 | /// `app_limited` indicates whether the connection was blocked on outgoing 25 | /// application data prior to receiving these acknowledgements. 26 | #[allow(unused_variables)] 27 | fn on_ack( 28 | &mut self, 29 | now: Instant, 30 | sent: Instant, 31 | bytes: u64, 32 | app_limited: bool, 33 | rtt: &RttEstimator, 34 | ) { 35 | } 36 | 37 | /// Packets are acked in batches, all with the same `now` argument. This indicates one of those batches has completed. 38 | #[allow(unused_variables)] 39 | fn on_end_acks( 40 | &mut self, 41 | now: Instant, 42 | in_flight: u64, 43 | app_limited: bool, 44 | largest_packet_num_acked: Option, 45 | ) { 46 | } 47 | 48 | /// Packets were deemed lost or marked congested 49 | /// 50 | /// `in_persistent_congestion` indicates whether all packets sent within the persistent 51 | /// congestion threshold period ending when the most recent packet in this batch was sent were 52 | /// lost. 53 | /// `lost_bytes` indicates how many bytes were lost. This value will be 0 for ECN triggers. 54 | fn on_congestion_event( 55 | &mut self, 56 | now: Instant, 57 | sent: Instant, 58 | is_persistent_congestion: bool, 59 | lost_bytes: u64, 60 | ); 61 | 62 | /// The known MTU for the current network path has been updated 63 | fn on_mtu_update(&mut self, new_mtu: u16); 64 | 65 | /// Number of ack-eliciting bytes that may be in flight 66 | fn window(&self) -> u64; 67 | 68 | /// Duplicate the controller's state 69 | fn clone_box(&self) -> Box; 70 | 71 | /// Initial congestion window 72 | fn initial_window(&self) -> u64; 73 | 74 | /// Returns Self for use in down-casting to extract implementation details 75 | fn into_any(self: Box) -> Box; 76 | } 77 | 78 | /// Constructs controllers on demand 79 | pub trait ControllerFactory { 80 | /// Construct a fresh `Controller` 81 | fn build(self: Arc, now: Instant, current_mtu: u16) -> Box; 82 | } 83 | 84 | const BASE_DATAGRAM_SIZE: u64 = 1200; 85 | -------------------------------------------------------------------------------- /quinn-proto/src/congestion/bbr/bw_estimation.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display, Formatter}; 2 | 3 | use super::min_max::MinMax; 4 | use crate::{Duration, Instant}; 5 | 6 | #[derive(Clone, Debug, Default)] 7 | pub(crate) struct BandwidthEstimation { 8 | total_acked: u64, 9 | prev_total_acked: u64, 10 | acked_time: Option, 11 | prev_acked_time: Option, 12 | total_sent: u64, 13 | prev_total_sent: u64, 14 | sent_time: Option, 15 | prev_sent_time: Option, 16 | max_filter: MinMax, 17 | acked_at_last_window: u64, 18 | } 19 | 20 | impl BandwidthEstimation { 21 | pub(crate) fn on_sent(&mut self, now: Instant, bytes: u64) { 22 | self.prev_total_sent = self.total_sent; 23 | self.total_sent += bytes; 24 | self.prev_sent_time = self.sent_time; 25 | self.sent_time = Some(now); 26 | } 27 | 28 | pub(crate) fn on_ack( 29 | &mut self, 30 | now: Instant, 31 | _sent: Instant, 32 | bytes: u64, 33 | round: u64, 34 | app_limited: bool, 35 | ) { 36 | self.prev_total_acked = self.total_acked; 37 | self.total_acked += bytes; 38 | self.prev_acked_time = self.acked_time; 39 | self.acked_time = Some(now); 40 | 41 | let prev_sent_time = match self.prev_sent_time { 42 | Some(prev_sent_time) => prev_sent_time, 43 | None => return, 44 | }; 45 | 46 | let send_rate = match self.sent_time { 47 | Some(sent_time) if sent_time > prev_sent_time => Self::bw_from_delta( 48 | self.total_sent - self.prev_total_sent, 49 | sent_time - prev_sent_time, 50 | ) 51 | .unwrap_or(0), 52 | _ => u64::MAX, // will take the min of send and ack, so this is just a skip 53 | }; 54 | 55 | let ack_rate = match self.prev_acked_time { 56 | Some(prev_acked_time) => Self::bw_from_delta( 57 | self.total_acked - self.prev_total_acked, 58 | now - prev_acked_time, 59 | ) 60 | .unwrap_or(0), 61 | None => 0, 62 | }; 63 | 64 | let bandwidth = send_rate.min(ack_rate); 65 | if !app_limited && self.max_filter.get() < bandwidth { 66 | self.max_filter.update_max(round, bandwidth); 67 | } 68 | } 69 | 70 | pub(crate) fn bytes_acked_this_window(&self) -> u64 { 71 | self.total_acked - self.acked_at_last_window 72 | } 73 | 74 | pub(crate) fn end_acks(&mut self, _current_round: u64, _app_limited: bool) { 75 | self.acked_at_last_window = self.total_acked; 76 | } 77 | 78 | pub(crate) fn get_estimate(&self) -> u64 { 79 | self.max_filter.get() 80 | } 81 | 82 | pub(crate) const fn bw_from_delta(bytes: u64, delta: Duration) -> Option { 83 | let window_duration_ns = delta.as_nanos(); 84 | if window_duration_ns == 0 { 85 | return None; 86 | } 87 | let b_ns = bytes * 1_000_000_000; 88 | let bytes_per_second = b_ns / (window_duration_ns as u64); 89 | Some(bytes_per_second) 90 | } 91 | } 92 | 93 | impl Display for BandwidthEstimation { 94 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 95 | write!( 96 | f, 97 | "{:.3} MB/s", 98 | self.get_estimate() as f32 / (1024 * 1024) as f32 99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /quinn-proto/src/congestion/bbr/min_max.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on Google code released under BSD license here: 3 | * https://groups.google.com/forum/#!topic/bbr-dev/3RTgkzi5ZD8 4 | */ 5 | 6 | /* 7 | * Kathleen Nichols' algorithm for tracking the minimum (or maximum) 8 | * value of a data stream over some fixed time interval. (E.g., 9 | * the minimum RTT over the past five minutes.) It uses constant 10 | * space and constant time per update yet almost always delivers 11 | * the same minimum as an implementation that has to keep all the 12 | * data in the window. 13 | * 14 | * The algorithm keeps track of the best, 2nd best & 3rd best min 15 | * values, maintaining an invariant that the measurement time of 16 | * the n'th best >= n-1'th best. It also makes sure that the three 17 | * values are widely separated in the time window since that bounds 18 | * the worse case error when that data is monotonically increasing 19 | * over the window. 20 | * 21 | * Upon getting a new min, we can forget everything earlier because 22 | * it has no value - the new min is <= everything else in the window 23 | * by definition and it samples the most recent. So we restart fresh on 24 | * every new min and overwrites 2nd & 3rd choices. The same property 25 | * holds for 2nd & 3rd best. 26 | */ 27 | 28 | use std::fmt::Debug; 29 | 30 | #[derive(Copy, Clone, Debug)] 31 | pub(super) struct MinMax { 32 | /// round count, not a timestamp 33 | window: u64, 34 | samples: [MinMaxSample; 3], 35 | } 36 | 37 | impl MinMax { 38 | pub(super) fn get(&self) -> u64 { 39 | self.samples[0].value 40 | } 41 | 42 | fn fill(&mut self, sample: MinMaxSample) { 43 | self.samples.fill(sample); 44 | } 45 | 46 | pub(super) fn reset(&mut self) { 47 | self.fill(Default::default()) 48 | } 49 | 50 | /// update_min is also defined in the original source, but removed here since it is not used. 51 | pub(super) fn update_max(&mut self, current_round: u64, measurement: u64) { 52 | let sample = MinMaxSample { 53 | time: current_round, 54 | value: measurement, 55 | }; 56 | 57 | if self.samples[0].value == 0 /* uninitialised */ 58 | || /* found new max? */ sample.value >= self.samples[0].value 59 | || /* nothing left in window? */ sample.time - self.samples[2].time > self.window 60 | { 61 | self.fill(sample); /* forget earlier samples */ 62 | return; 63 | } 64 | 65 | if sample.value >= self.samples[1].value { 66 | self.samples[2] = sample; 67 | self.samples[1] = sample; 68 | } else if sample.value >= self.samples[2].value { 69 | self.samples[2] = sample; 70 | } 71 | 72 | self.subwin_update(sample); 73 | } 74 | 75 | /* As time advances, update the 1st, 2nd, and 3rd choices. */ 76 | fn subwin_update(&mut self, sample: MinMaxSample) { 77 | let dt = sample.time - self.samples[0].time; 78 | if dt > self.window { 79 | /* 80 | * Passed entire window without a new sample so make 2nd 81 | * choice the new sample & 3rd choice the new 2nd choice. 82 | * we may have to iterate this since our 2nd choice 83 | * may also be outside the window (we checked on entry 84 | * that the third choice was in the window). 85 | */ 86 | self.samples[0] = self.samples[1]; 87 | self.samples[1] = self.samples[2]; 88 | self.samples[2] = sample; 89 | if sample.time - self.samples[0].time > self.window { 90 | self.samples[0] = self.samples[1]; 91 | self.samples[1] = self.samples[2]; 92 | self.samples[2] = sample; 93 | } 94 | } else if self.samples[1].time == self.samples[0].time && dt > self.window / 4 { 95 | /* 96 | * We've passed a quarter of the window without a new sample 97 | * so take a 2nd choice from the 2nd quarter of the window. 98 | */ 99 | self.samples[2] = sample; 100 | self.samples[1] = sample; 101 | } else if self.samples[2].time == self.samples[1].time && dt > self.window / 2 { 102 | /* 103 | * We've passed half the window without finding a new sample 104 | * so take a 3rd choice from the last half of the window 105 | */ 106 | self.samples[2] = sample; 107 | } 108 | } 109 | } 110 | 111 | impl Default for MinMax { 112 | fn default() -> Self { 113 | Self { 114 | window: 10, 115 | samples: [Default::default(); 3], 116 | } 117 | } 118 | } 119 | 120 | #[derive(Debug, Copy, Clone, Default)] 121 | struct MinMaxSample { 122 | /// round number, not a timestamp 123 | time: u64, 124 | value: u64, 125 | } 126 | 127 | #[cfg(test)] 128 | mod test { 129 | use super::*; 130 | 131 | #[test] 132 | fn test() { 133 | let round = 25; 134 | let mut min_max = MinMax::default(); 135 | min_max.update_max(round + 1, 100); 136 | assert_eq!(100, min_max.get()); 137 | min_max.update_max(round + 3, 120); 138 | assert_eq!(120, min_max.get()); 139 | min_max.update_max(round + 5, 160); 140 | assert_eq!(160, min_max.get()); 141 | min_max.update_max(round + 7, 100); 142 | assert_eq!(160, min_max.get()); 143 | min_max.update_max(round + 10, 100); 144 | assert_eq!(160, min_max.get()); 145 | min_max.update_max(round + 14, 100); 146 | assert_eq!(160, min_max.get()); 147 | min_max.update_max(round + 16, 100); 148 | assert_eq!(100, min_max.get()); 149 | min_max.update_max(round + 18, 130); 150 | assert_eq!(130, min_max.get()); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /quinn-proto/src/congestion/new_reno.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::sync::Arc; 3 | 4 | use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory}; 5 | use crate::Instant; 6 | use crate::connection::RttEstimator; 7 | 8 | /// A simple, standard congestion controller 9 | #[derive(Debug, Clone)] 10 | pub struct NewReno { 11 | config: Arc, 12 | current_mtu: u64, 13 | /// Maximum number of bytes in flight that may be sent. 14 | window: u64, 15 | /// Slow start threshold in bytes. When the congestion window is below ssthresh, the mode is 16 | /// slow start and the window grows by the number of bytes acknowledged. 17 | ssthresh: u64, 18 | /// The time when QUIC first detects a loss, causing it to enter recovery. When a packet sent 19 | /// after this time is acknowledged, QUIC exits recovery. 20 | recovery_start_time: Instant, 21 | /// Bytes which had been acked by the peer since leaving slow start 22 | bytes_acked: u64, 23 | } 24 | 25 | impl NewReno { 26 | /// Construct a state using the given `config` and current time `now` 27 | pub fn new(config: Arc, now: Instant, current_mtu: u16) -> Self { 28 | Self { 29 | window: config.initial_window, 30 | ssthresh: u64::MAX, 31 | recovery_start_time: now, 32 | current_mtu: current_mtu as u64, 33 | config, 34 | bytes_acked: 0, 35 | } 36 | } 37 | 38 | fn minimum_window(&self) -> u64 { 39 | 2 * self.current_mtu 40 | } 41 | } 42 | 43 | impl Controller for NewReno { 44 | fn on_ack( 45 | &mut self, 46 | _now: Instant, 47 | sent: Instant, 48 | bytes: u64, 49 | app_limited: bool, 50 | _rtt: &RttEstimator, 51 | ) { 52 | if app_limited || sent <= self.recovery_start_time { 53 | return; 54 | } 55 | 56 | if self.window < self.ssthresh { 57 | // Slow start 58 | self.window += bytes; 59 | 60 | if self.window >= self.ssthresh { 61 | // Exiting slow start 62 | // Initialize `bytes_acked` for congestion avoidance. The idea 63 | // here is that any bytes over `sshthresh` will already be counted 64 | // towards the congestion avoidance phase - independent of when 65 | // how close to `sshthresh` the `window` was when switching states, 66 | // and independent of datagram sizes. 67 | self.bytes_acked = self.window - self.ssthresh; 68 | } 69 | } else { 70 | // Congestion avoidance 71 | // This implementation uses the method which does not require 72 | // floating point math, which also increases the window by 1 datagram 73 | // for every round trip. 74 | // This mechanism is called Appropriate Byte Counting in 75 | // https://tools.ietf.org/html/rfc3465 76 | self.bytes_acked += bytes; 77 | 78 | if self.bytes_acked >= self.window { 79 | self.bytes_acked -= self.window; 80 | self.window += self.current_mtu; 81 | } 82 | } 83 | } 84 | 85 | fn on_congestion_event( 86 | &mut self, 87 | now: Instant, 88 | sent: Instant, 89 | is_persistent_congestion: bool, 90 | _lost_bytes: u64, 91 | ) { 92 | if sent <= self.recovery_start_time { 93 | return; 94 | } 95 | 96 | self.recovery_start_time = now; 97 | self.window = (self.window as f32 * self.config.loss_reduction_factor) as u64; 98 | self.window = self.window.max(self.minimum_window()); 99 | self.ssthresh = self.window; 100 | 101 | if is_persistent_congestion { 102 | self.window = self.minimum_window(); 103 | } 104 | } 105 | 106 | fn on_mtu_update(&mut self, new_mtu: u16) { 107 | self.current_mtu = new_mtu as u64; 108 | self.window = self.window.max(self.minimum_window()); 109 | } 110 | 111 | fn window(&self) -> u64 { 112 | self.window 113 | } 114 | 115 | fn clone_box(&self) -> Box { 116 | Box::new(self.clone()) 117 | } 118 | 119 | fn initial_window(&self) -> u64 { 120 | self.config.initial_window 121 | } 122 | 123 | fn into_any(self: Box) -> Box { 124 | self 125 | } 126 | } 127 | 128 | /// Configuration for the `NewReno` congestion controller 129 | #[derive(Debug, Clone)] 130 | pub struct NewRenoConfig { 131 | initial_window: u64, 132 | loss_reduction_factor: f32, 133 | } 134 | 135 | impl NewRenoConfig { 136 | /// Default limit on the amount of outstanding data in bytes. 137 | /// 138 | /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))` 139 | pub fn initial_window(&mut self, value: u64) -> &mut Self { 140 | self.initial_window = value; 141 | self 142 | } 143 | 144 | /// Reduction in congestion window when a new loss event is detected. 145 | pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self { 146 | self.loss_reduction_factor = value; 147 | self 148 | } 149 | } 150 | 151 | impl Default for NewRenoConfig { 152 | fn default() -> Self { 153 | Self { 154 | initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE), 155 | loss_reduction_factor: 0.5, 156 | } 157 | } 158 | } 159 | 160 | impl ControllerFactory for NewRenoConfig { 161 | fn build(self: Arc, now: Instant, current_mtu: u16) -> Box { 162 | Box::new(NewReno::new(self, now, current_mtu)) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /quinn-proto/src/connection/ack_frequency.rs: -------------------------------------------------------------------------------- 1 | use crate::Duration; 2 | use crate::connection::spaces::PendingAcks; 3 | use crate::frame::AckFrequency; 4 | use crate::transport_parameters::TransportParameters; 5 | use crate::{AckFrequencyConfig, TIMER_GRANULARITY, TransportError, VarInt}; 6 | 7 | /// State associated to ACK frequency 8 | pub(super) struct AckFrequencyState { 9 | // 10 | // Sending ACK_FREQUENCY frames 11 | // 12 | in_flight_ack_frequency_frame: Option<(u64, Duration)>, 13 | next_outgoing_sequence_number: VarInt, 14 | pub(super) peer_max_ack_delay: Duration, 15 | 16 | // 17 | // Receiving ACK_FREQUENCY frames 18 | // 19 | last_ack_frequency_frame: Option, 20 | pub(super) max_ack_delay: Duration, 21 | } 22 | 23 | impl AckFrequencyState { 24 | pub(super) fn new(default_max_ack_delay: Duration) -> Self { 25 | Self { 26 | in_flight_ack_frequency_frame: None, 27 | next_outgoing_sequence_number: VarInt(0), 28 | peer_max_ack_delay: default_max_ack_delay, 29 | 30 | last_ack_frequency_frame: None, 31 | max_ack_delay: default_max_ack_delay, 32 | } 33 | } 34 | 35 | /// Returns the `max_ack_delay` that should be requested of the peer when sending an 36 | /// ACK_FREQUENCY frame 37 | pub(super) fn candidate_max_ack_delay( 38 | &self, 39 | rtt: Duration, 40 | config: &AckFrequencyConfig, 41 | peer_params: &TransportParameters, 42 | ) -> Duration { 43 | // Use the peer's max_ack_delay if no custom max_ack_delay was provided in the config 44 | let min_ack_delay = 45 | Duration::from_micros(peer_params.min_ack_delay.map_or(0, |x| x.into())); 46 | config 47 | .max_ack_delay 48 | .unwrap_or(self.peer_max_ack_delay) 49 | .clamp(min_ack_delay, rtt.max(MIN_AUTOMATIC_ACK_DELAY)) 50 | } 51 | 52 | /// Returns the `max_ack_delay` for the purposes of calculating the PTO 53 | /// 54 | /// This `max_ack_delay` is defined as the maximum of the peer's current `max_ack_delay` and all 55 | /// in-flight `max_ack_delay`s (i.e. proposed values that haven't been acknowledged yet, but 56 | /// might be already in use by the peer). 57 | pub(super) fn max_ack_delay_for_pto(&self) -> Duration { 58 | // Note: we have at most one in-flight ACK_FREQUENCY frame 59 | if let Some((_, max_ack_delay)) = self.in_flight_ack_frequency_frame { 60 | self.peer_max_ack_delay.max(max_ack_delay) 61 | } else { 62 | self.peer_max_ack_delay 63 | } 64 | } 65 | 66 | /// Returns the next sequence number for an ACK_FREQUENCY frame 67 | pub(super) fn next_sequence_number(&mut self) -> VarInt { 68 | assert!(self.next_outgoing_sequence_number <= VarInt::MAX); 69 | 70 | let seq = self.next_outgoing_sequence_number; 71 | self.next_outgoing_sequence_number.0 += 1; 72 | seq 73 | } 74 | 75 | /// Returns true if we should send an ACK_FREQUENCY frame 76 | pub(super) fn should_send_ack_frequency( 77 | &self, 78 | rtt: Duration, 79 | config: &AckFrequencyConfig, 80 | peer_params: &TransportParameters, 81 | ) -> bool { 82 | if self.next_outgoing_sequence_number.0 == 0 { 83 | // Always send at startup 84 | return true; 85 | } 86 | let current = self 87 | .in_flight_ack_frequency_frame 88 | .map_or(self.peer_max_ack_delay, |(_, pending)| pending); 89 | let desired = self.candidate_max_ack_delay(rtt, config, peer_params); 90 | let error = (desired.as_secs_f32() / current.as_secs_f32()) - 1.0; 91 | error.abs() > MAX_RTT_ERROR 92 | } 93 | 94 | /// Notifies the [`AckFrequencyState`] that a packet containing an ACK_FREQUENCY frame was sent 95 | pub(super) fn ack_frequency_sent(&mut self, pn: u64, requested_max_ack_delay: Duration) { 96 | self.in_flight_ack_frequency_frame = Some((pn, requested_max_ack_delay)); 97 | } 98 | 99 | /// Notifies the [`AckFrequencyState`] that a packet has been ACKed 100 | pub(super) fn on_acked(&mut self, pn: u64) { 101 | match self.in_flight_ack_frequency_frame { 102 | Some((number, requested_max_ack_delay)) if number == pn => { 103 | self.in_flight_ack_frequency_frame = None; 104 | self.peer_max_ack_delay = requested_max_ack_delay; 105 | } 106 | _ => {} 107 | } 108 | } 109 | 110 | /// Notifies the [`AckFrequencyState`] that an ACK_FREQUENCY frame was received 111 | /// 112 | /// Updates the endpoint's params according to the payload of the ACK_FREQUENCY frame, or 113 | /// returns an error in case the requested `max_ack_delay` is invalid. 114 | /// 115 | /// Returns `true` if the frame was processed and `false` if it was ignored because of being 116 | /// stale. 117 | pub(super) fn ack_frequency_received( 118 | &mut self, 119 | frame: &AckFrequency, 120 | pending_acks: &mut PendingAcks, 121 | ) -> Result { 122 | if self 123 | .last_ack_frequency_frame 124 | .is_some_and(|highest_sequence_nr| frame.sequence.into_inner() <= highest_sequence_nr) 125 | { 126 | return Ok(false); 127 | } 128 | 129 | self.last_ack_frequency_frame = Some(frame.sequence.into_inner()); 130 | 131 | // Update max_ack_delay 132 | let max_ack_delay = Duration::from_micros(frame.request_max_ack_delay.into_inner()); 133 | if max_ack_delay < TIMER_GRANULARITY { 134 | return Err(TransportError::PROTOCOL_VIOLATION( 135 | "Requested Max Ack Delay in ACK_FREQUENCY frame is less than min_ack_delay", 136 | )); 137 | } 138 | self.max_ack_delay = max_ack_delay; 139 | 140 | // Update the rest of the params 141 | pending_acks.set_ack_frequency_params(frame); 142 | 143 | Ok(true) 144 | } 145 | } 146 | 147 | /// Maximum proportion difference between the most recently requested max ACK delay and the 148 | /// currently desired one before a new request is sent, when the peer supports the ACK frequency 149 | /// extension and an explicit max ACK delay is not configured. 150 | const MAX_RTT_ERROR: f32 = 0.2; 151 | 152 | /// Minimum value to request the peer set max ACK delay to when the peer supports the ACK frequency 153 | /// extension and an explicit max ACK delay is not configured. 154 | // Keep in sync with `AckFrequencyConfig::max_ack_delay` documentation 155 | const MIN_AUTOMATIC_ACK_DELAY: Duration = Duration::from_millis(25); 156 | -------------------------------------------------------------------------------- /quinn-proto/src/connection/packet_crypto.rs: -------------------------------------------------------------------------------- 1 | use tracing::{debug, trace}; 2 | 3 | use crate::Instant; 4 | use crate::connection::spaces::PacketSpace; 5 | use crate::crypto::{HeaderKey, KeyPair, PacketKey}; 6 | use crate::packet::{Packet, PartialDecode, SpaceId}; 7 | use crate::token::ResetToken; 8 | use crate::{RESET_TOKEN_SIZE, TransportError}; 9 | 10 | /// Removes header protection of a packet, or returns `None` if the packet was dropped 11 | pub(super) fn unprotect_header( 12 | partial_decode: PartialDecode, 13 | spaces: &[PacketSpace; 3], 14 | zero_rtt_crypto: Option<&ZeroRttCrypto>, 15 | stateless_reset_token: Option, 16 | ) -> Option { 17 | let header_crypto = if partial_decode.is_0rtt() { 18 | if let Some(crypto) = zero_rtt_crypto { 19 | Some(&*crypto.header) 20 | } else { 21 | debug!("dropping unexpected 0-RTT packet"); 22 | return None; 23 | } 24 | } else if let Some(space) = partial_decode.space() { 25 | if let Some(ref crypto) = spaces[space].crypto { 26 | Some(&*crypto.header.remote) 27 | } else { 28 | debug!( 29 | "discarding unexpected {:?} packet ({} bytes)", 30 | space, 31 | partial_decode.len(), 32 | ); 33 | return None; 34 | } 35 | } else { 36 | // Unprotected packet 37 | None 38 | }; 39 | 40 | let packet = partial_decode.data(); 41 | let stateless_reset = packet.len() >= RESET_TOKEN_SIZE + 5 42 | && stateless_reset_token.as_deref() == Some(&packet[packet.len() - RESET_TOKEN_SIZE..]); 43 | 44 | match partial_decode.finish(header_crypto) { 45 | Ok(packet) => Some(UnprotectHeaderResult { 46 | packet: Some(packet), 47 | stateless_reset, 48 | }), 49 | Err(_) if stateless_reset => Some(UnprotectHeaderResult { 50 | packet: None, 51 | stateless_reset: true, 52 | }), 53 | Err(e) => { 54 | trace!("unable to complete packet decoding: {}", e); 55 | None 56 | } 57 | } 58 | } 59 | 60 | pub(super) struct UnprotectHeaderResult { 61 | /// The packet with the now unprotected header (`None` in the case of stateless reset packets 62 | /// that fail to be decoded) 63 | pub(super) packet: Option, 64 | /// Whether the packet was a stateless reset packet 65 | pub(super) stateless_reset: bool, 66 | } 67 | 68 | /// Decrypts a packet's body in-place 69 | pub(super) fn decrypt_packet_body( 70 | packet: &mut Packet, 71 | spaces: &[PacketSpace; 3], 72 | zero_rtt_crypto: Option<&ZeroRttCrypto>, 73 | conn_key_phase: bool, 74 | prev_crypto: Option<&PrevCrypto>, 75 | next_crypto: Option<&KeyPair>>, 76 | ) -> Result, Option> { 77 | if !packet.header.is_protected() { 78 | // Unprotected packets also don't have packet numbers 79 | return Ok(None); 80 | } 81 | let space = packet.header.space(); 82 | let rx_packet = spaces[space].rx_packet; 83 | let number = packet.header.number().ok_or(None)?.expand(rx_packet + 1); 84 | let packet_key_phase = packet.header.key_phase(); 85 | 86 | let mut crypto_update = false; 87 | let crypto = if packet.header.is_0rtt() { 88 | &zero_rtt_crypto.unwrap().packet 89 | } else if packet_key_phase == conn_key_phase || space != SpaceId::Data { 90 | &spaces[space].crypto.as_ref().unwrap().packet.remote 91 | } else if let Some(prev) = prev_crypto.and_then(|crypto| { 92 | // If this packet comes prior to acknowledgment of the key update by the peer, 93 | if crypto.end_packet.map_or(true, |(pn, _)| number < pn) { 94 | // use the previous keys. 95 | Some(crypto) 96 | } else { 97 | // Otherwise, this must be a remotely-initiated key update, so fall through to the 98 | // final case. 99 | None 100 | } 101 | }) { 102 | &prev.crypto.remote 103 | } else { 104 | // We're in the Data space with a key phase mismatch and either there is no locally 105 | // initiated key update or the locally initiated key update was acknowledged by a 106 | // lower-numbered packet. The key phase mismatch must therefore represent a new 107 | // remotely-initiated key update. 108 | crypto_update = true; 109 | &next_crypto.unwrap().remote 110 | }; 111 | 112 | crypto 113 | .decrypt(number, &packet.header_data, &mut packet.payload) 114 | .map_err(|_| { 115 | trace!("decryption failed with packet number {}", number); 116 | None 117 | })?; 118 | 119 | if !packet.reserved_bits_valid() { 120 | return Err(Some(TransportError::PROTOCOL_VIOLATION( 121 | "reserved bits set", 122 | ))); 123 | } 124 | 125 | let mut outgoing_key_update_acked = false; 126 | if let Some(prev) = prev_crypto { 127 | if prev.end_packet.is_none() && packet_key_phase == conn_key_phase { 128 | outgoing_key_update_acked = true; 129 | } 130 | } 131 | 132 | if crypto_update { 133 | // Validate incoming key update 134 | if number <= rx_packet || prev_crypto.is_some_and(|x| x.update_unacked) { 135 | return Err(Some(TransportError::KEY_UPDATE_ERROR(""))); 136 | } 137 | } 138 | 139 | Ok(Some(DecryptPacketResult { 140 | number, 141 | outgoing_key_update_acked, 142 | incoming_key_update: crypto_update, 143 | })) 144 | } 145 | 146 | pub(super) struct DecryptPacketResult { 147 | /// The packet number 148 | pub(super) number: u64, 149 | /// Whether a locally initiated key update has been acknowledged by the peer 150 | pub(super) outgoing_key_update_acked: bool, 151 | /// Whether the peer has initiated a key update 152 | pub(super) incoming_key_update: bool, 153 | } 154 | 155 | pub(super) struct PrevCrypto { 156 | /// The keys used for the previous key phase, temporarily retained to decrypt packets sent by 157 | /// the peer prior to its own key update. 158 | pub(super) crypto: KeyPair>, 159 | /// The incoming packet that ends the interval for which these keys are applicable, and the time 160 | /// of its receipt. 161 | /// 162 | /// Incoming packets should be decrypted using these keys iff this is `None` or their packet 163 | /// number is lower. `None` indicates that we have not yet received a packet using newer keys, 164 | /// which implies that the update was locally initiated. 165 | pub(super) end_packet: Option<(u64, Instant)>, 166 | /// Whether the following key phase is from a remotely initiated update that we haven't acked 167 | pub(super) update_unacked: bool, 168 | } 169 | 170 | pub(super) struct ZeroRttCrypto { 171 | pub(super) header: Box, 172 | pub(super) packet: Box, 173 | } 174 | -------------------------------------------------------------------------------- /quinn-proto/src/connection/stats.rs: -------------------------------------------------------------------------------- 1 | //! Connection statistics 2 | 3 | use crate::{Dir, Duration, frame::Frame}; 4 | 5 | /// Statistics about UDP datagrams transmitted or received on a connection 6 | #[derive(Default, Debug, Copy, Clone)] 7 | #[non_exhaustive] 8 | pub struct UdpStats { 9 | /// The amount of UDP datagrams observed 10 | pub datagrams: u64, 11 | /// The total amount of bytes which have been transferred inside UDP datagrams 12 | pub bytes: u64, 13 | /// The amount of I/O operations executed 14 | /// 15 | /// Can be less than `datagrams` when GSO, GRO, and/or batched system calls are in use. 16 | pub ios: u64, 17 | } 18 | 19 | impl UdpStats { 20 | pub(crate) fn on_sent(&mut self, datagrams: u64, bytes: usize) { 21 | self.datagrams += datagrams; 22 | self.bytes += bytes as u64; 23 | self.ios += 1; 24 | } 25 | } 26 | 27 | /// Number of frames transmitted of each frame type 28 | #[derive(Default, Copy, Clone)] 29 | #[non_exhaustive] 30 | #[allow(missing_docs)] 31 | pub struct FrameStats { 32 | pub acks: u64, 33 | pub ack_frequency: u64, 34 | pub crypto: u64, 35 | pub connection_close: u64, 36 | pub data_blocked: u64, 37 | pub datagram: u64, 38 | pub handshake_done: u8, 39 | pub immediate_ack: u64, 40 | pub max_data: u64, 41 | pub max_stream_data: u64, 42 | pub max_streams_bidi: u64, 43 | pub max_streams_uni: u64, 44 | pub new_connection_id: u64, 45 | pub new_token: u64, 46 | pub path_challenge: u64, 47 | pub path_response: u64, 48 | pub ping: u64, 49 | pub reset_stream: u64, 50 | pub retire_connection_id: u64, 51 | pub stream_data_blocked: u64, 52 | pub streams_blocked_bidi: u64, 53 | pub streams_blocked_uni: u64, 54 | pub stop_sending: u64, 55 | pub stream: u64, 56 | } 57 | 58 | impl FrameStats { 59 | pub(crate) fn record(&mut self, frame: &Frame) { 60 | match frame { 61 | Frame::Padding => {} 62 | Frame::Ping => self.ping += 1, 63 | Frame::Ack(_) => self.acks += 1, 64 | Frame::ResetStream(_) => self.reset_stream += 1, 65 | Frame::StopSending(_) => self.stop_sending += 1, 66 | Frame::Crypto(_) => self.crypto += 1, 67 | Frame::Datagram(_) => self.datagram += 1, 68 | Frame::NewToken(_) => self.new_token += 1, 69 | Frame::MaxData(_) => self.max_data += 1, 70 | Frame::MaxStreamData { .. } => self.max_stream_data += 1, 71 | Frame::MaxStreams { dir, .. } => { 72 | if *dir == Dir::Bi { 73 | self.max_streams_bidi += 1; 74 | } else { 75 | self.max_streams_uni += 1; 76 | } 77 | } 78 | Frame::DataBlocked { .. } => self.data_blocked += 1, 79 | Frame::Stream(_) => self.stream += 1, 80 | Frame::StreamDataBlocked { .. } => self.stream_data_blocked += 1, 81 | Frame::StreamsBlocked { dir, .. } => { 82 | if *dir == Dir::Bi { 83 | self.streams_blocked_bidi += 1; 84 | } else { 85 | self.streams_blocked_uni += 1; 86 | } 87 | } 88 | Frame::NewConnectionId(_) => self.new_connection_id += 1, 89 | Frame::RetireConnectionId { .. } => self.retire_connection_id += 1, 90 | Frame::PathChallenge(_) => self.path_challenge += 1, 91 | Frame::PathResponse(_) => self.path_response += 1, 92 | Frame::Close(_) => self.connection_close += 1, 93 | Frame::AckFrequency(_) => self.ack_frequency += 1, 94 | Frame::ImmediateAck => self.immediate_ack += 1, 95 | Frame::HandshakeDone => self.handshake_done = self.handshake_done.saturating_add(1), 96 | } 97 | } 98 | } 99 | 100 | impl std::fmt::Debug for FrameStats { 101 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 102 | f.debug_struct("FrameStats") 103 | .field("ACK", &self.acks) 104 | .field("ACK_FREQUENCY", &self.ack_frequency) 105 | .field("CONNECTION_CLOSE", &self.connection_close) 106 | .field("CRYPTO", &self.crypto) 107 | .field("DATA_BLOCKED", &self.data_blocked) 108 | .field("DATAGRAM", &self.datagram) 109 | .field("HANDSHAKE_DONE", &self.handshake_done) 110 | .field("IMMEDIATE_ACK", &self.immediate_ack) 111 | .field("MAX_DATA", &self.max_data) 112 | .field("MAX_STREAM_DATA", &self.max_stream_data) 113 | .field("MAX_STREAMS_BIDI", &self.max_streams_bidi) 114 | .field("MAX_STREAMS_UNI", &self.max_streams_uni) 115 | .field("NEW_CONNECTION_ID", &self.new_connection_id) 116 | .field("NEW_TOKEN", &self.new_token) 117 | .field("PATH_CHALLENGE", &self.path_challenge) 118 | .field("PATH_RESPONSE", &self.path_response) 119 | .field("PING", &self.ping) 120 | .field("RESET_STREAM", &self.reset_stream) 121 | .field("RETIRE_CONNECTION_ID", &self.retire_connection_id) 122 | .field("STREAM_DATA_BLOCKED", &self.stream_data_blocked) 123 | .field("STREAMS_BLOCKED_BIDI", &self.streams_blocked_bidi) 124 | .field("STREAMS_BLOCKED_UNI", &self.streams_blocked_uni) 125 | .field("STOP_SENDING", &self.stop_sending) 126 | .field("STREAM", &self.stream) 127 | .finish() 128 | } 129 | } 130 | 131 | /// Statistics related to a transmission path 132 | #[derive(Debug, Default, Copy, Clone)] 133 | #[non_exhaustive] 134 | pub struct PathStats { 135 | /// Current best estimate of this connection's latency (round-trip-time) 136 | pub rtt: Duration, 137 | /// Current congestion window of the connection 138 | pub cwnd: u64, 139 | /// Congestion events on the connection 140 | pub congestion_events: u64, 141 | /// The amount of packets lost on this path 142 | pub lost_packets: u64, 143 | /// The amount of bytes lost on this path 144 | pub lost_bytes: u64, 145 | /// The amount of packets sent on this path 146 | pub sent_packets: u64, 147 | /// The amount of PLPMTUD probe packets sent on this path (also counted by `sent_packets`) 148 | pub sent_plpmtud_probes: u64, 149 | /// The amount of PLPMTUD probe packets lost on this path (ignored by `lost_packets` and 150 | /// `lost_bytes`) 151 | pub lost_plpmtud_probes: u64, 152 | /// The number of times a black hole was detected in the path 153 | pub black_holes_detected: u64, 154 | /// Largest UDP payload size the path currently supports 155 | pub current_mtu: u16, 156 | } 157 | 158 | /// Connection statistics 159 | #[derive(Debug, Default, Copy, Clone)] 160 | #[non_exhaustive] 161 | pub struct ConnectionStats { 162 | /// Statistics about UDP datagrams transmitted on a connection 163 | pub udp_tx: UdpStats, 164 | /// Statistics about UDP datagrams received on a connection 165 | pub udp_rx: UdpStats, 166 | /// Statistics about frames transmitted on a connection 167 | pub frame_tx: FrameStats, 168 | /// Statistics about frames received on a connection 169 | pub frame_rx: FrameStats, 170 | /// Statistics related to the current transmission path 171 | pub path: PathStats, 172 | } 173 | -------------------------------------------------------------------------------- /quinn-proto/src/connection/timer.rs: -------------------------------------------------------------------------------- 1 | use crate::Instant; 2 | 3 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 4 | pub(crate) enum Timer { 5 | /// When to send an ack-eliciting probe packet or declare unacked packets lost 6 | LossDetection = 0, 7 | /// When to close the connection after no activity 8 | Idle = 1, 9 | /// When the close timer expires, the connection has been gracefully terminated. 10 | Close = 2, 11 | /// When keys are discarded because they should not be needed anymore 12 | KeyDiscard = 3, 13 | /// When to give up on validating a new path to the peer 14 | PathValidation = 4, 15 | /// When to send a `PING` frame to keep the connection alive 16 | KeepAlive = 5, 17 | /// When pacing will allow us to send a packet 18 | Pacing = 6, 19 | /// When to invalidate old CID and proactively push new one via NEW_CONNECTION_ID frame 20 | PushNewCid = 7, 21 | /// When to send an immediate ACK if there are unacked ack-eliciting packets of the peer 22 | MaxAckDelay = 8, 23 | } 24 | 25 | impl Timer { 26 | pub(crate) const VALUES: [Self; 9] = [ 27 | Self::LossDetection, 28 | Self::Idle, 29 | Self::Close, 30 | Self::KeyDiscard, 31 | Self::PathValidation, 32 | Self::KeepAlive, 33 | Self::Pacing, 34 | Self::PushNewCid, 35 | Self::MaxAckDelay, 36 | ]; 37 | } 38 | 39 | /// A table of data associated with each distinct kind of `Timer` 40 | #[derive(Debug, Copy, Clone, Default)] 41 | pub(crate) struct TimerTable { 42 | data: [Option; 10], 43 | } 44 | 45 | impl TimerTable { 46 | pub(super) fn set(&mut self, timer: Timer, time: Instant) { 47 | self.data[timer as usize] = Some(time); 48 | } 49 | 50 | pub(super) fn get(&self, timer: Timer) -> Option { 51 | self.data[timer as usize] 52 | } 53 | 54 | pub(super) fn stop(&mut self, timer: Timer) { 55 | self.data[timer as usize] = None; 56 | } 57 | 58 | pub(super) fn next_timeout(&self) -> Option { 59 | self.data.iter().filter_map(|&x| x).min() 60 | } 61 | 62 | pub(super) fn is_expired(&self, timer: Timer, after: Instant) -> bool { 63 | self.data[timer as usize].is_some_and(|x| x <= after) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /quinn-proto/src/constant_time.rs: -------------------------------------------------------------------------------- 1 | // This function is non-inline to prevent the optimizer from looking inside it. 2 | #[inline(never)] 3 | fn constant_time_ne(a: &[u8], b: &[u8]) -> u8 { 4 | assert!(a.len() == b.len()); 5 | 6 | // These useless slices make the optimizer elide the bounds checks. 7 | // See the comment in clone_from_slice() added on Rust commit 6a7bc47. 8 | let len = a.len(); 9 | let a = &a[..len]; 10 | let b = &b[..len]; 11 | 12 | let mut tmp = 0; 13 | for i in 0..len { 14 | tmp |= a[i] ^ b[i]; 15 | } 16 | tmp // The compare with 0 must happen outside this function. 17 | } 18 | 19 | /// Compares byte strings in constant time. 20 | pub(crate) fn eq(a: &[u8], b: &[u8]) -> bool { 21 | a.len() == b.len() && constant_time_ne(a, b) == 0 22 | } 23 | -------------------------------------------------------------------------------- /quinn-proto/src/crypto/ring_like.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] 2 | use aws_lc_rs::{aead, error, hkdf, hmac}; 3 | #[cfg(feature = "ring")] 4 | use ring::{aead, error, hkdf, hmac}; 5 | 6 | use crate::crypto::{self, CryptoError}; 7 | 8 | impl crypto::HmacKey for hmac::Key { 9 | fn sign(&self, data: &[u8], out: &mut [u8]) { 10 | out.copy_from_slice(hmac::sign(self, data).as_ref()); 11 | } 12 | 13 | fn signature_len(&self) -> usize { 14 | 32 15 | } 16 | 17 | fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), CryptoError> { 18 | Ok(hmac::verify(self, data, signature)?) 19 | } 20 | } 21 | 22 | impl crypto::HandshakeTokenKey for hkdf::Prk { 23 | fn aead_from_hkdf(&self, random_bytes: &[u8]) -> Box { 24 | let mut key_buffer = [0u8; 32]; 25 | let info = [random_bytes]; 26 | let okm = self.expand(&info, hkdf::HKDF_SHA256).unwrap(); 27 | 28 | okm.fill(&mut key_buffer).unwrap(); 29 | 30 | let key = aead::UnboundKey::new(&aead::AES_256_GCM, &key_buffer).unwrap(); 31 | Box::new(aead::LessSafeKey::new(key)) 32 | } 33 | } 34 | 35 | impl crypto::AeadKey for aead::LessSafeKey { 36 | fn seal(&self, data: &mut Vec, additional_data: &[u8]) -> Result<(), CryptoError> { 37 | let aad = aead::Aad::from(additional_data); 38 | let zero_nonce = aead::Nonce::assume_unique_for_key([0u8; 12]); 39 | Ok(self.seal_in_place_append_tag(zero_nonce, aad, data)?) 40 | } 41 | 42 | fn open<'a>( 43 | &self, 44 | data: &'a mut [u8], 45 | additional_data: &[u8], 46 | ) -> Result<&'a mut [u8], CryptoError> { 47 | let aad = aead::Aad::from(additional_data); 48 | let zero_nonce = aead::Nonce::assume_unique_for_key([0u8; 12]); 49 | Ok(self.open_in_place(zero_nonce, aad, data)?) 50 | } 51 | } 52 | 53 | impl From for CryptoError { 54 | fn from(_: error::Unspecified) -> Self { 55 | Self 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /quinn-proto/src/range_set/mod.rs: -------------------------------------------------------------------------------- 1 | mod array_range_set; 2 | mod btree_range_set; 3 | #[cfg(test)] 4 | mod tests; 5 | 6 | pub(crate) use array_range_set::ArrayRangeSet; 7 | pub(crate) use btree_range_set::RangeSet; 8 | -------------------------------------------------------------------------------- /quinn-proto/src/shared.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, net::SocketAddr}; 2 | 3 | use bytes::{Buf, BufMut, BytesMut}; 4 | 5 | use crate::{Instant, MAX_CID_SIZE, ResetToken, coding::BufExt, packet::PartialDecode}; 6 | 7 | /// Events sent from an Endpoint to a Connection 8 | #[derive(Debug)] 9 | pub struct ConnectionEvent(pub(crate) ConnectionEventInner); 10 | 11 | #[derive(Debug)] 12 | pub(crate) enum ConnectionEventInner { 13 | /// A datagram has been received for the Connection 14 | Datagram(DatagramConnectionEvent), 15 | /// New connection identifiers have been issued for the Connection 16 | NewIdentifiers(Vec, Instant), 17 | } 18 | 19 | /// Variant of [`ConnectionEventInner`]. 20 | #[derive(Debug)] 21 | pub(crate) struct DatagramConnectionEvent { 22 | pub(crate) now: Instant, 23 | pub(crate) remote: SocketAddr, 24 | pub(crate) ecn: Option, 25 | pub(crate) first_decode: PartialDecode, 26 | pub(crate) remaining: Option, 27 | } 28 | 29 | /// Events sent from a Connection to an Endpoint 30 | #[derive(Debug)] 31 | pub struct EndpointEvent(pub(crate) EndpointEventInner); 32 | 33 | impl EndpointEvent { 34 | /// Construct an event that indicating that a `Connection` will no longer emit events 35 | /// 36 | /// Useful for notifying an `Endpoint` that a `Connection` has been destroyed outside of the 37 | /// usual state machine flow, e.g. when being dropped by the user. 38 | pub fn drained() -> Self { 39 | Self(EndpointEventInner::Drained) 40 | } 41 | 42 | /// Determine whether this is the last event a `Connection` will emit 43 | /// 44 | /// Useful for determining when connection-related event loop state can be freed. 45 | pub fn is_drained(&self) -> bool { 46 | self.0 == EndpointEventInner::Drained 47 | } 48 | } 49 | 50 | #[derive(Clone, Debug, Eq, PartialEq)] 51 | pub(crate) enum EndpointEventInner { 52 | /// The connection has been drained 53 | Drained, 54 | /// The reset token and/or address eligible for generating resets has been updated 55 | ResetToken(SocketAddr, ResetToken), 56 | /// The connection needs connection identifiers 57 | NeedIdentifiers(Instant, u64), 58 | /// Stop routing connection ID for this sequence number to the connection 59 | /// When `bool == true`, a new connection ID will be issued to peer 60 | RetireConnectionId(Instant, u64, bool), 61 | } 62 | 63 | /// Protocol-level identifier for a connection. 64 | /// 65 | /// Mainly useful for identifying this connection's packets on the wire with tools like Wireshark. 66 | #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 67 | pub struct ConnectionId { 68 | /// length of CID 69 | len: u8, 70 | /// CID in byte array 71 | bytes: [u8; MAX_CID_SIZE], 72 | } 73 | 74 | impl ConnectionId { 75 | /// Construct cid from byte array 76 | pub fn new(bytes: &[u8]) -> Self { 77 | debug_assert!(bytes.len() <= MAX_CID_SIZE); 78 | let mut res = Self { 79 | len: bytes.len() as u8, 80 | bytes: [0; MAX_CID_SIZE], 81 | }; 82 | res.bytes[..bytes.len()].copy_from_slice(bytes); 83 | res 84 | } 85 | 86 | /// Constructs cid by reading `len` bytes from a `Buf` 87 | /// 88 | /// Callers need to assure that `buf.remaining() >= len` 89 | pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> Self { 90 | debug_assert!(len <= MAX_CID_SIZE); 91 | let mut res = Self { 92 | len: len as u8, 93 | bytes: [0; MAX_CID_SIZE], 94 | }; 95 | buf.copy_to_slice(&mut res[..len]); 96 | res 97 | } 98 | 99 | /// Decode from long header format 100 | pub(crate) fn decode_long(buf: &mut impl Buf) -> Option { 101 | let len = buf.get::().ok()? as usize; 102 | match len > MAX_CID_SIZE || buf.remaining() < len { 103 | false => Some(Self::from_buf(buf, len)), 104 | true => None, 105 | } 106 | } 107 | 108 | /// Encode in long header format 109 | pub(crate) fn encode_long(&self, buf: &mut impl BufMut) { 110 | buf.put_u8(self.len() as u8); 111 | buf.put_slice(self); 112 | } 113 | } 114 | 115 | impl ::std::ops::Deref for ConnectionId { 116 | type Target = [u8]; 117 | fn deref(&self) -> &[u8] { 118 | &self.bytes[0..self.len as usize] 119 | } 120 | } 121 | 122 | impl ::std::ops::DerefMut for ConnectionId { 123 | fn deref_mut(&mut self) -> &mut [u8] { 124 | &mut self.bytes[0..self.len as usize] 125 | } 126 | } 127 | 128 | impl fmt::Debug for ConnectionId { 129 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 130 | self.bytes[0..self.len as usize].fmt(f) 131 | } 132 | } 133 | 134 | impl fmt::Display for ConnectionId { 135 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 136 | for byte in self.iter() { 137 | write!(f, "{byte:02x}")?; 138 | } 139 | Ok(()) 140 | } 141 | } 142 | 143 | /// Explicit congestion notification codepoint 144 | #[repr(u8)] 145 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 146 | pub enum EcnCodepoint { 147 | /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable 148 | Ect0 = 0b10, 149 | /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable 150 | Ect1 = 0b01, 151 | /// The CE codepoint, signalling that congestion was experienced 152 | Ce = 0b11, 153 | } 154 | 155 | impl EcnCodepoint { 156 | /// Create new object from the given bits 157 | pub fn from_bits(x: u8) -> Option { 158 | use EcnCodepoint::*; 159 | Some(match x & 0b11 { 160 | 0b10 => Ect0, 161 | 0b01 => Ect1, 162 | 0b11 => Ce, 163 | _ => { 164 | return None; 165 | } 166 | }) 167 | } 168 | 169 | /// Returns whether the codepoint is a CE, signalling that congestion was experienced 170 | pub fn is_ce(self) -> bool { 171 | matches!(self, Self::Ce) 172 | } 173 | } 174 | 175 | #[derive(Debug, Copy, Clone)] 176 | pub(crate) struct IssuedCid { 177 | pub(crate) sequence: u64, 178 | pub(crate) id: ConnectionId, 179 | pub(crate) reset_token: ResetToken, 180 | } 181 | -------------------------------------------------------------------------------- /quinn-proto/src/transport_error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use bytes::{Buf, BufMut}; 4 | 5 | use crate::{ 6 | coding::{self, BufExt, BufMutExt}, 7 | frame, 8 | }; 9 | 10 | /// Transport-level errors occur when a peer violates the protocol specification 11 | #[derive(Debug, Clone, Eq, PartialEq)] 12 | pub struct Error { 13 | /// Type of error 14 | pub code: Code, 15 | /// Frame type that triggered the error 16 | pub frame: Option, 17 | /// Human-readable explanation of the reason 18 | pub reason: String, 19 | } 20 | 21 | impl fmt::Display for Error { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | self.code.fmt(f)?; 24 | if let Some(frame) = self.frame { 25 | write!(f, " in {frame}")?; 26 | } 27 | if !self.reason.is_empty() { 28 | write!(f, ": {}", self.reason)?; 29 | } 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl std::error::Error for Error {} 35 | 36 | impl From for Error { 37 | fn from(x: Code) -> Self { 38 | Self { 39 | code: x, 40 | frame: None, 41 | reason: "".to_string(), 42 | } 43 | } 44 | } 45 | 46 | /// Transport-level error code 47 | #[derive(Copy, Clone, Eq, PartialEq)] 48 | pub struct Code(u64); 49 | 50 | impl Code { 51 | /// Create QUIC error code from TLS alert code 52 | pub fn crypto(code: u8) -> Self { 53 | Self(0x100 | u64::from(code)) 54 | } 55 | } 56 | 57 | impl coding::Codec for Code { 58 | fn decode(buf: &mut B) -> coding::Result { 59 | Ok(Self(buf.get_var()?)) 60 | } 61 | fn encode(&self, buf: &mut B) { 62 | buf.write_var(self.0) 63 | } 64 | } 65 | 66 | impl From for u64 { 67 | fn from(x: Code) -> Self { 68 | x.0 69 | } 70 | } 71 | 72 | macro_rules! errors { 73 | {$($name:ident($val:expr) $desc:expr;)*} => { 74 | #[allow(non_snake_case, unused)] 75 | impl Error { 76 | $( 77 | pub(crate) fn $name(reason: T) -> Self where T: Into { 78 | Self { 79 | code: Code::$name, 80 | frame: None, 81 | reason: reason.into(), 82 | } 83 | } 84 | )* 85 | } 86 | 87 | impl Code { 88 | $(#[doc = $desc] pub const $name: Self = Code($val);)* 89 | } 90 | 91 | impl fmt::Debug for Code { 92 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 93 | match self.0 { 94 | $($val => f.write_str(stringify!($name)),)* 95 | x if (0x100..0x200).contains(&x) => write!(f, "Code::crypto({:02x})", self.0 as u8), 96 | _ => write!(f, "Code({:x})", self.0), 97 | } 98 | } 99 | } 100 | 101 | impl fmt::Display for Code { 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | match self.0 { 104 | $($val => f.write_str($desc),)* 105 | // We're trying to be abstract over the crypto protocol, so human-readable descriptions here is tricky. 106 | _ if self.0 >= 0x100 && self.0 < 0x200 => write!(f, "the cryptographic handshake failed: error {}", self.0 & 0xFF), 107 | _ => f.write_str("unknown error"), 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | errors! { 115 | NO_ERROR(0x0) "the connection is being closed abruptly in the absence of any error"; 116 | INTERNAL_ERROR(0x1) "the endpoint encountered an internal error and cannot continue with the connection"; 117 | CONNECTION_REFUSED(0x2) "the server refused to accept a new connection"; 118 | FLOW_CONTROL_ERROR(0x3) "received more data than permitted in advertised data limits"; 119 | STREAM_LIMIT_ERROR(0x4) "received a frame for a stream identifier that exceeded advertised the stream limit for the corresponding stream type"; 120 | STREAM_STATE_ERROR(0x5) "received a frame for a stream that was not in a state that permitted that frame"; 121 | FINAL_SIZE_ERROR(0x6) "received a STREAM frame or a RESET_STREAM frame containing a different final size to the one already established"; 122 | FRAME_ENCODING_ERROR(0x7) "received a frame that was badly formatted"; 123 | TRANSPORT_PARAMETER_ERROR(0x8) "received transport parameters that were badly formatted, included an invalid value, was absent even though it is mandatory, was present though it is forbidden, or is otherwise in error"; 124 | CONNECTION_ID_LIMIT_ERROR(0x9) "the number of connection IDs provided by the peer exceeds the advertised active_connection_id_limit"; 125 | PROTOCOL_VIOLATION(0xA) "detected an error with protocol compliance that was not covered by more specific error codes"; 126 | INVALID_TOKEN(0xB) "received an invalid Retry Token in a client Initial"; 127 | APPLICATION_ERROR(0xC) "the application or application protocol caused the connection to be closed during the handshake"; 128 | CRYPTO_BUFFER_EXCEEDED(0xD) "received more data in CRYPTO frames than can be buffered"; 129 | KEY_UPDATE_ERROR(0xE) "key update error"; 130 | AEAD_LIMIT_REACHED(0xF) "the endpoint has reached the confidentiality or integrity limit for the AEAD algorithm"; 131 | NO_VIABLE_PATH(0x10) "no viable network path exists"; 132 | } 133 | -------------------------------------------------------------------------------- /quinn-proto/src/varint.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryInto, fmt}; 2 | 3 | use bytes::{Buf, BufMut}; 4 | use thiserror::Error; 5 | 6 | use crate::coding::{self, Codec, UnexpectedEnd}; 7 | 8 | #[cfg(feature = "arbitrary")] 9 | use arbitrary::Arbitrary; 10 | 11 | /// An integer less than 2^62 12 | /// 13 | /// Values of this type are suitable for encoding as QUIC variable-length integer. 14 | // It would be neat if we could express to Rust that the top two bits are available for use as enum 15 | // discriminants 16 | #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 17 | pub struct VarInt(pub(crate) u64); 18 | 19 | impl VarInt { 20 | /// The largest representable value 21 | pub const MAX: Self = Self((1 << 62) - 1); 22 | /// The largest encoded value length 23 | pub const MAX_SIZE: usize = 8; 24 | 25 | /// Construct a `VarInt` infallibly 26 | pub const fn from_u32(x: u32) -> Self { 27 | Self(x as u64) 28 | } 29 | 30 | /// Succeeds iff `x` < 2^62 31 | pub fn from_u64(x: u64) -> Result { 32 | if x < 2u64.pow(62) { 33 | Ok(Self(x)) 34 | } else { 35 | Err(VarIntBoundsExceeded) 36 | } 37 | } 38 | 39 | /// Create a VarInt without ensuring it's in range 40 | /// 41 | /// # Safety 42 | /// 43 | /// `x` must be less than 2^62. 44 | pub const unsafe fn from_u64_unchecked(x: u64) -> Self { 45 | Self(x) 46 | } 47 | 48 | /// Extract the integer value 49 | pub const fn into_inner(self) -> u64 { 50 | self.0 51 | } 52 | 53 | /// Compute the number of bytes needed to encode this value 54 | pub(crate) const fn size(self) -> usize { 55 | let x = self.0; 56 | if x < 2u64.pow(6) { 57 | 1 58 | } else if x < 2u64.pow(14) { 59 | 2 60 | } else if x < 2u64.pow(30) { 61 | 4 62 | } else if x < 2u64.pow(62) { 63 | 8 64 | } else { 65 | panic!("malformed VarInt"); 66 | } 67 | } 68 | } 69 | 70 | impl From for u64 { 71 | fn from(x: VarInt) -> Self { 72 | x.0 73 | } 74 | } 75 | 76 | impl From for VarInt { 77 | fn from(x: u8) -> Self { 78 | Self(x.into()) 79 | } 80 | } 81 | 82 | impl From for VarInt { 83 | fn from(x: u16) -> Self { 84 | Self(x.into()) 85 | } 86 | } 87 | 88 | impl From for VarInt { 89 | fn from(x: u32) -> Self { 90 | Self(x.into()) 91 | } 92 | } 93 | 94 | impl std::convert::TryFrom for VarInt { 95 | type Error = VarIntBoundsExceeded; 96 | /// Succeeds iff `x` < 2^62 97 | fn try_from(x: u64) -> Result { 98 | Self::from_u64(x) 99 | } 100 | } 101 | 102 | impl std::convert::TryFrom for VarInt { 103 | type Error = VarIntBoundsExceeded; 104 | /// Succeeds iff `x` < 2^62 105 | fn try_from(x: u128) -> Result { 106 | Self::from_u64(x.try_into().map_err(|_| VarIntBoundsExceeded)?) 107 | } 108 | } 109 | 110 | impl std::convert::TryFrom for VarInt { 111 | type Error = VarIntBoundsExceeded; 112 | /// Succeeds iff `x` < 2^62 113 | fn try_from(x: usize) -> Result { 114 | Self::try_from(x as u64) 115 | } 116 | } 117 | 118 | impl fmt::Debug for VarInt { 119 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 120 | self.0.fmt(f) 121 | } 122 | } 123 | 124 | impl fmt::Display for VarInt { 125 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 | self.0.fmt(f) 127 | } 128 | } 129 | 130 | #[cfg(feature = "arbitrary")] 131 | impl<'arbitrary> Arbitrary<'arbitrary> for VarInt { 132 | fn arbitrary(u: &mut arbitrary::Unstructured<'arbitrary>) -> arbitrary::Result { 133 | Ok(Self(u.int_in_range(0..=Self::MAX.0)?)) 134 | } 135 | } 136 | 137 | /// Error returned when constructing a `VarInt` from a value >= 2^62 138 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Error)] 139 | #[error("value too large for varint encoding")] 140 | pub struct VarIntBoundsExceeded; 141 | 142 | impl Codec for VarInt { 143 | fn decode(r: &mut B) -> coding::Result { 144 | if !r.has_remaining() { 145 | return Err(UnexpectedEnd); 146 | } 147 | let mut buf = [0; 8]; 148 | buf[0] = r.get_u8(); 149 | let tag = buf[0] >> 6; 150 | buf[0] &= 0b0011_1111; 151 | let x = match tag { 152 | 0b00 => u64::from(buf[0]), 153 | 0b01 => { 154 | if r.remaining() < 1 { 155 | return Err(UnexpectedEnd); 156 | } 157 | r.copy_to_slice(&mut buf[1..2]); 158 | u64::from(u16::from_be_bytes(buf[..2].try_into().unwrap())) 159 | } 160 | 0b10 => { 161 | if r.remaining() < 3 { 162 | return Err(UnexpectedEnd); 163 | } 164 | r.copy_to_slice(&mut buf[1..4]); 165 | u64::from(u32::from_be_bytes(buf[..4].try_into().unwrap())) 166 | } 167 | 0b11 => { 168 | if r.remaining() < 7 { 169 | return Err(UnexpectedEnd); 170 | } 171 | r.copy_to_slice(&mut buf[1..8]); 172 | u64::from_be_bytes(buf) 173 | } 174 | _ => unreachable!(), 175 | }; 176 | Ok(Self(x)) 177 | } 178 | 179 | fn encode(&self, w: &mut B) { 180 | let x = self.0; 181 | if x < 2u64.pow(6) { 182 | w.put_u8(x as u8); 183 | } else if x < 2u64.pow(14) { 184 | w.put_u16((0b01 << 14) | x as u16); 185 | } else if x < 2u64.pow(30) { 186 | w.put_u32((0b10 << 30) | x as u32); 187 | } else if x < 2u64.pow(62) { 188 | w.put_u64((0b11 << 62) | x); 189 | } else { 190 | unreachable!("malformed VarInt") 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /quinn-udp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quinn-udp" 3 | version = "0.5.12" 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "UDP sockets with ECN information for the QUIC transport protocol" 9 | keywords.workspace = true 10 | categories.workspace = true 11 | workspace = ".." 12 | 13 | [features] 14 | # NOTE: Please keep this in sync with the feature list in `.github/workflows/codecov.yml`, see 15 | # comment in that file for more information. 16 | default = ["tracing", "log"] 17 | # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. 18 | log = ["tracing/log"] 19 | direct-log = ["dep:log"] 20 | # Use private Apple APIs to send multiple packets in a single syscall. 21 | fast-apple-datapath = [] 22 | 23 | [dependencies] 24 | libc = "0.2.158" 25 | log = { workspace = true, optional = true } 26 | tracing = { workspace = true, optional = true } 27 | 28 | [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] 29 | socket2 = { workspace = true } 30 | 31 | [target.'cfg(windows)'.dependencies] 32 | once_cell = { workspace = true } 33 | windows-sys = { workspace = true } 34 | 35 | [dev-dependencies] 36 | criterion = { version = "0.6", default-features = false, features = ["async_tokio"] } 37 | tokio = { workspace = true, features = ["rt", "rt-multi-thread", "net"] } 38 | 39 | [build-dependencies] 40 | cfg_aliases = { workspace = true } 41 | 42 | [lib] 43 | # See https://github.com/bheisler/criterion.rs/blob/master/book/src/faq.md#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options 44 | bench = false 45 | 46 | [[bench]] 47 | name = "throughput" 48 | harness = false 49 | 50 | [package.metadata.docs.rs] 51 | all-features = true 52 | -------------------------------------------------------------------------------- /quinn-udp/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /quinn-udp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /quinn-udp/benches/throughput.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::min, 3 | io::{ErrorKind, IoSliceMut}, 4 | net::{Ipv4Addr, Ipv6Addr, UdpSocket}, 5 | }; 6 | 7 | use criterion::{Criterion, criterion_group, criterion_main}; 8 | use tokio::{io::Interest, runtime::Runtime}; 9 | 10 | use quinn_udp::{BATCH_SIZE, RecvMeta, Transmit, UdpSocketState}; 11 | 12 | pub fn criterion_benchmark(c: &mut Criterion) { 13 | const TOTAL_BYTES: usize = 10 * 1024 * 1024; 14 | const SEGMENT_SIZE: usize = 1280; 15 | 16 | let rt = Runtime::new().unwrap(); 17 | let _guard = rt.enter(); 18 | 19 | let (send_state, send_socket) = new_socket(); 20 | let (recv_state, recv_socket) = new_socket(); 21 | let dst_addr = recv_socket.local_addr().unwrap(); 22 | 23 | let mut permutations = vec![]; 24 | for gso_enabled in [ 25 | false, 26 | #[cfg(any(target_os = "linux", target_os = "windows", apple))] 27 | true, 28 | ] { 29 | for gro_enabled in [false, true] { 30 | #[cfg(target_os = "windows")] 31 | if gso_enabled && !gro_enabled { 32 | // Windows requires receive buffer to fit entire datagram on GRO 33 | // enabled socket. 34 | // 35 | // OS error: "A message sent on a datagram socket was larger 36 | // than the internal message buffer or some other network limit, 37 | // or the buffer used to receive a datagram into was smaller 38 | // than the datagram itself." 39 | continue; 40 | } 41 | 42 | for recvmmsg_enabled in [false, true] { 43 | permutations.push((gso_enabled, gro_enabled, recvmmsg_enabled)); 44 | } 45 | } 46 | } 47 | 48 | for (gso_enabled, gro_enabled, recvmmsg_enabled) in permutations { 49 | let mut group = c.benchmark_group(format!( 50 | "gso_{}_gro_{}_recvmmsg_{}", 51 | gso_enabled, gro_enabled, recvmmsg_enabled 52 | )); 53 | group.throughput(criterion::Throughput::Bytes(TOTAL_BYTES as u64)); 54 | 55 | let gso_segments = if gso_enabled { 56 | send_state.max_gso_segments() 57 | } else { 58 | 1 59 | }; 60 | let msg = vec![0xAB; min(MAX_DATAGRAM_SIZE, SEGMENT_SIZE * gso_segments)]; 61 | let transmit = Transmit { 62 | destination: dst_addr, 63 | ecn: None, 64 | contents: &msg, 65 | segment_size: gso_enabled.then_some(SEGMENT_SIZE), 66 | src_ip: None, 67 | }; 68 | let gro_segments = if gro_enabled { 69 | recv_state.gro_segments() 70 | } else { 71 | 1 72 | }; 73 | let batch_size = if recvmmsg_enabled { BATCH_SIZE } else { 1 }; 74 | 75 | group.bench_function("throughput", |b| { 76 | b.to_async(&rt).iter(|| async { 77 | let mut receive_buffers = vec![vec![0; SEGMENT_SIZE * gro_segments]; batch_size]; 78 | let mut receive_slices = receive_buffers 79 | .iter_mut() 80 | .map(|buf| IoSliceMut::new(buf)) 81 | .collect::>(); 82 | let mut meta = vec![RecvMeta::default(); batch_size]; 83 | 84 | let mut sent: usize = 0; 85 | let mut received: usize = 0; 86 | while sent < TOTAL_BYTES { 87 | send_socket.writable().await.unwrap(); 88 | send_socket 89 | .try_io(Interest::WRITABLE, || { 90 | send_state.send((&send_socket).into(), &transmit) 91 | }) 92 | .unwrap(); 93 | sent += transmit.contents.len(); 94 | 95 | while received < sent { 96 | recv_socket.readable().await.unwrap(); 97 | let n = match recv_socket.try_io(Interest::READABLE, || { 98 | recv_state.recv((&recv_socket).into(), &mut receive_slices, &mut meta) 99 | }) { 100 | Ok(n) => n, 101 | // recv.readable() can lead to false positives. Try again. 102 | Err(e) if e.kind() == ErrorKind::WouldBlock => continue, 103 | e => e.unwrap(), 104 | }; 105 | received += meta.iter().map(|m| m.len).take(n).sum::(); 106 | } 107 | } 108 | }) 109 | }); 110 | } 111 | } 112 | 113 | fn new_socket() -> (UdpSocketState, tokio::net::UdpSocket) { 114 | let socket = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0)) 115 | .or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) 116 | .unwrap(); 117 | 118 | ( 119 | UdpSocketState::new((&socket).into()).unwrap(), 120 | tokio::net::UdpSocket::from_std(socket).unwrap(), 121 | ) 122 | } 123 | 124 | criterion_group!(benches, criterion_benchmark); 125 | criterion_main!(benches); 126 | 127 | const MAX_IP_UDP_HEADER_SIZE: usize = 48; 128 | const MAX_DATAGRAM_SIZE: usize = u16::MAX as usize - MAX_IP_UDP_HEADER_SIZE; 129 | -------------------------------------------------------------------------------- /quinn-udp/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | // Setup cfg aliases 5 | cfg_aliases! { 6 | // Platforms 7 | apple: { 8 | any( 9 | target_os = "macos", 10 | target_os = "ios", 11 | target_os = "tvos", 12 | target_os = "visionos" 13 | ) 14 | }, 15 | bsd: { 16 | any( 17 | target_os = "freebsd", 18 | target_os = "openbsd", 19 | target_os = "netbsd" 20 | ) 21 | }, 22 | solarish: { 23 | any( 24 | target_os = "solaris", 25 | target_os = "illumos" 26 | ) 27 | }, 28 | // Convenience aliases 29 | apple_fast: { all(apple, feature = "fast-apple-datapath") }, 30 | apple_slow: { all(apple, not(feature = "fast-apple-datapath")) }, 31 | wasm_browser: { all(target_family = "wasm", target_os = "unknown") }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quinn-udp/src/cmsg/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_int, c_uchar}, 3 | mem, ptr, 4 | }; 5 | 6 | #[cfg(unix)] 7 | #[path = "unix.rs"] 8 | mod imp; 9 | 10 | #[cfg(windows)] 11 | #[path = "windows.rs"] 12 | mod imp; 13 | 14 | pub(crate) use imp::Aligned; 15 | 16 | /// Helper to encode a series of control messages (native "cmsgs") to a buffer for use in `sendmsg` 17 | // like API. 18 | /// 19 | /// The operation must be "finished" for the native msghdr to be usable, either by calling `finish` 20 | /// explicitly or by dropping the `Encoder`. 21 | pub(crate) struct Encoder<'a, M: MsgHdr> { 22 | hdr: &'a mut M, 23 | cmsg: Option<&'a mut M::ControlMessage>, 24 | len: usize, 25 | } 26 | 27 | impl<'a, M: MsgHdr> Encoder<'a, M> { 28 | /// # Safety 29 | /// - `hdr` must contain a suitably aligned pointer to a big enough buffer to hold control messages 30 | /// bytes. All bytes of this buffer can be safely written. 31 | /// - The `Encoder` must be dropped before `hdr` is passed to a system call, and must not be leaked. 32 | pub(crate) unsafe fn new(hdr: &'a mut M) -> Self { 33 | Self { 34 | cmsg: hdr.cmsg_first_hdr().as_mut(), 35 | hdr, 36 | len: 0, 37 | } 38 | } 39 | 40 | /// Append a control message to the buffer. 41 | /// 42 | /// # Panics 43 | /// - If insufficient buffer space remains. 44 | /// - If `T` has stricter alignment requirements than `M::ControlMessage` 45 | pub(crate) fn push(&mut self, level: c_int, ty: c_int, value: T) { 46 | assert!(mem::align_of::() <= mem::align_of::()); 47 | let space = M::ControlMessage::cmsg_space(mem::size_of_val(&value)); 48 | assert!( 49 | self.hdr.control_len() >= self.len + space, 50 | "control message buffer too small. Required: {}, Available: {}", 51 | self.len + space, 52 | self.hdr.control_len() 53 | ); 54 | let cmsg = self.cmsg.take().expect("no control buffer space remaining"); 55 | cmsg.set( 56 | level, 57 | ty, 58 | M::ControlMessage::cmsg_len(mem::size_of_val(&value)), 59 | ); 60 | unsafe { 61 | ptr::write(cmsg.cmsg_data() as *const T as *mut T, value); 62 | } 63 | self.len += space; 64 | self.cmsg = unsafe { self.hdr.cmsg_nxt_hdr(cmsg).as_mut() }; 65 | } 66 | 67 | /// Finishes appending control messages to the buffer 68 | pub(crate) fn finish(self) { 69 | // Delegates to the `Drop` impl 70 | } 71 | } 72 | 73 | // Statically guarantees that the encoding operation is "finished" before the control buffer is read 74 | // by `sendmsg` like API. 75 | impl Drop for Encoder<'_, M> { 76 | fn drop(&mut self) { 77 | self.hdr.set_control_len(self.len as _); 78 | } 79 | } 80 | 81 | /// # Safety 82 | /// 83 | /// `cmsg` must refer to a native cmsg containing a payload of type `T` 84 | pub(crate) unsafe fn decode(cmsg: &impl CMsgHdr) -> T { 85 | assert!(mem::align_of::() <= mem::align_of::()); 86 | debug_assert_eq!(cmsg.len(), C::cmsg_len(mem::size_of::())); 87 | ptr::read(cmsg.cmsg_data() as *const T) 88 | } 89 | 90 | pub(crate) struct Iter<'a, M: MsgHdr> { 91 | hdr: &'a M, 92 | cmsg: Option<&'a M::ControlMessage>, 93 | } 94 | 95 | impl<'a, M: MsgHdr> Iter<'a, M> { 96 | /// # Safety 97 | /// 98 | /// `hdr` must hold a pointer to memory outliving `'a` which can be soundly read for the 99 | /// lifetime of the constructed `Iter` and contains a buffer of native cmsgs, i.e. is aligned 100 | // for native `cmsghdr`, is fully initialized, and has correct internal links. 101 | pub(crate) unsafe fn new(hdr: &'a M) -> Self { 102 | Self { 103 | hdr, 104 | cmsg: hdr.cmsg_first_hdr().as_ref(), 105 | } 106 | } 107 | } 108 | 109 | impl<'a, M: MsgHdr> Iterator for Iter<'a, M> { 110 | type Item = &'a M::ControlMessage; 111 | 112 | fn next(&mut self) -> Option { 113 | let current = self.cmsg.take()?; 114 | self.cmsg = unsafe { self.hdr.cmsg_nxt_hdr(current).as_ref() }; 115 | 116 | #[cfg(apple_fast)] 117 | { 118 | // On MacOS < 14 CMSG_NXTHDR might continuously return a zeroed cmsg. In 119 | // such case, return `None` instead, thus indicating the end of 120 | // the cmsghdr chain. 121 | if current.len() < mem::size_of::() { 122 | return None; 123 | } 124 | } 125 | 126 | Some(current) 127 | } 128 | } 129 | 130 | // Helper traits for native types for control messages 131 | pub(crate) trait MsgHdr { 132 | type ControlMessage: CMsgHdr; 133 | 134 | fn cmsg_first_hdr(&self) -> *mut Self::ControlMessage; 135 | 136 | fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage; 137 | 138 | /// Sets the number of control messages added to this `struct msghdr`. 139 | /// 140 | /// Note that this is a destructive operation and should only be done as a finalisation 141 | /// step. 142 | fn set_control_len(&mut self, len: usize); 143 | 144 | fn control_len(&self) -> usize; 145 | } 146 | 147 | pub(crate) trait CMsgHdr { 148 | fn cmsg_len(length: usize) -> usize; 149 | 150 | fn cmsg_space(length: usize) -> usize; 151 | 152 | fn cmsg_data(&self) -> *mut c_uchar; 153 | 154 | fn set(&mut self, level: c_int, ty: c_int, len: usize); 155 | 156 | fn len(&self) -> usize; 157 | } 158 | -------------------------------------------------------------------------------- /quinn-udp/src/cmsg/unix.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_int, c_uchar}; 2 | 3 | use super::{CMsgHdr, MsgHdr}; 4 | 5 | #[derive(Copy, Clone)] 6 | #[repr(align(8))] // Conservative bound for align_of 7 | pub(crate) struct Aligned(pub(crate) T); 8 | 9 | /// Helpers for [`libc::msghdr`] 10 | impl MsgHdr for libc::msghdr { 11 | type ControlMessage = libc::cmsghdr; 12 | 13 | fn cmsg_first_hdr(&self) -> *mut Self::ControlMessage { 14 | unsafe { libc::CMSG_FIRSTHDR(self) } 15 | } 16 | 17 | fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage { 18 | unsafe { libc::CMSG_NXTHDR(self, cmsg) } 19 | } 20 | 21 | fn set_control_len(&mut self, len: usize) { 22 | self.msg_controllen = len as _; 23 | if len == 0 { 24 | // netbsd is particular about this being a NULL pointer if there are no control 25 | // messages. 26 | self.msg_control = std::ptr::null_mut(); 27 | } 28 | } 29 | 30 | fn control_len(&self) -> usize { 31 | self.msg_controllen as _ 32 | } 33 | } 34 | 35 | #[cfg(apple_fast)] 36 | impl MsgHdr for crate::imp::msghdr_x { 37 | type ControlMessage = libc::cmsghdr; 38 | 39 | fn cmsg_first_hdr(&self) -> *mut Self::ControlMessage { 40 | let selfp = self as *const _ as *mut libc::msghdr; 41 | unsafe { libc::CMSG_FIRSTHDR(selfp) } 42 | } 43 | 44 | fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage { 45 | let selfp = self as *const _ as *mut libc::msghdr; 46 | unsafe { libc::CMSG_NXTHDR(selfp, cmsg) } 47 | } 48 | 49 | fn set_control_len(&mut self, len: usize) { 50 | self.msg_controllen = len as _; 51 | } 52 | 53 | fn control_len(&self) -> usize { 54 | self.msg_controllen as _ 55 | } 56 | } 57 | 58 | /// Helpers for [`libc::cmsghdr`] 59 | impl CMsgHdr for libc::cmsghdr { 60 | fn cmsg_len(length: usize) -> usize { 61 | unsafe { libc::CMSG_LEN(length as _) as usize } 62 | } 63 | 64 | fn cmsg_space(length: usize) -> usize { 65 | unsafe { libc::CMSG_SPACE(length as _) as usize } 66 | } 67 | 68 | fn cmsg_data(&self) -> *mut c_uchar { 69 | unsafe { libc::CMSG_DATA(self) } 70 | } 71 | 72 | fn set(&mut self, level: c_int, ty: c_int, len: usize) { 73 | self.cmsg_level = level as _; 74 | self.cmsg_type = ty as _; 75 | self.cmsg_len = len as _; 76 | } 77 | 78 | fn len(&self) -> usize { 79 | self.cmsg_len as _ 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /quinn-udp/src/cmsg/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_int, c_uchar}, 3 | mem, ptr, 4 | }; 5 | 6 | use windows_sys::Win32::Networking::WinSock; 7 | 8 | use super::{CMsgHdr, MsgHdr}; 9 | 10 | #[derive(Copy, Clone)] 11 | #[repr(align(8))] // Conservative bound for align_of 12 | pub(crate) struct Aligned(pub(crate) T); 13 | 14 | /// Helpers for [`WinSock::WSAMSG`] 15 | // https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-wsamsg 16 | // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Networking/WinSock/struct.WSAMSG.html 17 | impl MsgHdr for WinSock::WSAMSG { 18 | type ControlMessage = WinSock::CMSGHDR; 19 | 20 | fn cmsg_first_hdr(&self) -> *mut Self::ControlMessage { 21 | if self.Control.len as usize >= mem::size_of::() { 22 | self.Control.buf as *mut WinSock::CMSGHDR 23 | } else { 24 | ptr::null_mut::() 25 | } 26 | } 27 | 28 | fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage { 29 | let next = 30 | (cmsg as *const _ as usize + cmsghdr_align(cmsg.cmsg_len)) as *mut WinSock::CMSGHDR; 31 | let max = self.Control.buf as usize + self.Control.len as usize; 32 | if unsafe { next.offset(1) } as usize > max { 33 | ptr::null_mut() 34 | } else { 35 | next 36 | } 37 | } 38 | 39 | fn set_control_len(&mut self, len: usize) { 40 | self.Control.len = len as _; 41 | } 42 | 43 | fn control_len(&self) -> usize { 44 | self.Control.len as _ 45 | } 46 | } 47 | 48 | /// Helpers for [`WinSock::CMSGHDR`] 49 | // https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-wsacmsghdr 50 | // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Networking/WinSock/struct.CMSGHDR.html 51 | impl CMsgHdr for WinSock::CMSGHDR { 52 | fn cmsg_len(length: usize) -> usize { 53 | cmsgdata_align(mem::size_of::()) + length 54 | } 55 | 56 | fn cmsg_space(length: usize) -> usize { 57 | cmsgdata_align(mem::size_of::() + cmsghdr_align(length)) 58 | } 59 | 60 | fn cmsg_data(&self) -> *mut c_uchar { 61 | (self as *const _ as usize + cmsgdata_align(mem::size_of::())) as *mut c_uchar 62 | } 63 | 64 | fn set(&mut self, level: c_int, ty: c_int, len: usize) { 65 | self.cmsg_level = level as _; 66 | self.cmsg_type = ty as _; 67 | self.cmsg_len = len as _; 68 | } 69 | 70 | fn len(&self) -> usize { 71 | self.cmsg_len as _ 72 | } 73 | } 74 | 75 | // Helpers functions for `WinSock::WSAMSG` and `WinSock::CMSGHDR` are based on C macros from 76 | // https://github.com/microsoft/win32metadata/blob/main/generation/WinSDK/RecompiledIdlHeaders/shared/ws2def.h#L741 77 | fn cmsghdr_align(length: usize) -> usize { 78 | (length + mem::align_of::() - 1) & !(mem::align_of::() - 1) 79 | } 80 | 81 | fn cmsgdata_align(length: usize) -> usize { 82 | (length + mem::align_of::() - 1) & !(mem::align_of::() - 1) 83 | } 84 | -------------------------------------------------------------------------------- /quinn-udp/src/fallback.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, IoSliceMut}, 3 | sync::Mutex, 4 | time::Instant, 5 | }; 6 | 7 | use super::{IO_ERROR_LOG_INTERVAL, RecvMeta, Transmit, UdpSockRef, log_sendmsg_error}; 8 | 9 | /// Fallback UDP socket interface that stubs out all special functionality 10 | /// 11 | /// Used when a better implementation is not available for a particular target, at the cost of 12 | /// reduced performance compared to that enabled by some target-specific interfaces. 13 | #[derive(Debug)] 14 | pub struct UdpSocketState { 15 | last_send_error: Mutex, 16 | } 17 | 18 | impl UdpSocketState { 19 | pub fn new(socket: UdpSockRef<'_>) -> io::Result { 20 | socket.0.set_nonblocking(true)?; 21 | let now = Instant::now(); 22 | Ok(Self { 23 | last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), 24 | }) 25 | } 26 | 27 | /// Sends a [`Transmit`] on the given socket. 28 | /// 29 | /// This function will only ever return errors of kind [`io::ErrorKind::WouldBlock`]. 30 | /// All other errors will be logged and converted to `Ok`. 31 | /// 32 | /// UDP transmission errors are considered non-fatal because higher-level protocols must 33 | /// employ retransmits and timeouts anyway in order to deal with UDP's unreliable nature. 34 | /// Thus, logging is most likely the only thing you can do with these errors. 35 | /// 36 | /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`] 37 | /// instead. 38 | pub fn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { 39 | match send(socket, transmit) { 40 | Ok(()) => Ok(()), 41 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e), 42 | Err(e) => { 43 | log_sendmsg_error(&self.last_send_error, e, transmit); 44 | 45 | Ok(()) 46 | } 47 | } 48 | } 49 | 50 | /// Sends a [`Transmit`] on the given socket without any additional error handling. 51 | pub fn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { 52 | send(socket, transmit) 53 | } 54 | 55 | pub fn recv( 56 | &self, 57 | socket: UdpSockRef<'_>, 58 | bufs: &mut [IoSliceMut<'_>], 59 | meta: &mut [RecvMeta], 60 | ) -> io::Result { 61 | // Safety: both `IoSliceMut` and `MaybeUninitSlice` promise to have the 62 | // same layout, that of `iovec`/`WSABUF`. Furthermore `recv_vectored` 63 | // promises to not write unitialised bytes to the `bufs` and pass it 64 | // directly to the `recvmsg` system call, so this is safe. 65 | let bufs = unsafe { 66 | &mut *(bufs as *mut [IoSliceMut<'_>] as *mut [socket2::MaybeUninitSlice<'_>]) 67 | }; 68 | let (len, _flags, addr) = socket.0.recv_from_vectored(bufs)?; 69 | meta[0] = RecvMeta { 70 | len, 71 | stride: len, 72 | addr: addr.as_socket().unwrap(), 73 | ecn: None, 74 | dst_ip: None, 75 | }; 76 | Ok(1) 77 | } 78 | 79 | #[inline] 80 | pub fn max_gso_segments(&self) -> usize { 81 | 1 82 | } 83 | 84 | #[inline] 85 | pub fn gro_segments(&self) -> usize { 86 | 1 87 | } 88 | 89 | /// Resize the send buffer of `socket` to `bytes` 90 | #[inline] 91 | pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { 92 | socket.0.set_send_buffer_size(bytes) 93 | } 94 | 95 | /// Resize the receive buffer of `socket` to `bytes` 96 | #[inline] 97 | pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { 98 | socket.0.set_recv_buffer_size(bytes) 99 | } 100 | 101 | /// Get the size of the `socket` send buffer 102 | #[inline] 103 | pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result { 104 | socket.0.send_buffer_size() 105 | } 106 | 107 | /// Get the size of the `socket` receive buffer 108 | #[inline] 109 | pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result { 110 | socket.0.recv_buffer_size() 111 | } 112 | 113 | #[inline] 114 | pub fn may_fragment(&self) -> bool { 115 | true 116 | } 117 | } 118 | 119 | fn send(socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { 120 | socket.0.send_to( 121 | transmit.contents, 122 | &socket2::SockAddr::from(transmit.destination), 123 | ) 124 | } 125 | 126 | pub(crate) const BATCH_SIZE: usize = 1; 127 | -------------------------------------------------------------------------------- /quinn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quinn" 3 | version = "0.11.8" 4 | license.workspace = true 5 | repository.workspace = true 6 | description = "Versatile QUIC transport protocol implementation" 7 | readme = "../README.md" 8 | keywords.workspace = true 9 | categories.workspace = true 10 | workspace = ".." 11 | edition.workspace = true 12 | rust-version.workspace = true 13 | 14 | 15 | [features] 16 | # NOTE: Please keep this in sync with the feature list in `.github/workflows/codecov.yml`, see 17 | # comment in that file for more information. 18 | default = ["log", "platform-verifier", "runtime-tokio", "rustls-ring", "bloom"] 19 | # Enables `Endpoint::client` and `Endpoint::server` conveniences 20 | aws-lc-rs = ["proto/aws-lc-rs"] 21 | aws-lc-rs-fips = ["proto/aws-lc-rs-fips"] 22 | # Enables BloomTokenLog, and uses it by default 23 | bloom = ["proto/bloom"] 24 | # Records how long locks are held, and warns if they are held >= 1ms 25 | lock_tracking = [] 26 | # Provides `ClientConfig::with_platform_verifier()` convenience method 27 | platform-verifier = ["proto/platform-verifier"] 28 | # For backwards compatibility, `rustls` forwards to `rustls-ring` 29 | rustls = ["rustls-ring"] 30 | # Enable rustls with the `aws-lc-rs` crypto provider 31 | rustls-aws-lc-rs = ["dep:rustls", "aws-lc-rs", "proto/rustls-aws-lc-rs", "proto/aws-lc-rs"] 32 | rustls-aws-lc-rs-fips = ["dep:rustls", "aws-lc-rs-fips", "proto/rustls-aws-lc-rs-fips", "proto/aws-lc-rs-fips"] 33 | # Enable rustls with the `ring` crypto provider 34 | rustls-ring = ["dep:rustls", "ring", "proto/rustls-ring", "proto/ring"] 35 | # Enable the `ring` crypto provider. 36 | # Outside wasm*-unknown-unknown targets, this enables `Endpoint::client` and `Endpoint::server` conveniences. 37 | ring = ["proto/ring"] 38 | runtime-tokio = ["tokio/time", "tokio/rt", "tokio/net"] 39 | runtime-async-std = ["async-io", "async-std"] 40 | runtime-smol = ["async-io", "smol"] 41 | 42 | # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. 43 | log = ["tracing/log", "proto/log", "udp/log"] 44 | # Enable rustls logging 45 | rustls-log = ["rustls?/logging"] 46 | 47 | [dependencies] 48 | async-io = { workspace = true, optional = true } 49 | async-std = { workspace = true, optional = true } 50 | bytes = { workspace = true } 51 | # Enables futures::io::{AsyncRead, AsyncWrite} support for streams 52 | futures-io = { workspace = true, optional = true } 53 | rustc-hash = { workspace = true } 54 | pin-project-lite = { workspace = true } 55 | proto = { package = "quinn-proto", path = "../quinn-proto", version = "0.11.12", default-features = false } 56 | rustls = { workspace = true, optional = true } 57 | smol = { workspace = true, optional = true } 58 | thiserror = { workspace = true } 59 | tracing = { workspace = true } 60 | tokio = { workspace = true } 61 | udp = { package = "quinn-udp", path = "../quinn-udp", version = "0.5", default-features = false, features = ["tracing"] } 62 | 63 | [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] 64 | socket2 = { workspace = true } 65 | 66 | [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 67 | web-time = { workspace = true } 68 | 69 | [dev-dependencies] 70 | anyhow = { workspace = true } 71 | crc = { workspace = true } 72 | bencher = { workspace = true } 73 | directories-next = { workspace = true } 74 | rand = { workspace = true } 75 | rcgen = { workspace = true } 76 | rustls-pemfile = { workspace = true } 77 | clap = { workspace = true } 78 | tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time", "macros"] } 79 | tracing-subscriber = { workspace = true } 80 | tracing-futures = { workspace = true } 81 | url = { workspace = true } 82 | 83 | [build-dependencies] 84 | cfg_aliases = { workspace = true } 85 | 86 | [[example]] 87 | name = "server" 88 | required-features = ["rustls-ring"] 89 | 90 | [[example]] 91 | name = "client" 92 | required-features = ["rustls-ring"] 93 | 94 | [[example]] 95 | name = "insecure_connection" 96 | required-features = ["rustls-ring"] 97 | 98 | [[example]] 99 | name = "single_socket" 100 | required-features = ["rustls-ring"] 101 | 102 | [[example]] 103 | name = "connection" 104 | required-features = ["rustls-ring"] 105 | 106 | [[bench]] 107 | name = "bench" 108 | harness = false 109 | required-features = ["rustls-ring"] 110 | 111 | [package.metadata.docs.rs] 112 | # all non-default features except fips (cannot build on docs.rs environment) 113 | features = ["lock_tracking", "rustls-aws-lc-rs", "rustls-ring", "runtime-tokio", "runtime-async-std", "runtime-smol", "log", "rustls-log"] 114 | -------------------------------------------------------------------------------- /quinn/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /quinn/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /quinn/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket}, 3 | sync::Arc, 4 | thread, 5 | }; 6 | 7 | use bencher::{Bencher, benchmark_group, benchmark_main}; 8 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 9 | use tokio::runtime::{Builder, Runtime}; 10 | use tracing::error_span; 11 | use tracing_futures::Instrument as _; 12 | 13 | use quinn::{Endpoint, TokioRuntime}; 14 | 15 | benchmark_group!( 16 | benches, 17 | large_data_1_stream, 18 | large_data_10_streams, 19 | small_data_1_stream, 20 | small_data_100_streams 21 | ); 22 | benchmark_main!(benches); 23 | 24 | fn large_data_1_stream(bench: &mut Bencher) { 25 | send_data(bench, LARGE_DATA, 1); 26 | } 27 | 28 | fn large_data_10_streams(bench: &mut Bencher) { 29 | send_data(bench, LARGE_DATA, 10); 30 | } 31 | 32 | fn small_data_1_stream(bench: &mut Bencher) { 33 | send_data(bench, SMALL_DATA, 1); 34 | } 35 | 36 | fn small_data_100_streams(bench: &mut Bencher) { 37 | send_data(bench, SMALL_DATA, 100); 38 | } 39 | 40 | fn send_data(bench: &mut Bencher, data: &'static [u8], concurrent_streams: usize) { 41 | let _ = tracing_subscriber::fmt::try_init(); 42 | 43 | let ctx = Context::new(); 44 | let (addr, thread) = ctx.spawn_server(); 45 | let (endpoint, client, runtime) = ctx.make_client(addr); 46 | let client = Arc::new(client); 47 | 48 | bench.bytes = (data.len() as u64) * (concurrent_streams as u64); 49 | bench.iter(|| { 50 | let mut handles = Vec::new(); 51 | 52 | for _ in 0..concurrent_streams { 53 | let client = client.clone(); 54 | handles.push(runtime.spawn(async move { 55 | let mut stream = client.open_uni().await.unwrap(); 56 | stream.write_all(data).await.unwrap(); 57 | stream.finish().unwrap(); 58 | // Wait for stream to close 59 | _ = stream.stopped().await; 60 | })); 61 | } 62 | 63 | runtime.block_on(async { 64 | for handle in handles { 65 | handle.await.unwrap(); 66 | } 67 | }); 68 | }); 69 | drop(client); 70 | runtime.block_on(endpoint.wait_idle()); 71 | thread.join().unwrap() 72 | } 73 | 74 | struct Context { 75 | server_config: quinn::ServerConfig, 76 | client_config: quinn::ClientConfig, 77 | } 78 | 79 | impl Context { 80 | fn new() -> Self { 81 | let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 82 | let key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); 83 | let cert = CertificateDer::from(cert.cert); 84 | 85 | let mut server_config = 86 | quinn::ServerConfig::with_single_cert(vec![cert.clone()], key.into()).unwrap(); 87 | let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); 88 | transport_config.max_concurrent_uni_streams(1024_u16.into()); 89 | 90 | let mut roots = rustls::RootCertStore::empty(); 91 | roots.add(cert).unwrap(); 92 | 93 | Self { 94 | server_config, 95 | client_config: quinn::ClientConfig::with_root_certificates(Arc::new(roots)).unwrap(), 96 | } 97 | } 98 | 99 | pub fn spawn_server(&self) -> (SocketAddr, thread::JoinHandle<()>) { 100 | let sock = UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0)).unwrap(); 101 | let addr = sock.local_addr().unwrap(); 102 | let config = self.server_config.clone(); 103 | let handle = thread::spawn(move || { 104 | let runtime = rt(); 105 | let endpoint = { 106 | let _guard = runtime.enter(); 107 | Endpoint::new( 108 | Default::default(), 109 | Some(config), 110 | sock, 111 | Arc::new(TokioRuntime), 112 | ) 113 | .unwrap() 114 | }; 115 | let handle = runtime.spawn( 116 | async move { 117 | let connection = endpoint 118 | .accept() 119 | .await 120 | .expect("accept") 121 | .await 122 | .expect("connect"); 123 | 124 | while let Ok(mut stream) = connection.accept_uni().await { 125 | tokio::spawn(async move { 126 | while stream 127 | .read_chunk(usize::MAX, false) 128 | .await 129 | .unwrap() 130 | .is_some() 131 | {} 132 | }); 133 | } 134 | } 135 | .instrument(error_span!("server")), 136 | ); 137 | runtime.block_on(handle).unwrap(); 138 | }); 139 | (addr, handle) 140 | } 141 | 142 | pub fn make_client( 143 | &self, 144 | server_addr: SocketAddr, 145 | ) -> (quinn::Endpoint, quinn::Connection, Runtime) { 146 | let runtime = rt(); 147 | let endpoint = { 148 | let _guard = runtime.enter(); 149 | Endpoint::client(SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0)).unwrap() 150 | }; 151 | let connection = runtime 152 | .block_on(async { 153 | endpoint 154 | .connect_with(self.client_config.clone(), server_addr, "localhost") 155 | .unwrap() 156 | .instrument(error_span!("client")) 157 | .await 158 | }) 159 | .unwrap(); 160 | (endpoint, connection, runtime) 161 | } 162 | } 163 | 164 | fn rt() -> Runtime { 165 | Builder::new_current_thread().enable_all().build().unwrap() 166 | } 167 | 168 | const LARGE_DATA: &[u8] = &[0xAB; 1024 * 1024]; 169 | 170 | const SMALL_DATA: &[u8] = &[0xAB; 1]; 171 | -------------------------------------------------------------------------------- /quinn/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | // Setup cfg aliases 5 | cfg_aliases! { 6 | // Convenience aliases 7 | wasm_browser: { all(target_family = "wasm", target_os = "unknown") }, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /quinn/examples/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP/0.9 File Serving Example 2 | 3 | The `server` and `client` examples demonstrate fetching files using a HTTP-like toy protocol. 4 | 5 | 1. Server (`server.rs`) 6 | 7 | The server listens for any client requesting a file. 8 | If the file path is valid and allowed, it returns the contents. 9 | 10 | Open up a terminal and execute: 11 | 12 | ```text 13 | $ cargo run --example server ./ 14 | ``` 15 | 16 | 2. Client (`client.rs`) 17 | 18 | The client requests a file and prints it to the console. 19 | If the file is on the server, it will receive the response. 20 | 21 | In a new terminal execute: 22 | 23 | ```test 24 | $ cargo run --example client https://localhost:4433/Cargo.toml 25 | ``` 26 | 27 | where `Cargo.toml` is any file in the directory passed to the server. 28 | 29 | **Result:** 30 | 31 | The output will be the contents of this README. 32 | 33 | **Troubleshooting:** 34 | 35 | If the client times out with no activity on the server, try forcing the server to run on IPv4 by 36 | running it with `cargo run --example server -- ./ --listen 127.0.0.1:4433`. The server listens on 37 | IPv6 by default, `localhost` tends to resolve to IPv4, and support for accepting IPv4 packets on 38 | IPv6 sockets varies between platforms. 39 | 40 | If the client prints `failed to process request: failed reading file`, the request was processed 41 | successfully but the path segment of the URL did not correspond to a file in the directory being 42 | served. 43 | 44 | ## Minimal Example 45 | The `connection.rs` example intends to use the smallest amount of code to make a simple QUIC connection. 46 | The server issues it's own certificate and passes it to the client to trust. 47 | 48 | ```text 49 | $ cargo run --example connection 50 | ``` 51 | 52 | This example will make a QUIC connection on localhost, and you should see output like: 53 | 54 | ```text 55 | [client] connected: addr=127.0.0.1:5000 56 | [server] connection accepted: addr=127.0.0.1:53712 57 | ``` 58 | 59 | ## Insecure Connection Example 60 | 61 | The `insecure_connection.rs` example demonstrates how to make a QUIC connection that ignores the server certificate. 62 | 63 | ```text 64 | $ cargo run --example insecure_connection --features="rustls/dangerous_configuration" 65 | ``` 66 | 67 | ## Single Socket Example 68 | 69 | You can have multiple QUIC connections over a single UDP socket. This is especially 70 | useful, if you are building a peer-to-peer system where you potentially need to communicate with 71 | thousands of peers or if you have a 72 | [hole punched](https://en.wikipedia.org/wiki/UDP_hole_punching) UDP socket. 73 | Additionally, QUIC servers and clients can both operate on the same UDP socket. 74 | This example demonstrates how to make multiple outgoing connections on a single UDP socket. 75 | 76 | ```text 77 | $ cargo run --example single_socket 78 | ``` 79 | 80 | The expected output should be something like: 81 | 82 | ```text 83 | [client] connected: addr=127.0.0.1:5000 84 | [server] incoming connection: addr=127.0.0.1:48930 85 | [client] connected: addr=127.0.0.1:5001 86 | [client] connected: addr=127.0.0.1:5002 87 | [server] incoming connection: addr=127.0.0.1:48930 88 | [server] incoming connection: addr=127.0.0.1:48930 89 | ``` 90 | 91 | Notice how the server sees multiple incoming connections with different IDs coming from the same 92 | endpoint. 93 | -------------------------------------------------------------------------------- /quinn/examples/client.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates an HTTP client that requests files from a server. 2 | //! 3 | //! Checkout the `README.md` for guidance. 4 | 5 | use std::{ 6 | fs, 7 | io::{self, Write}, 8 | net::{SocketAddr, ToSocketAddrs}, 9 | path::PathBuf, 10 | sync::Arc, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use anyhow::{Result, anyhow}; 15 | use clap::Parser; 16 | use proto::crypto::rustls::QuicClientConfig; 17 | use rustls::pki_types::CertificateDer; 18 | use tracing::{error, info}; 19 | use url::Url; 20 | 21 | mod common; 22 | 23 | /// HTTP/0.9 over QUIC client 24 | #[derive(Parser, Debug)] 25 | #[clap(name = "client")] 26 | struct Opt { 27 | /// Perform NSS-compatible TLS key logging to the file specified in `SSLKEYLOGFILE`. 28 | #[clap(long = "keylog")] 29 | keylog: bool, 30 | 31 | url: Url, 32 | 33 | /// Override hostname used for certificate verification 34 | #[clap(long = "host")] 35 | host: Option, 36 | 37 | /// Custom certificate authority to trust, in DER format 38 | #[clap(long = "ca")] 39 | ca: Option, 40 | 41 | /// Simulate NAT rebinding after connecting 42 | #[clap(long = "rebind")] 43 | rebind: bool, 44 | 45 | /// Address to bind on 46 | #[clap(long = "bind", default_value = "[::]:0")] 47 | bind: SocketAddr, 48 | } 49 | 50 | fn main() { 51 | tracing::subscriber::set_global_default( 52 | tracing_subscriber::FmtSubscriber::builder() 53 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 54 | .finish(), 55 | ) 56 | .unwrap(); 57 | let opt = Opt::parse(); 58 | let code = { 59 | if let Err(e) = run(opt) { 60 | eprintln!("ERROR: {e}"); 61 | 1 62 | } else { 63 | 0 64 | } 65 | }; 66 | ::std::process::exit(code); 67 | } 68 | 69 | #[tokio::main] 70 | async fn run(options: Opt) -> Result<()> { 71 | let url = options.url; 72 | let url_host = strip_ipv6_brackets(url.host_str().unwrap()); 73 | let remote = (url_host, url.port().unwrap_or(4433)) 74 | .to_socket_addrs()? 75 | .next() 76 | .ok_or_else(|| anyhow!("couldn't resolve to an address"))?; 77 | 78 | let mut roots = rustls::RootCertStore::empty(); 79 | if let Some(ca_path) = options.ca { 80 | roots.add(CertificateDer::from(fs::read(ca_path)?))?; 81 | } else { 82 | let dirs = directories_next::ProjectDirs::from("org", "quinn", "quinn-examples").unwrap(); 83 | match fs::read(dirs.data_local_dir().join("cert.der")) { 84 | Ok(cert) => { 85 | roots.add(CertificateDer::from(cert))?; 86 | } 87 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { 88 | info!("local server certificate not found"); 89 | } 90 | Err(e) => { 91 | error!("failed to open local server certificate: {}", e); 92 | } 93 | } 94 | } 95 | let mut client_crypto = rustls::ClientConfig::builder() 96 | .with_root_certificates(roots) 97 | .with_no_client_auth(); 98 | 99 | client_crypto.alpn_protocols = common::ALPN_QUIC_HTTP.iter().map(|&x| x.into()).collect(); 100 | if options.keylog { 101 | client_crypto.key_log = Arc::new(rustls::KeyLogFile::new()); 102 | } 103 | 104 | let client_config = 105 | quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?)); 106 | let mut endpoint = quinn::Endpoint::client(options.bind)?; 107 | endpoint.set_default_client_config(client_config); 108 | 109 | let request = format!("GET {}\r\n", url.path()); 110 | let start = Instant::now(); 111 | let rebind = options.rebind; 112 | let host = options.host.as_deref().unwrap_or(url_host); 113 | 114 | eprintln!("connecting to {host} at {remote}"); 115 | let conn = endpoint 116 | .connect(remote, host)? 117 | .await 118 | .map_err(|e| anyhow!("failed to connect: {}", e))?; 119 | eprintln!("connected at {:?}", start.elapsed()); 120 | let (mut send, mut recv) = conn 121 | .open_bi() 122 | .await 123 | .map_err(|e| anyhow!("failed to open stream: {}", e))?; 124 | if rebind { 125 | let socket = std::net::UdpSocket::bind("[::]:0").unwrap(); 126 | let addr = socket.local_addr().unwrap(); 127 | eprintln!("rebinding to {addr}"); 128 | endpoint.rebind(socket).expect("rebind failed"); 129 | } 130 | 131 | send.write_all(request.as_bytes()) 132 | .await 133 | .map_err(|e| anyhow!("failed to send request: {}", e))?; 134 | send.finish().unwrap(); 135 | let response_start = Instant::now(); 136 | eprintln!("request sent at {:?}", response_start - start); 137 | let resp = recv 138 | .read_to_end(usize::MAX) 139 | .await 140 | .map_err(|e| anyhow!("failed to read response: {}", e))?; 141 | let duration = response_start.elapsed(); 142 | eprintln!( 143 | "response received in {:?} - {} KiB/s", 144 | duration, 145 | resp.len() as f32 / (duration_secs(&duration) * 1024.0) 146 | ); 147 | io::stdout().write_all(&resp).unwrap(); 148 | io::stdout().flush().unwrap(); 149 | conn.close(0u32.into(), b"done"); 150 | 151 | // Give the server a fair chance to receive the close packet 152 | endpoint.wait_idle().await; 153 | 154 | Ok(()) 155 | } 156 | 157 | fn strip_ipv6_brackets(host: &str) -> &str { 158 | // An ipv6 url looks like eg https://[::1]:4433/Cargo.toml, wherein the host [::1] is the 159 | // ipv6 address ::1 wrapped in brackets, per RFC 2732. This strips those. 160 | if host.starts_with('[') && host.ends_with(']') { 161 | &host[1..host.len() - 1] 162 | } else { 163 | host 164 | } 165 | } 166 | 167 | fn duration_secs(x: &Duration) -> f32 { 168 | x.as_secs() as f32 + x.subsec_nanos() as f32 * 1e-9 169 | } 170 | -------------------------------------------------------------------------------- /quinn/examples/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] 2 | //! Commonly used code in most examples. 3 | 4 | use quinn::{ClientConfig, Endpoint, ServerConfig}; 5 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 6 | 7 | use std::{error::Error, net::SocketAddr, sync::Arc}; 8 | 9 | /// Constructs a QUIC endpoint configured for use a client only. 10 | /// 11 | /// ## Args 12 | /// 13 | /// - server_certs: list of trusted certificates. 14 | #[allow(unused)] 15 | pub fn make_client_endpoint( 16 | bind_addr: SocketAddr, 17 | server_certs: &[&[u8]], 18 | ) -> Result> { 19 | let client_cfg = configure_client(server_certs)?; 20 | let mut endpoint = Endpoint::client(bind_addr)?; 21 | endpoint.set_default_client_config(client_cfg); 22 | Ok(endpoint) 23 | } 24 | 25 | /// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address 26 | /// and port. 27 | /// 28 | /// ## Returns 29 | /// 30 | /// - a stream of incoming QUIC connections 31 | /// - server certificate serialized into DER format 32 | #[allow(unused)] 33 | pub fn make_server_endpoint( 34 | bind_addr: SocketAddr, 35 | ) -> Result<(Endpoint, CertificateDer<'static>), Box> { 36 | let (server_config, server_cert) = configure_server()?; 37 | let endpoint = Endpoint::server(server_config, bind_addr)?; 38 | Ok((endpoint, server_cert)) 39 | } 40 | 41 | /// Builds default quinn client config and trusts given certificates. 42 | /// 43 | /// ## Args 44 | /// 45 | /// - server_certs: a list of trusted certificates in DER format. 46 | fn configure_client( 47 | server_certs: &[&[u8]], 48 | ) -> Result> { 49 | let mut certs = rustls::RootCertStore::empty(); 50 | for cert in server_certs { 51 | certs.add(CertificateDer::from(*cert))?; 52 | } 53 | 54 | Ok(ClientConfig::with_root_certificates(Arc::new(certs))?) 55 | } 56 | 57 | /// Returns default server configuration along with its certificate. 58 | fn configure_server() 59 | -> Result<(ServerConfig, CertificateDer<'static>), Box> { 60 | let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 61 | let cert_der = CertificateDer::from(cert.cert); 62 | let priv_key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); 63 | 64 | let mut server_config = 65 | ServerConfig::with_single_cert(vec![cert_der.clone()], priv_key.into())?; 66 | let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); 67 | transport_config.max_concurrent_uni_streams(0_u8.into()); 68 | 69 | Ok((server_config, cert_der)) 70 | } 71 | 72 | #[allow(unused)] 73 | pub const ALPN_QUIC_HTTP: &[&[u8]] = &[b"hq-29"]; 74 | -------------------------------------------------------------------------------- /quinn/examples/connection.rs: -------------------------------------------------------------------------------- 1 | //! This example intends to use the smallest amount of code to make a simple QUIC connection. 2 | //! 3 | //! Checkout the `README.md` for guidance. 4 | 5 | use std::{ 6 | error::Error, 7 | net::{IpAddr, Ipv4Addr, SocketAddr}, 8 | }; 9 | 10 | mod common; 11 | use common::{make_client_endpoint, make_server_endpoint}; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<(), Box> { 15 | let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000); 16 | let (endpoint, server_cert) = make_server_endpoint(server_addr)?; 17 | // accept a single connection 18 | let endpoint2 = endpoint.clone(); 19 | tokio::spawn(async move { 20 | let incoming_conn = endpoint2.accept().await.unwrap(); 21 | let conn = incoming_conn.await.unwrap(); 22 | println!( 23 | "[server] connection accepted: addr={}", 24 | conn.remote_address() 25 | ); 26 | // Dropping all handles associated with a connection implicitly closes it 27 | }); 28 | 29 | let endpoint = make_client_endpoint("0.0.0.0:0".parse().unwrap(), &[&server_cert])?; 30 | // connect to server 31 | let connection = endpoint 32 | .connect(server_addr, "localhost") 33 | .unwrap() 34 | .await 35 | .unwrap(); 36 | println!("[client] connected: addr={}", connection.remote_address()); 37 | 38 | // Waiting for a stream will complete with an error when the server closes the connection 39 | let _ = connection.accept_uni().await; 40 | 41 | // Make sure the server has a chance to clean up 42 | endpoint.wait_idle().await; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /quinn/examples/insecure_connection.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to make a QUIC connection that ignores the server certificate. 2 | //! 3 | //! Checkout the `README.md` for guidance. 4 | 5 | use std::{ 6 | error::Error, 7 | net::{IpAddr, Ipv4Addr, SocketAddr}, 8 | sync::Arc, 9 | }; 10 | 11 | use proto::crypto::rustls::QuicClientConfig; 12 | use quinn::{ClientConfig, Endpoint}; 13 | use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; 14 | 15 | mod common; 16 | use common::make_server_endpoint; 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | // server and client are running on the same thread asynchronously 21 | let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080); 22 | tokio::spawn(run_server(addr)); 23 | run_client(addr).await?; 24 | Ok(()) 25 | } 26 | 27 | /// Runs a QUIC server bound to given address. 28 | async fn run_server(addr: SocketAddr) { 29 | let (endpoint, _server_cert) = make_server_endpoint(addr).unwrap(); 30 | // accept a single connection 31 | let incoming_conn = endpoint.accept().await.unwrap(); 32 | let conn = incoming_conn.await.unwrap(); 33 | println!( 34 | "[server] connection accepted: addr={}", 35 | conn.remote_address() 36 | ); 37 | } 38 | 39 | async fn run_client(server_addr: SocketAddr) -> Result<(), Box> { 40 | let mut endpoint = Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))?; 41 | 42 | endpoint.set_default_client_config(ClientConfig::new(Arc::new(QuicClientConfig::try_from( 43 | rustls::ClientConfig::builder() 44 | .dangerous() 45 | .with_custom_certificate_verifier(SkipServerVerification::new()) 46 | .with_no_client_auth(), 47 | )?))); 48 | 49 | // connect to server 50 | let connection = endpoint 51 | .connect(server_addr, "localhost") 52 | .unwrap() 53 | .await 54 | .unwrap(); 55 | println!("[client] connected: addr={}", connection.remote_address()); 56 | // Dropping handles allows the corresponding objects to automatically shut down 57 | drop(connection); 58 | // Make sure the server has a chance to clean up 59 | endpoint.wait_idle().await; 60 | 61 | Ok(()) 62 | } 63 | 64 | /// Dummy certificate verifier that treats any certificate as valid. 65 | /// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing. 66 | #[derive(Debug)] 67 | struct SkipServerVerification(Arc); 68 | 69 | impl SkipServerVerification { 70 | fn new() -> Arc { 71 | Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider()))) 72 | } 73 | } 74 | 75 | impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { 76 | fn verify_server_cert( 77 | &self, 78 | _end_entity: &CertificateDer<'_>, 79 | _intermediates: &[CertificateDer<'_>], 80 | _server_name: &ServerName<'_>, 81 | _ocsp: &[u8], 82 | _now: UnixTime, 83 | ) -> Result { 84 | Ok(rustls::client::danger::ServerCertVerified::assertion()) 85 | } 86 | 87 | fn verify_tls12_signature( 88 | &self, 89 | message: &[u8], 90 | cert: &CertificateDer<'_>, 91 | dss: &rustls::DigitallySignedStruct, 92 | ) -> Result { 93 | rustls::crypto::verify_tls12_signature( 94 | message, 95 | cert, 96 | dss, 97 | &self.0.signature_verification_algorithms, 98 | ) 99 | } 100 | 101 | fn verify_tls13_signature( 102 | &self, 103 | message: &[u8], 104 | cert: &CertificateDer<'_>, 105 | dss: &rustls::DigitallySignedStruct, 106 | ) -> Result { 107 | rustls::crypto::verify_tls13_signature( 108 | message, 109 | cert, 110 | dss, 111 | &self.0.signature_verification_algorithms, 112 | ) 113 | } 114 | 115 | fn supported_verify_schemes(&self) -> Vec { 116 | self.0.signature_verification_algorithms.supported_schemes() 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /quinn/examples/single_socket.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to make multiple outgoing connections on a single UDP socket. 2 | //! 3 | //! Checkout the `README.md` for guidance. 4 | 5 | use std::{ 6 | error::Error, 7 | net::{IpAddr, Ipv4Addr, SocketAddr}, 8 | }; 9 | 10 | use quinn::Endpoint; 11 | 12 | mod common; 13 | use common::{make_client_endpoint, make_server_endpoint}; 14 | use rustls::pki_types::CertificateDer; 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), Box> { 18 | let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000); 19 | let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001); 20 | let addr3 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5002); 21 | let server1_cert = run_server(addr1)?; 22 | let server2_cert = run_server(addr2)?; 23 | let server3_cert = run_server(addr3)?; 24 | 25 | let client = make_client_endpoint( 26 | SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), 27 | &[&server1_cert, &server2_cert, &server3_cert], 28 | )?; 29 | 30 | // connect to multiple endpoints using the same socket/endpoint 31 | tokio::join!( 32 | run_client(&client, addr1), 33 | run_client(&client, addr2), 34 | run_client(&client, addr3), 35 | ); 36 | 37 | // Make sure the server has a chance to clean up 38 | client.wait_idle().await; 39 | 40 | Ok(()) 41 | } 42 | 43 | /// Runs a QUIC server bound to given address and returns server certificate. 44 | fn run_server( 45 | addr: SocketAddr, 46 | ) -> Result, Box> { 47 | let (endpoint, server_cert) = make_server_endpoint(addr)?; 48 | // accept a single connection 49 | tokio::spawn(async move { 50 | let connection = endpoint.accept().await.unwrap().await.unwrap(); 51 | println!( 52 | "[server] incoming connection: addr={}", 53 | connection.remote_address() 54 | ); 55 | }); 56 | 57 | Ok(server_cert) 58 | } 59 | 60 | /// Attempt QUIC connection with the given server address. 61 | async fn run_client(endpoint: &Endpoint, server_addr: SocketAddr) { 62 | let connect = endpoint.connect(server_addr, "localhost").unwrap(); 63 | let connection = connect.await.unwrap(); 64 | println!("[client] connected: addr={}", connection.remote_address()); 65 | } 66 | -------------------------------------------------------------------------------- /quinn/src/incoming.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::{Future, IntoFuture}, 3 | net::{IpAddr, SocketAddr}, 4 | pin::Pin, 5 | sync::Arc, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use proto::{ConnectionError, ConnectionId, ServerConfig}; 10 | use thiserror::Error; 11 | 12 | use crate::{ 13 | connection::{Connecting, Connection}, 14 | endpoint::EndpointRef, 15 | }; 16 | 17 | /// An incoming connection for which the server has not yet begun its part of the handshake 18 | #[derive(Debug)] 19 | pub struct Incoming(Option); 20 | 21 | impl Incoming { 22 | pub(crate) fn new(inner: proto::Incoming, endpoint: EndpointRef) -> Self { 23 | Self(Some(State { inner, endpoint })) 24 | } 25 | 26 | /// Attempt to accept this incoming connection (an error may still occur) 27 | pub fn accept(mut self) -> Result { 28 | let state = self.0.take().unwrap(); 29 | state.endpoint.accept(state.inner, None) 30 | } 31 | 32 | /// Accept this incoming connection using a custom configuration 33 | /// 34 | /// See [`accept()`][Incoming::accept] for more details. 35 | pub fn accept_with( 36 | mut self, 37 | server_config: Arc, 38 | ) -> Result { 39 | let state = self.0.take().unwrap(); 40 | state.endpoint.accept(state.inner, Some(server_config)) 41 | } 42 | 43 | /// Reject this incoming connection attempt 44 | pub fn refuse(mut self) { 45 | let state = self.0.take().unwrap(); 46 | state.endpoint.refuse(state.inner); 47 | } 48 | 49 | /// Respond with a retry packet, requiring the client to retry with address validation 50 | /// 51 | /// Errors if `may_retry()` is false. 52 | pub fn retry(mut self) -> Result<(), RetryError> { 53 | let state = self.0.take().unwrap(); 54 | state.endpoint.retry(state.inner).map_err(|e| { 55 | RetryError(Box::new(Self(Some(State { 56 | inner: e.into_incoming(), 57 | endpoint: state.endpoint, 58 | })))) 59 | }) 60 | } 61 | 62 | /// Ignore this incoming connection attempt, not sending any packet in response 63 | pub fn ignore(mut self) { 64 | let state = self.0.take().unwrap(); 65 | state.endpoint.ignore(state.inner); 66 | } 67 | 68 | /// The local IP address which was used when the peer established the connection 69 | pub fn local_ip(&self) -> Option { 70 | self.0.as_ref().unwrap().inner.local_ip() 71 | } 72 | 73 | /// The peer's UDP address 74 | pub fn remote_address(&self) -> SocketAddr { 75 | self.0.as_ref().unwrap().inner.remote_address() 76 | } 77 | 78 | /// Whether the socket address that is initiating this connection has been validated 79 | /// 80 | /// This means that the sender of the initial packet has proved that they can receive traffic 81 | /// sent to `self.remote_address()`. 82 | /// 83 | /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. 84 | /// The inverse is not guaranteed. 85 | pub fn remote_address_validated(&self) -> bool { 86 | self.0.as_ref().unwrap().inner.remote_address_validated() 87 | } 88 | 89 | /// Whether it is legal to respond with a retry packet 90 | /// 91 | /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. 92 | /// The inverse is not guaranteed. 93 | pub fn may_retry(&self) -> bool { 94 | self.0.as_ref().unwrap().inner.may_retry() 95 | } 96 | 97 | /// The original destination CID when initiating the connection 98 | pub fn orig_dst_cid(&self) -> ConnectionId { 99 | *self.0.as_ref().unwrap().inner.orig_dst_cid() 100 | } 101 | } 102 | 103 | impl Drop for Incoming { 104 | fn drop(&mut self) { 105 | // Implicit reject, similar to Connection's implicit close 106 | if let Some(state) = self.0.take() { 107 | state.endpoint.refuse(state.inner); 108 | } 109 | } 110 | } 111 | 112 | #[derive(Debug)] 113 | struct State { 114 | inner: proto::Incoming, 115 | endpoint: EndpointRef, 116 | } 117 | 118 | /// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry 119 | #[derive(Debug, Error)] 120 | #[error("retry() with validated Incoming")] 121 | pub struct RetryError(Box); 122 | 123 | impl RetryError { 124 | /// Get the [`Incoming`] 125 | pub fn into_incoming(self) -> Incoming { 126 | *self.0 127 | } 128 | } 129 | 130 | /// Basic adapter to let [`Incoming`] be `await`-ed like a [`Connecting`] 131 | #[derive(Debug)] 132 | pub struct IncomingFuture(Result); 133 | 134 | impl Future for IncomingFuture { 135 | type Output = Result; 136 | 137 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 138 | match &mut self.0 { 139 | Ok(ref mut connecting) => Pin::new(connecting).poll(cx), 140 | Err(e) => Poll::Ready(Err(e.clone())), 141 | } 142 | } 143 | } 144 | 145 | impl IntoFuture for Incoming { 146 | type Output = Result; 147 | type IntoFuture = IncomingFuture; 148 | 149 | fn into_future(self) -> Self::IntoFuture { 150 | IncomingFuture(self.accept()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /quinn/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! QUIC transport protocol implementation 2 | //! 3 | //! [QUIC](https://en.wikipedia.org/wiki/QUIC) is a modern transport protocol addressing 4 | //! shortcomings of TCP, such as head-of-line blocking, poor security, slow handshakes, and 5 | //! inefficient congestion control. This crate provides a portable userspace implementation. It 6 | //! builds on top of quinn-proto, which implements protocol logic independent of any particular 7 | //! runtime. 8 | //! 9 | //! The entry point of this crate is the [`Endpoint`]. 10 | //! 11 | //! # About QUIC 12 | //! 13 | //! A QUIC connection is an association between two endpoints. The endpoint which initiates the 14 | //! connection is termed the client, and the endpoint which accepts it is termed the server. A 15 | //! single endpoint may function as both client and server for different connections, for example 16 | //! in a peer-to-peer application. To communicate application data, each endpoint may open streams 17 | //! up to a limit dictated by its peer. Typically, that limit is increased as old streams are 18 | //! finished. 19 | //! 20 | //! Streams may be unidirectional or bidirectional, and are cheap to create and disposable. For 21 | //! example, a traditionally datagram-oriented application could use a new stream for every 22 | //! message it wants to send, no longer needing to worry about MTUs. Bidirectional streams behave 23 | //! much like a traditional TCP connection, and are useful for sending messages that have an 24 | //! immediate response, such as an HTTP request. Stream data is delivered reliably, and there is no 25 | //! ordering enforced between data on different streams. 26 | //! 27 | //! By avoiding head-of-line blocking and providing unified congestion control across all streams 28 | //! of a connection, QUIC is able to provide higher throughput and lower latency than one or 29 | //! multiple TCP connections between the same two hosts, while providing more useful behavior than 30 | //! raw UDP sockets. 31 | //! 32 | //! Quinn also exposes unreliable datagrams, which are a low-level primitive preferred when 33 | //! automatic fragmentation and retransmission of certain data is not desired. 34 | //! 35 | //! QUIC uses encryption and identity verification built directly on TLS 1.3. Just as with a TLS 36 | //! server, it is useful for a QUIC server to be identified by a certificate signed by a trusted 37 | //! authority. If this is infeasible--for example, if servers are short-lived or not associated 38 | //! with a domain name--then as with TLS, self-signed certificates can be used to provide 39 | //! encryption alone. 40 | #![warn(missing_docs)] 41 | #![warn(unreachable_pub)] 42 | #![warn(clippy::use_self)] 43 | 44 | use std::sync::Arc; 45 | 46 | mod connection; 47 | mod endpoint; 48 | mod incoming; 49 | mod mutex; 50 | mod recv_stream; 51 | mod runtime; 52 | mod send_stream; 53 | mod work_limiter; 54 | 55 | #[cfg(not(wasm_browser))] 56 | pub(crate) use std::time::{Duration, Instant}; 57 | #[cfg(wasm_browser)] 58 | pub(crate) use web_time::{Duration, Instant}; 59 | 60 | #[cfg(feature = "bloom")] 61 | pub use proto::BloomTokenLog; 62 | pub use proto::{ 63 | AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosedStream, ConfigError, 64 | ConnectError, ConnectionClose, ConnectionError, ConnectionId, ConnectionIdGenerator, 65 | ConnectionStats, Dir, EcnCodepoint, EndpointConfig, FrameStats, FrameType, IdleTimeout, 66 | MtuDiscoveryConfig, NoneTokenLog, NoneTokenStore, PathStats, ServerConfig, Side, StdSystemTime, 67 | StreamId, TimeSource, TokenLog, TokenMemoryCache, TokenReuseError, TokenStore, Transmit, 68 | TransportConfig, TransportErrorCode, UdpStats, ValidationTokenConfig, VarInt, 69 | VarIntBoundsExceeded, Written, congestion, crypto, 70 | }; 71 | #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] 72 | pub use rustls; 73 | pub use udp; 74 | 75 | pub use crate::connection::{ 76 | AcceptBi, AcceptUni, Connecting, Connection, OpenBi, OpenUni, ReadDatagram, SendDatagram, 77 | SendDatagramError, ZeroRttAccepted, 78 | }; 79 | pub use crate::endpoint::{Accept, Endpoint, EndpointStats}; 80 | pub use crate::incoming::{Incoming, IncomingFuture, RetryError}; 81 | pub use crate::recv_stream::{ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError}; 82 | #[cfg(feature = "runtime-async-std")] 83 | pub use crate::runtime::AsyncStdRuntime; 84 | #[cfg(feature = "runtime-smol")] 85 | pub use crate::runtime::SmolRuntime; 86 | #[cfg(feature = "runtime-tokio")] 87 | pub use crate::runtime::TokioRuntime; 88 | pub use crate::runtime::{AsyncTimer, AsyncUdpSocket, Runtime, UdpPoller, default_runtime}; 89 | pub use crate::send_stream::{SendStream, StoppedError, WriteError}; 90 | 91 | #[cfg(test)] 92 | mod tests; 93 | 94 | #[derive(Debug)] 95 | enum ConnectionEvent { 96 | Close { 97 | error_code: VarInt, 98 | reason: bytes::Bytes, 99 | }, 100 | Proto(proto::ConnectionEvent), 101 | Rebind(Arc), 102 | } 103 | 104 | fn udp_transmit<'a>(t: &proto::Transmit, buffer: &'a [u8]) -> udp::Transmit<'a> { 105 | udp::Transmit { 106 | destination: t.destination, 107 | ecn: t.ecn.map(udp_ecn), 108 | contents: buffer, 109 | segment_size: t.segment_size, 110 | src_ip: t.src_ip, 111 | } 112 | } 113 | 114 | fn udp_ecn(ecn: proto::EcnCodepoint) -> udp::EcnCodepoint { 115 | match ecn { 116 | proto::EcnCodepoint::Ect0 => udp::EcnCodepoint::Ect0, 117 | proto::EcnCodepoint::Ect1 => udp::EcnCodepoint::Ect1, 118 | proto::EcnCodepoint::Ce => udp::EcnCodepoint::Ce, 119 | } 120 | } 121 | 122 | /// Maximum number of datagrams processed in send/recv calls to make before moving on to other processing 123 | /// 124 | /// This helps ensure we don't starve anything when the CPU is slower than the link. 125 | /// Value is selected by picking a low number which didn't degrade throughput in benchmarks. 126 | const IO_LOOP_BOUND: usize = 160; 127 | 128 | /// The maximum amount of time that should be spent in `recvmsg()` calls per endpoint iteration 129 | /// 130 | /// 50us are chosen so that an endpoint iteration with a 50us sendmsg limit blocks 131 | /// the runtime for a maximum of about 100us. 132 | /// Going much lower does not yield any noticeable difference, since a single `recvmmsg` 133 | /// batch of size 32 was observed to take 30us on some systems. 134 | const RECV_TIME_BOUND: Duration = Duration::from_micros(50); 135 | -------------------------------------------------------------------------------- /quinn/src/mutex.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | 6 | #[cfg(feature = "lock_tracking")] 7 | mod tracking { 8 | use super::*; 9 | use crate::{Duration, Instant}; 10 | use std::collections::VecDeque; 11 | use tracing::warn; 12 | 13 | #[derive(Debug)] 14 | struct Inner { 15 | last_lock_owner: VecDeque<(&'static str, Duration)>, 16 | value: T, 17 | } 18 | 19 | /// A Mutex which optionally allows to track the time a lock was held and 20 | /// emit warnings in case of excessive lock times 21 | pub(crate) struct Mutex { 22 | inner: std::sync::Mutex>, 23 | } 24 | 25 | impl std::fmt::Debug for Mutex { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | std::fmt::Debug::fmt(&self.inner, f) 28 | } 29 | } 30 | 31 | impl Mutex { 32 | pub(crate) fn new(value: T) -> Self { 33 | Self { 34 | inner: std::sync::Mutex::new(Inner { 35 | last_lock_owner: VecDeque::new(), 36 | value, 37 | }), 38 | } 39 | } 40 | 41 | /// Acquires the lock for a certain purpose 42 | /// 43 | /// The purpose will be recorded in the list of last lock owners 44 | pub(crate) fn lock(&self, purpose: &'static str) -> MutexGuard { 45 | // We don't bother dispatching through Runtime::now because they're pure performance 46 | // diagnostics. 47 | let now = Instant::now(); 48 | let guard = self.inner.lock().unwrap(); 49 | 50 | let lock_time = Instant::now(); 51 | let elapsed = lock_time.duration_since(now); 52 | 53 | if elapsed > Duration::from_millis(1) { 54 | warn!( 55 | "Locking the connection for {} took {:?}. Last owners: {:?}", 56 | purpose, elapsed, guard.last_lock_owner 57 | ); 58 | } 59 | 60 | MutexGuard { 61 | guard, 62 | start_time: lock_time, 63 | purpose, 64 | } 65 | } 66 | } 67 | 68 | pub(crate) struct MutexGuard<'a, T> { 69 | guard: std::sync::MutexGuard<'a, Inner>, 70 | start_time: Instant, 71 | purpose: &'static str, 72 | } 73 | 74 | impl Drop for MutexGuard<'_, T> { 75 | fn drop(&mut self) { 76 | if self.guard.last_lock_owner.len() == MAX_LOCK_OWNERS { 77 | self.guard.last_lock_owner.pop_back(); 78 | } 79 | 80 | let duration = self.start_time.elapsed(); 81 | 82 | if duration > Duration::from_millis(1) { 83 | warn!( 84 | "Utilizing the connection for {} took {:?}", 85 | self.purpose, duration 86 | ); 87 | } 88 | 89 | self.guard 90 | .last_lock_owner 91 | .push_front((self.purpose, duration)); 92 | } 93 | } 94 | 95 | impl Deref for MutexGuard<'_, T> { 96 | type Target = T; 97 | 98 | fn deref(&self) -> &Self::Target { 99 | &self.guard.value 100 | } 101 | } 102 | 103 | impl DerefMut for MutexGuard<'_, T> { 104 | fn deref_mut(&mut self) -> &mut Self::Target { 105 | &mut self.guard.value 106 | } 107 | } 108 | 109 | const MAX_LOCK_OWNERS: usize = 20; 110 | } 111 | 112 | #[cfg(feature = "lock_tracking")] 113 | pub(crate) use tracking::Mutex; 114 | 115 | #[cfg(not(feature = "lock_tracking"))] 116 | mod non_tracking { 117 | use super::*; 118 | 119 | /// A Mutex which optionally allows to track the time a lock was held and 120 | /// emit warnings in case of excessive lock times 121 | #[derive(Debug)] 122 | pub(crate) struct Mutex { 123 | inner: std::sync::Mutex, 124 | } 125 | 126 | impl Mutex { 127 | pub(crate) fn new(value: T) -> Self { 128 | Self { 129 | inner: std::sync::Mutex::new(value), 130 | } 131 | } 132 | 133 | /// Acquires the lock for a certain purpose 134 | /// 135 | /// The purpose will be recorded in the list of last lock owners 136 | pub(crate) fn lock(&self, _purpose: &'static str) -> MutexGuard { 137 | MutexGuard { 138 | guard: self.inner.lock().unwrap(), 139 | } 140 | } 141 | } 142 | 143 | pub(crate) struct MutexGuard<'a, T> { 144 | guard: std::sync::MutexGuard<'a, T>, 145 | } 146 | 147 | impl Deref for MutexGuard<'_, T> { 148 | type Target = T; 149 | 150 | fn deref(&self) -> &Self::Target { 151 | self.guard.deref() 152 | } 153 | } 154 | 155 | impl DerefMut for MutexGuard<'_, T> { 156 | fn deref_mut(&mut self) -> &mut Self::Target { 157 | self.guard.deref_mut() 158 | } 159 | } 160 | } 161 | 162 | #[cfg(not(feature = "lock_tracking"))] 163 | pub(crate) use non_tracking::Mutex; 164 | -------------------------------------------------------------------------------- /quinn/src/runtime/async_io.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | time::Instant, 6 | }; 7 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 8 | use std::{io, sync::Arc, task::ready}; 9 | 10 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 11 | use async_io::Async; 12 | use async_io::Timer; 13 | 14 | use super::AsyncTimer; 15 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 16 | use super::{AsyncUdpSocket, Runtime, UdpPollHelper}; 17 | 18 | #[cfg(feature = "runtime-smol")] 19 | // Due to MSRV, we must specify `self::` where there's crate/module ambiguity 20 | pub use self::smol::SmolRuntime; 21 | 22 | #[cfg(feature = "runtime-smol")] 23 | mod smol { 24 | use super::*; 25 | 26 | /// A Quinn runtime for smol 27 | #[derive(Debug)] 28 | pub struct SmolRuntime; 29 | 30 | impl Runtime for SmolRuntime { 31 | fn new_timer(&self, t: Instant) -> Pin> { 32 | Box::pin(Timer::at(t)) 33 | } 34 | 35 | fn spawn(&self, future: Pin + Send>>) { 36 | ::smol::spawn(future).detach(); 37 | } 38 | 39 | fn wrap_udp_socket( 40 | &self, 41 | sock: std::net::UdpSocket, 42 | ) -> io::Result> { 43 | Ok(Arc::new(UdpSocket::new(sock)?)) 44 | } 45 | } 46 | } 47 | 48 | #[cfg(feature = "runtime-async-std")] 49 | // Due to MSRV, we must specify `self::` where there's crate/module ambiguity 50 | pub use self::async_std::AsyncStdRuntime; 51 | 52 | #[cfg(feature = "runtime-async-std")] 53 | mod async_std { 54 | use super::*; 55 | 56 | /// A Quinn runtime for async-std 57 | #[derive(Debug)] 58 | pub struct AsyncStdRuntime; 59 | 60 | impl Runtime for AsyncStdRuntime { 61 | fn new_timer(&self, t: Instant) -> Pin> { 62 | Box::pin(Timer::at(t)) 63 | } 64 | 65 | fn spawn(&self, future: Pin + Send>>) { 66 | ::async_std::task::spawn(future); 67 | } 68 | 69 | fn wrap_udp_socket( 70 | &self, 71 | sock: std::net::UdpSocket, 72 | ) -> io::Result> { 73 | Ok(Arc::new(UdpSocket::new(sock)?)) 74 | } 75 | } 76 | } 77 | 78 | impl AsyncTimer for Timer { 79 | fn reset(mut self: Pin<&mut Self>, t: Instant) { 80 | self.set_at(t) 81 | } 82 | 83 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { 84 | Future::poll(self, cx).map(|_| ()) 85 | } 86 | } 87 | 88 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 89 | #[derive(Debug)] 90 | struct UdpSocket { 91 | io: Async, 92 | inner: udp::UdpSocketState, 93 | } 94 | 95 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 96 | impl UdpSocket { 97 | fn new(sock: std::net::UdpSocket) -> io::Result { 98 | Ok(Self { 99 | inner: udp::UdpSocketState::new((&sock).into())?, 100 | io: Async::new_nonblocking(sock)?, 101 | }) 102 | } 103 | } 104 | 105 | #[cfg(any(feature = "runtime-smol", feature = "runtime-async-std"))] 106 | impl AsyncUdpSocket for UdpSocket { 107 | fn create_io_poller(self: Arc) -> Pin> { 108 | Box::pin(UdpPollHelper::new(move || { 109 | let socket = self.clone(); 110 | async move { socket.io.writable().await } 111 | })) 112 | } 113 | 114 | fn try_send(&self, transmit: &udp::Transmit) -> io::Result<()> { 115 | self.inner.send((&self.io).into(), transmit) 116 | } 117 | 118 | fn poll_recv( 119 | &self, 120 | cx: &mut Context, 121 | bufs: &mut [io::IoSliceMut<'_>], 122 | meta: &mut [udp::RecvMeta], 123 | ) -> Poll> { 124 | loop { 125 | ready!(self.io.poll_readable(cx))?; 126 | if let Ok(res) = self.inner.recv((&self.io).into(), bufs, meta) { 127 | return Poll::Ready(Ok(res)); 128 | } 129 | } 130 | } 131 | 132 | fn local_addr(&self) -> io::Result { 133 | self.io.as_ref().local_addr() 134 | } 135 | 136 | fn may_fragment(&self) -> bool { 137 | self.inner.may_fragment() 138 | } 139 | 140 | fn max_transmit_segments(&self) -> usize { 141 | self.inner.max_gso_segments() 142 | } 143 | 144 | fn max_receive_segments(&self) -> usize { 145 | self.inner.gro_segments() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /quinn/src/runtime/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | io, 4 | pin::Pin, 5 | sync::Arc, 6 | task::{Context, Poll, ready}, 7 | time::Instant, 8 | }; 9 | 10 | use tokio::{ 11 | io::Interest, 12 | time::{Sleep, sleep_until}, 13 | }; 14 | 15 | use super::{AsyncTimer, AsyncUdpSocket, Runtime, UdpPollHelper}; 16 | 17 | /// A Quinn runtime for Tokio 18 | #[derive(Debug)] 19 | pub struct TokioRuntime; 20 | 21 | impl Runtime for TokioRuntime { 22 | fn new_timer(&self, t: Instant) -> Pin> { 23 | Box::pin(sleep_until(t.into())) 24 | } 25 | 26 | fn spawn(&self, future: Pin + Send>>) { 27 | tokio::spawn(future); 28 | } 29 | 30 | fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result> { 31 | Ok(Arc::new(UdpSocket { 32 | inner: udp::UdpSocketState::new((&sock).into())?, 33 | io: tokio::net::UdpSocket::from_std(sock)?, 34 | })) 35 | } 36 | 37 | fn now(&self) -> Instant { 38 | tokio::time::Instant::now().into_std() 39 | } 40 | } 41 | 42 | impl AsyncTimer for Sleep { 43 | fn reset(self: Pin<&mut Self>, t: Instant) { 44 | Self::reset(self, t.into()) 45 | } 46 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { 47 | Future::poll(self, cx) 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | struct UdpSocket { 53 | io: tokio::net::UdpSocket, 54 | inner: udp::UdpSocketState, 55 | } 56 | 57 | impl AsyncUdpSocket for UdpSocket { 58 | fn create_io_poller(self: Arc) -> Pin> { 59 | Box::pin(UdpPollHelper::new(move || { 60 | let socket = self.clone(); 61 | async move { socket.io.writable().await } 62 | })) 63 | } 64 | 65 | fn try_send(&self, transmit: &udp::Transmit) -> io::Result<()> { 66 | self.io.try_io(Interest::WRITABLE, || { 67 | self.inner.send((&self.io).into(), transmit) 68 | }) 69 | } 70 | 71 | fn poll_recv( 72 | &self, 73 | cx: &mut Context, 74 | bufs: &mut [std::io::IoSliceMut<'_>], 75 | meta: &mut [udp::RecvMeta], 76 | ) -> Poll> { 77 | loop { 78 | ready!(self.io.poll_recv_ready(cx))?; 79 | if let Ok(res) = self.io.try_io(Interest::READABLE, || { 80 | self.inner.recv((&self.io).into(), bufs, meta) 81 | }) { 82 | return Poll::Ready(Ok(res)); 83 | } 84 | } 85 | } 86 | 87 | fn local_addr(&self) -> io::Result { 88 | self.io.local_addr() 89 | } 90 | 91 | fn may_fragment(&self) -> bool { 92 | self.inner.may_fragment() 93 | } 94 | 95 | fn max_transmit_segments(&self) -> usize { 96 | self.inner.max_gso_segments() 97 | } 98 | 99 | fn max_receive_segments(&self) -> usize { 100 | self.inner.gro_segments() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | --------------------------------------------------------------------------------