Files
noq/print.html
divagant-martian 40fe9ada9c deploy: 93b6d01605
2025-09-25 16:54:03 +00:00

664 lines
42 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Quinn</title>
<meta name="robots" content="noindex">
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Quinn</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="networking-introduction"><a class="header" href="#networking-introduction">Networking Introduction</a></h1>
<p>In this chapter, you will find a very short introduction to various networking concepts.
These concepts are important to understanding when to use QUIC.</p>
<h2 id="1-tcpip-and-udp-comparison"><a class="header" href="#1-tcpip-and-udp-comparison">1. TCP/IP and UDP Comparison</a></h2>
<p>Let's compare TCP, UDP, and QUIC.</p>
<ul>
<li><strong>unreliable</strong>: Transport packets are not assured of arrival and ordering.</li>
<li><strong>reliable</strong>: Transport packets are assured of arrival and ordering.</li>
</ul>
<div class="table-wrapper"><table><thead><tr><th style="text-align: center">Feature</th><th style="text-align: center">TCP</th><th style="text-align: center">UDP</th><th style="text-align: center">QUIC</th></tr></thead><tbody>
<tr><td style="text-align: center"><a href="https://en.wikipedia.org/wiki/Connection-oriented_communication">Connection-Oriented</a></td><td style="text-align: center">Yes</td><td style="text-align: center">No</td><td style="text-align: center">Yes</td></tr>
<tr><td style="text-align: center">Transport Guarantees</td><td style="text-align: center">Reliable</td><td style="text-align: center">Unreliable</td><td style="text-align: center">Reliable ('a)</td></tr>
<tr><td style="text-align: center">Packet Transfer</td><td style="text-align: center"><a href="https://en.wikipedia.org/wiki/Stream_(computing)">Stream-based</a></td><td style="text-align: center">Message based</td><td style="text-align: center">Stream based</td></tr>
<tr><td style="text-align: center">Header Size</td><td style="text-align: center">~20 bytes</td><td style="text-align: center">8 bytes</td><td style="text-align: center">~16 bytes (depending on connection id)</td></tr>
<tr><td style="text-align: center"><a href="https://en.wikipedia.org/wiki/TCP_congestion_control">Control Flow, Congestion Avoidance/Control</a></td><td style="text-align: center">Yes</td><td style="text-align: center">No</td><td style="text-align: center">Yes ('b)</td></tr>
<tr><td style="text-align: center">Based On</td><td style="text-align: center"><a href="https://nl.wikipedia.org/wiki/Internetprotocol">IP</a></td><td style="text-align: center"><a href="https://nl.wikipedia.org/wiki/Internetprotocol">IP</a></td><td style="text-align: center">UDP</td></tr>
</tbody></table>
</div>
<p>'a. Unreliable is supported as an extension.<br />
'b. QUIC control flow/congestion implementations will run in userspace whereas in TCP it's running in kernel space,
however, there might be a kernel implementation for QUIC in the future.</p>
<h2 id="2-issues-with-tcp"><a class="header" href="#2-issues-with-tcp">2. Issues with TCP</a></h2>
<p>TCP has been around for a long time and was not designed with the modern internet in mind.
It has several difficulties that QUIC tries to resolve.</p>
<h3 id="head-of-line-blocking"><a class="header" href="#head-of-line-blocking">Head-of-line Blocking</a></h3>
<p>One of the biggest issues with TCP is that of Head-of-line blocking.
It is a convenient feature because it ensures that all packages are sent and arrive in order.
However, in cases of high throughput (multiplayer game networking) and big load in a short time (web page load), this can severely impact latency.</p>
<p>The issue is demonstrated in the following animation:</p>
<p><img src="./images/hol.gif" alt="Head of line blocking" /></p>
<p>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.</p>
<p>Let's look at two areas where head-of-line blocking causes problems.</p>
<p><strong>Web Networking</strong></p>
<p>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.
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.
However, data loss of a single packet will still block all response streams because they exist within the context of a single TCP stream.</p>
<h3 id="connection-setup-duration"><a class="header" href="#connection-setup-duration">Connection Setup Duration</a></h3>
<p>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.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="the-quic-protocol"><a class="header" href="#the-quic-protocol">The QUIC protocol</a></h1>
<p><a href="https://en.wikipedia.org/wiki/QUIC">QUIC</a> is a general-purpose network protocol built on top of UDP,
and <a href="https://www.rfc-editor.org/rfc/rfc9000.html">standardized</a> by the <a href="https://www.ietf.org/">IETF</a>. Although QUIC is still relatively new,
the protocol is used for all connections from Chrome web browsers to the Google servers.</p>
<p>QUIC solves a number of transport-layer and application-layer problems experienced by modern web applications.
It is very similar to TCP+TLS+HTTP2, but implemented on top of UDP.
Having QUIC as a self-contained protocol allows innovations which arent
possible with existing protocols as they are hampered by legacy clients and middleboxes.</p>
<p>Key advantages of QUIC over TCP+TLS+HTTP2 include:</p>
<ul>
<li>Improved connection establishment speed (0-rtt).</li>
<li>Improved congestion control by moving congestion control algorithms into the user space at both endpoints.</li>
<li>Improved bandwidth estimation in each direction to avoid congestion.</li>
<li>Improved multiplexing without head-of-line blocking.</li>
<li>Contains forward error correction (FEC).</li>
</ul>
<p>While QUIC's intentions are originally web-oriented, it offers interesting opportunities in other areas like game networking.
One thing is for sure, QUIC has many great potentials and will serve us in the future with HTTP/3.</p>
<p>In the upcoming chapter we will be discussing various aspects of QUIC also in relation to Quinn.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 align="center"><img width="500" src="https://raw.githubusercontent.com/quinn-rs/quinn/51a3cea225670757cb844a342428e4e1341d9f13/docs/thumbnail.svg" /></h1>
<p><a href="https://docs.rs/quinn/"><img src="https://docs.rs/quinn/badge.svg" alt="Documentation" /></a>
<a href="https://crates.io/crates/quinn"><img src="https://img.shields.io/crates/v/quinn.svg" alt="Crates.io" /></a>
<a href="https://github.com/djc/quinn/actions?query=workflow%3ACI"><img src="https://github.com/quinn-rs/quinn/workflows/CI/badge.svg" alt="Build status" /></a>
<a href="https://codecov.io/gh/quinn-rs/quinn"><img src="https://codecov.io/gh/quinn-rs/quinn/branch/main/graph/badge.svg" alt="codecov" /></a>
<a href="https://matrix.to/#/#quinn:matrix.org"><img src="https://img.shields.io/badge/chat-%23quinn:matrix.org-%2346BC99?logo=matrix" alt="Chat" /></a>
<a href="https://discord.gg/SGPEcDfVzh"><img src="https://img.shields.io/discord/976380008299917365?logo=discord" alt="Chat" /></a>
<a href="LICENSE-MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
<a href="LICENSE-APACHE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0" /></a></p>
<p>Quinn is a pure-Rust, async-compatible implementation of the IETF <a href="https://quicwg.github.io/">QUIC</a> transport protocol.
The project was founded by <a href="https://github.com/djc">Dirkjan Ochtman</a> and
<a href="https://github.com/Ralith">Benjamin Saunders</a> as a side project in 2018, and has seen more than
30 releases since then. If you're using Quinn in a commercial setting, please consider
<a href="https://opencollective.com/quinn-rs">sponsoring</a> the project.</p>
<h2 id="features"><a class="header" href="#features">Features</a></h2>
<ul>
<li>Simultaneous client/server operation</li>
<li>Ordered and unordered stream reads for improved performance</li>
<li>Works on stable Rust, tested on Linux, macOS and Windows</li>
<li>Pluggable cryptography, with a standard implementation backed by
<a href="https://github.com/ctz/rustls">rustls</a> and <a href="https://github.com/briansmith/ring"><em>ring</em></a></li>
<li>Application-layer datagrams for small, unreliable messages</li>
<li>Future-based async API</li>
<li>Minimum supported Rust version of 1.74.1</li>
</ul>
<h2 id="overview"><a class="header" href="#overview">Overview</a></h2>
<ul>
<li><strong>quinn:</strong> High-level async API based on tokio, see <a href="https://github.com/djc/quinn/tree/main/quinn/examples">examples</a> for usage. This will be used by most developers. (Basic benchmarks are included.)</li>
<li><strong>quinn-proto:</strong> Deterministic state machine of the protocol which performs <a href="https://sans-io.readthedocs.io/how-to-sans-io.html"><strong>no</strong> I/O</a> internally and is suitable for use with custom event loops (and potentially a C or C++ API).</li>
<li><strong>quinn-udp:</strong> UDP sockets with ECN information tuned for the protocol.</li>
<li><strong>bench:</strong> Benchmarks without any framework.</li>
<li><strong>fuzz:</strong> Fuzz tests.</li>
</ul>
<h1 id="getting-started"><a class="header" href="#getting-started">Getting Started</a></h1>
<p><strong>Examples</strong></p>
<pre><code class="language-sh">$ cargo run --example server ./
$ cargo run --example client https://localhost:4433/Cargo.toml
</code></pre>
<p>This launches an HTTP 0.9 server on the loopback address serving the current
working directory, with the client fetching <code>./Cargo.toml</code>. By default, the
server generates a self-signed certificate and stores it to disk, where the
client will automatically find and trust it.</p>
<p><strong>Links</strong></p>
<ul>
<li>Talk at <a href="https://paris.rustfest.eu/sessions/a-quic-future-in-rust">RustFest Paris (May 2018) presentation</a>; <a href="https://github.com/djc/talks/blob/ff760845b51ba4836cce82e7f2c640ecb5fd59fa/2018-05-26%20A%20QUIC%20future%20in%20Rust/Quinn-Speaker.pdf">slides</a>; <a href="https://www.youtube.com/watch?v=EHgyY5DNdvI">YouTube</a></li>
<li>Usage <a href="https://github.com/djc/quinn/tree/main/quinn/examples">examples</a></li>
<li>Guide <a href="https://quinn-rs.github.io/quinn/networking-introduction.html">book</a></li>
</ul>
<h2 id="usage-notes"><a class="header" href="#usage-notes">Usage Notes</a></h2>
<details>
<summary>
Click to show the notes
</summary>
<h3 id="buffers"><a class="header" href="#buffers">Buffers</a></h3>
<p>A Quinn endpoint corresponds to a single UDP socket, no matter how many
connections are in use. Handling high aggregate data rates on a single endpoint
can require a larger UDP buffer than is configured by default in most
environments. If you observe erratic latency and/or throughput over a stable
network link, consider increasing the buffer sizes used. For example, you could
adjust the <code>SO_SNDBUF</code> and <code>SO_RCVBUF</code> options of the UDP socket to be used
before passing it in to Quinn. Note that some platforms (e.g. Linux) require
elevated privileges or modified system configuration for a process to increase
its UDP buffer sizes.</p>
<h3 id="certificates"><a class="header" href="#certificates">Certificates</a></h3>
<p>By default, Quinn clients validate the cryptographic identity of servers they
connect to. This prevents an active, on-path attacker from intercepting
messages, but requires trusting some certificate authority. For many purposes,
this can be accomplished by using certificates from <a href="https://letsencrypt.org/">Let's Encrypt</a>
for servers, and relying on the default configuration for clients.</p>
<p>For some cases, including peer-to-peer, trust-on-first-use, deliberately
insecure applications, or any case where servers are not identified by domain
name, this isn't practical. Arbitrary certificate validation logic can be
implemented by customizing the <code>rustls</code> configuration; see the
<a href="https://github.com/quinn-rs/quinn/blob/main/quinn/examples/insecure_connection.rs">insecure_connection.rs</a> example for details.</p>
<p>When operating your own certificate authority doesn't make sense, <a href="https://crates.io/crates/rcgen">rcgen</a>
can be used to generate self-signed certificates on demand. To support
trust-on-first-use, servers that automatically generate self-signed certificates
should write their generated certificate to persistent storage and reuse it on
future runs.</p>
</details>
<p></p>
<h2 id="contribution"><a class="header" href="#contribution">Contribution</a></h2>
<p>All feedback welcome. Feel free to file bugs, requests for documentation and
any other feedback to the <a href="https://github.com/djc/quinn/issues">issue tracker</a>.</p>
<p>The quinn-proto test suite uses simulated IO for reproducibility and to avoid
long sleeps in certain timing-sensitive tests. If the <code>SSLKEYLOGFILE</code>
environment variable is set, the tests will emit UDP packets for inspection
using external protocol analyzers like Wireshark, and NSS-compatible key logs
for the client side of each connection will be written to the path specified in
the variable.</p>
<p>The minimum supported Rust version for published releases of our
crates will always be at least 6 months old at the time of release.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="certificates-1"><a class="header" href="#certificates-1">Certificates</a></h1>
<p>In this chapter, we discuss the configuration of the certificates that are <strong>required</strong> for a working Quinn connection.</p>
<p>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.</p>
<h2 id="insecure-connection"><a class="header" href="#insecure-connection">Insecure Connection</a></h2>
<p>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!).
When the <a href="https://github.com/ctz/rustls">rustls</a> <code>dangerous_configuration</code> feature flag is enabled, a client can be configured to trust any server.</p>
<p>Start by adding a <a href="https://github.com/ctz/rustls">rustls</a> dependency with the <code>dangerous_configuration</code> feature flag to your <code>Cargo.toml</code> file.</p>
<pre><code class="language-toml">quinn = "0.11"
rustls = "0.23"
</code></pre>
<p>Then, allow the client to skip the certificate validation by implementing <a href="https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html">ServerCertVerifier</a> and letting it assert verification for any server.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Implementation of `ServerCertVerifier` that verifies everything as trustworthy.
#[derive(Debug)]
struct SkipServerVerification(Arc&lt;CryptoProvider&gt;);
impl SkipServerVerification {
fn new() -&gt; Arc&lt;Self&gt; {
Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider())))
}
}
impl danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&amp;self,
_end_entity: &amp;CertificateDer&lt;'_&gt;,
_intermediates: &amp;[CertificateDer&lt;'_&gt;],
_server_name: &amp;ServerName&lt;'_&gt;,
_ocsp: &amp;[u8],
_now: UnixTime,
) -&gt; Result&lt;danger::ServerCertVerified, rustls::Error&gt; {
Ok(danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&amp;self,
message: &amp;[u8],
cert: &amp;CertificateDer&lt;'_&gt;,
dss: &amp;DigitallySignedStruct,
) -&gt; Result&lt;danger::HandshakeSignatureValid, rustls::Error&gt; {
verify_tls12_signature(
message,
cert,
dss,
&amp;self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&amp;self,
message: &amp;[u8],
cert: &amp;CertificateDer&lt;'_&gt;,
dss: &amp;DigitallySignedStruct,
) -&gt; Result&lt;danger::HandshakeSignatureValid, rustls::Error&gt; {
verify_tls13_signature(
message,
cert,
dss,
&amp;self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&amp;self) -&gt; Vec&lt;SignatureScheme&gt; {
self.0.signature_verification_algorithms.supported_schemes()
}
}
<span class="boring">}</span></code></pre></pre>
<p>After that, modify the <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> to use this <a href="https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html">ServerCertVerifier</a> implementation.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn configure_client() -&gt; Result&lt;ClientConfig, NoInitialCipherSuite&gt; {
let crypto = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(SkipServerVerification::new())
.with_no_client_auth();
Ok(ClientConfig::new(Arc::new(QuicClientConfig::try_from(
crypto,
)?)))
}
<span class="boring">}</span></code></pre></pre>
<p>Finally, if you plug this <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> into the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.set_default_client_config">Endpoint::set_default_client_config()</a> your client endpoint should verify all connections as trustworthy.</p>
<h2 id="using-certificates"><a class="header" href="#using-certificates">Using Certificates</a></h2>
<p>In this section, we look at certifying an endpoint with a certificate.
The certificate can be signed with its key, or with a certificate authority's key.</p>
<h3 id="self-signed-certificates"><a class="header" href="#self-signed-certificates">Self Signed Certificates</a></h3>
<p>Relying on <a href="https://en.wikipedia.org/wiki/Self-signed_certificate#:~:text=In%20cryptography%20and%20computer%20security,a%20CA%20aim%20to%20provide.">self-signed</a> certificates means that clients allow servers to sign their certificates.
This is simpler because no third party is involved in signing the server's certificate.
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 <a href="https://github.com/est31/rcgen">rcgen</a> crate or the openssl binary.
This example uses <a href="https://github.com/est31/rcgen">rcgen</a> to generate a certificate.</p>
<p>Let's look at an example:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn generate_self_signed_cert()
-&gt; Result&lt;(CertificateDer&lt;'static&gt;, PrivatePkcs8KeyDer&lt;'static&gt;), Box&lt;dyn Error&gt;&gt; {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?;
let cert_der = CertificateDer::from(cert.cert);
let key = PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der());
Ok((cert_der, key))
}
<span class="boring">}</span></code></pre></pre>
<p><em>Note that <a href="https://docs.rs/rcgen/latest/rcgen/fn.generate_simple_self_signed.html">generate_simple_self_signed</a> returns a <a href="https://en.wikipedia.org/wiki/Public_key_certificate">Certificate</a> that can be serialized to both <code>.der</code> and <code>.pem</code> formats.</em></p>
<h3 id="non-self-signed-certificates"><a class="header" href="#non-self-signed-certificates">Non-self-signed Certificates</a></h3>
<p>For this example, we use <a href="https://letsencrypt.org/getting-started/">Let's Encrypt</a>, a well-known Certificate Authority (<a href="https://en.wikipedia.org/wiki/Certificate_authority">CA</a>) (certificate issuer) which distributes certificates for free.</p>
<p><strong>Generate Certificate</strong></p>
<p><a href="https://certbot.eff.org/instructions">certbot</a> can be used with Let's Encrypt to generate certificates; its website comes with clear instructions.
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.</p>
<p>On the certbot website, select that you do not have a public web server and follow the given installation instructions.
certbot must answer a cryptographic challenge of the Let's Encrypt API to prove that you control the domain.
It needs to listen on port 80 (HTTP) or 443 (HTTPS) to achieve this. Open the appropriate port in your firewall and router.</p>
<p>If certbot is installed, run <code>certbot certonly --standalone</code>, this command will start a web server in the background and start the challenge.
certbot asks for the required data and writes the certificates to <code>fullchain.pem</code> and the private key to <code>privkey.pem</code>.
These files can then be referenced in code.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn read_certs_from_file()
-&gt; Result&lt;(Vec&lt;CertificateDer&lt;'static&gt;&gt;, PrivateKeyDer&lt;'static&gt;), Box&lt;dyn Error&gt;&gt; {
let certs = CertificateDer::pem_file_iter("./fullchain.pem")
.unwrap()
.map(|cert| cert.unwrap())
.collect();
let key = PrivateKeyDer::from_pem_file("./privkey.pem").unwrap();
Ok((certs, key))
}
<span class="boring">}</span></code></pre></pre>
<h3 id="configuring-certificates"><a class="header" href="#configuring-certificates">Configuring Certificates</a></h3>
<p>Now that you have a valid certificate, the client and server need to be configured to use it.
After configuring plug the configuration into the <code>Endpoint</code>.</p>
<p><strong>Configure Server</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let server_config = quinn::ServerConfig::with_single_cert(certs, key);
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your server to be secured.</p>
<p><strong>Configure Client</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let client_config = quinn::ClientConfig::try_with_platform_verifier().unwrap();
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your client to trust a server certificate signed by a conventional certificate authority.</p>
<p><br><hr></p>
<p><a href="quinn/set-up-connection.html">Next</a>, let's have a look at how to set up a connection.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="connection-setup"><a class="header" href="#connection-setup">Connection Setup</a></h1>
<p>In the <a href="quinn/certificate.html">previous chapter</a> we looked at how to configure a certificate.
This aspect is omitted in this chapter to prevent duplication.
But <strong>remember</strong> that this is required to get your <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html">Endpoint</a> up and running.
This chapter explains how to set up a connection and prepare it for data transfer.</p>
<p>It all starts with the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html">Endpoint</a> struct, this is the entry point of the library.</p>
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<p>Let's start by defining some constants.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>const SERVER_NAME: &amp;str = "localhost";
const LOCALHOST_V4: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
const CLIENT_ADDR: SocketAddr = SocketAddr::new(LOCALHOST_V4, 5000);
const SERVER_ADDR: SocketAddr = SocketAddr::new(LOCALHOST_V4, 5001);
<span class="boring">}</span></code></pre></pre>
<p><strong>Server</strong></p>
<p>First, the server endpoint should be bound to a socket.
The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.server">server()</a> method, which can be used for this, returns the <code>Endpoint</code> type.
<code>Endpoint</code> is used to start outgoing connections and accept incoming connections.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn server(config: ServerConfig) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
// Bind this endpoint to a UDP socket on the given server address.
let endpoint = Endpoint::server(config, SERVER_ADDR)?;
// Start iterating over incoming connections.
while let Some(conn) = endpoint.accept().await {
let connection = conn.await?;
// Save connection somewhere, start transferring, receiving data, see DataTransfer tutorial.
}
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Client</strong></p>
<p>The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.client">client()</a> returns only a <code>Endpoint</code> type.
The client needs to connect to the server using the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.connect">connect(server_name)</a> method.
The <code>SERVER_NAME</code> argument is the DNS name, matching the certificate configured in the server.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn client() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
// Bind this endpoint to a UDP socket on the given client address.
let endpoint = Endpoint::client(CLIENT_ADDR)?;
// Connect to the server passing in the server name which is supposed to be in the server certificate.
let connection = endpoint.connect(SERVER_ADDR, SERVER_NAME)?.await?;
// Start transferring, receiving data, see data transfer page.
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><br><hr></p>
<p><a href="quinn/data-transfer.html">Next up</a>, let's have a look at sending data over this connection.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="data-transfer"><a class="header" href="#data-transfer">Data Transfer</a></h1>
<p>The <a href="quinn/set-up-connection.html">previous chapter</a> explained how to set up an <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html">Endpoint</a>
and then get access to a <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html">Connection</a>.
This chapter continues with the subject of sending data over this connection.</p>
<h2 id="multiplexing"><a class="header" href="#multiplexing">Multiplexing</a></h2>
<p>Multiplexing is the act of combining data from multiple streams into a single stream.
This can have a significant positive effect on the performance of the application.
With QUIC, the programmer is in full control over the stream allocation.</p>
<h2 id="stream-types"><a class="header" href="#stream-types">Stream Types</a></h2>
<p>QUIC provides support for both stream and message-based communication.
Streams and messages can be initiated both on the client and server.</p>
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Type</th><th style="text-align: left">Description</th><th style="text-align: left">Reference</th></tr></thead><tbody>
<tr><td style="text-align: left"><strong>Bidirectional Stream</strong></td><td style="text-align: left">two way stream communication.</td><td style="text-align: left">see <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_bi">open_bi</a></td></tr>
<tr><td style="text-align: left"><strong>Unidirectional Stream</strong></td><td style="text-align: left">one way stream communication.</td><td style="text-align: left">see <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_uni">open_uni</a></td></tr>
<tr><td style="text-align: left"><strong>Unreliable Messaging (extension)</strong></td><td style="text-align: left">message based unreliable communication.</td><td style="text-align: left">see <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.send_datagram">send_datagram</a></td></tr>
</tbody></table>
</div>
<h2 id="how-to-use"><a class="header" href="#how-to-use">How to Use</a></h2>
<p>New streams can be created with <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html">Connection</a>'s <a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_bi">open_bi()</a> and
<a href="https://docs.rs/quinn/latest/quinn/struct.Connection.html#method.open_uni">open_uni()</a> methods.</p>
<h2 id="bidirectional-streams"><a class="header" href="#bidirectional-streams">Bidirectional Streams</a></h2>
<p>With bidirectional streams, data can be sent in both directions.
For example, from the connection initiator to the peer and the other way around.</p>
<p><em>open bidirectional stream</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn open_bidirectional_stream(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
let (mut send, mut recv) = connection.open_bi().await?;
send.write_all(b"test").await?;
send.finish()?;
let received = recv.read_to_end(10).await?;
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><em>iterate incoming bidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn receive_bidirectional_stream(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
while let Ok((mut send, mut recv)) = connection.accept_bi().await {
// Because it is a bidirectional stream, we can both send and receive.
println!("request: {:?}", recv.read_to_end(50).await?);
send.write_all(b"response").await?;
send.finish()?;
}
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<h2 id="unidirectional-streams"><a class="header" href="#unidirectional-streams">Unidirectional Streams</a></h2>
<p>With unidirectional streams, you can carry data only in one direction: from the initiator of the stream to its peer.
It is possible to get reliability without ordering (so no head-of-line blocking) by opening a new stream for each packet.</p>
<p><em>open unidirectional stream</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn open_unidirectional_stream(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
let mut send = connection.open_uni().await?;
send.write_all(b"test").await?;
send.finish()?;
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><em>iterating incoming unidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn receive_unidirectional_stream(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
while let Ok(mut recv) = connection.accept_uni().await {
// Because it is a unidirectional stream, we can only receive not send back.
println!("{:?}", recv.read_to_end(50).await?);
}
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<h2 id="unreliable-messaging"><a class="header" href="#unreliable-messaging">Unreliable Messaging</a></h2>
<p>With unreliable messaging, you can transfer data without reliability.
This could be useful if data arrival isn't essential or when high throughput is important.</p>
<p><em>send datagram</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn send_unreliable(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
connection.send_datagram(Bytes::from(&amp;b"test"[..]))?;
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><em>iterating datagram stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn receive_datagram(connection: Connection) -&gt; anyhow::Result&lt;()&gt; {
while let Ok(received_bytes) = connection.read_datagram().await {
// Because it is a unidirectional stream, we can only receive not send back.
println!("request: {:?}", received_bytes);
}
Ok(())
}
<span class="boring">}</span></code></pre></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</div>
</body>
</html>