├── .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 | [](https://docs.rs/quinn/)
4 | [](https://crates.io/crates/quinn)
5 | [](https://github.com/djc/quinn/actions?query=workflow%3ACI)
6 | [](https://codecov.io/gh/quinn-rs/quinn)
7 | [](https://matrix.to/#/#quinn:matrix.org)
8 | [](https://discord.gg/SGPEcDfVzh)
9 | [](LICENSE-MIT)
10 | [](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 |
--------------------------------------------------------------------------------
/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