This commit is contained in:
anlicheng 2026-07-03 11:01:14 +08:00
parent 9f0ed421f9
commit b6607195f4
9 changed files with 284 additions and 47 deletions

270
Cargo.lock generated
View File

@ -17,6 +17,51 @@ version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "asn1-rs"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror 2.0.18",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.17.1"
@ -46,6 +91,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit-vec"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.13.0"
@ -129,12 +183,43 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "data-encoding"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
[[package]]
name = "displaydoc"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dunce"
version = "1.0.5"
@ -367,6 +452,12 @@ version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "1.2.1"
@ -378,6 +469,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@ -387,12 +488,49 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-bigint"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@ -577,14 +715,15 @@ dependencies = [
[[package]]
name = "rcgen"
version = "0.13.2"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"x509-parser",
"yasna",
]
@ -646,9 +785,18 @@ dependencies = [
[[package]]
name = "rustc-hash"
version = "2.1.2"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustls"
@ -821,11 +969,11 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.9"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@ -898,6 +1046,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -958,6 +1117,7 @@ dependencies = [
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
@ -966,6 +1126,16 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
@ -1011,44 +1181,42 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.23"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"indexmap",
"serde",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_write",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "tracing"
@ -1458,12 +1626,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.15"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
[[package]]
name = "wit-bindgen"
@ -1472,11 +1637,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "yasna"
version = "0.5.2"
name = "x509-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
dependencies = [
"asn1-rs",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"ring",
"rusticata-macros",
"thiserror 2.0.18",
"time",
]
[[package]]
name = "yasna"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [
"bit-vec",
"time",
]

View File

@ -6,11 +6,11 @@ edition = "2024"
[dependencies]
anyhow = "1"
quinn = { version = "0.11", default-features = true, features = ["runtime-tokio", "rustls"] }
rcgen = "0.13"
rcgen = "0.14.8"
rustls = "0.23"
rustls-pemfile = "2"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
toml = "0.8"
toml = "1.1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }

View File

@ -8,6 +8,7 @@ pub struct Authenticator {
}
impl Authenticator {
/// Builds an authenticator from configured username/password pairs.
pub fn new(users: &[UserConfig]) -> Self {
let users = users
.iter()
@ -25,10 +26,12 @@ impl Authenticator {
}
}
/// Returns whether there are any usable credentials configured.
pub fn is_empty(&self) -> bool {
self.users.is_empty()
}
/// Verifies the credentials carried by an open request.
pub fn authenticate(&self, request: &OpenRequest) -> bool {
let Some(expected_password) = self.users.get(request.username.as_slice()) else {
return false;
@ -38,6 +41,7 @@ impl Authenticator {
}
}
/// Compares two byte slices without early exit once their lengths match.
fn secure_equal(left: &[u8], right: &[u8]) -> bool {
if left.len() != right.len() {
return false;
@ -53,6 +57,7 @@ fn secure_equal(left: &[u8], right: &[u8]) -> bool {
mod tests {
use super::*;
/// Creates an authenticator with one known test account.
fn auth() -> Authenticator {
Authenticator::new(&[UserConfig {
username: "admin".to_owned(),
@ -60,6 +65,7 @@ mod tests {
}])
}
/// Creates an open request with caller-controlled credentials.
fn request(username: &[u8], password: &[u8]) -> OpenRequest {
OpenRequest {
host: "example.com".to_owned(),
@ -70,16 +76,19 @@ mod tests {
}
#[test]
/// Accepts a request with a configured username and matching password.
fn authenticates_known_user() {
assert!(auth().authenticate(&request(b"admin", b"secret")));
}
#[test]
/// Rejects a request when the password does not match the configured value.
fn rejects_bad_password() {
assert!(!auth().authenticate(&request(b"admin", b"wrong")));
}
#[test]
/// Rejects a request when the username is not configured.
fn rejects_unknown_user() {
assert!(!auth().authenticate(&request(b"guest", b"secret")));
}

View File

@ -33,6 +33,7 @@ pub struct TlsConfig {
}
impl Default for Config {
/// Provides production-shaped defaults matching the old Erlang listener.
fn default() -> Self {
Self {
listen: "0.0.0.0:1443".to_owned(),
@ -46,6 +47,7 @@ impl Default for Config {
}
impl Default for TlsConfig {
/// Defaults to a generated self-signed certificate for local development.
fn default() -> Self {
Self {
cert_path: None,
@ -56,6 +58,7 @@ impl Default for TlsConfig {
}
impl Config {
/// Loads configuration from TOML, or returns defaults when no path is given.
pub fn load(path: Option<&Path>) -> anyhow::Result<Self> {
let Some(path) = path else {
return Ok(Self::default());
@ -66,20 +69,24 @@ impl Config {
toml::from_str(&content).with_context(|| format!("failed to parse {}", path.display()))
}
/// Parses the configured QUIC listen address.
pub fn listen_addr(&self) -> anyhow::Result<SocketAddr> {
self.listen
.parse()
.with_context(|| format!("invalid listen address {}", self.listen))
}
/// Returns the QUIC idle timeout as a duration.
pub fn idle_timeout(&self) -> Duration {
Duration::from_millis(self.idle_timeout_ms)
}
/// Returns the outbound TCP connect timeout as a duration.
pub fn connect_timeout(&self) -> Duration {
Duration::from_millis(self.connect_timeout_ms)
}
/// Validates configuration combinations that cannot be expressed by types.
pub fn validate(&self) -> anyhow::Result<()> {
if self.max_connections == 0 {
bail!("max_connections must be greater than 0");
@ -104,11 +111,13 @@ mod tests {
use super::*;
#[test]
/// Confirms the default listener keeps the old service port.
fn parses_default_listen_addr() {
assert_eq!(Config::default().listen_addr().unwrap().port(), 1443);
}
#[test]
/// Ensures the server cannot be configured with no connection capacity.
fn rejects_zero_connection_limit() {
let config = Config {
max_connections: 0,

View File

@ -5,6 +5,7 @@ use relay_server::{config::Config, server};
use tracing_subscriber::{EnvFilter, fmt};
#[tokio::main]
/// Loads configuration, initializes logging, and starts the QUIC relay server.
async fn main() -> anyhow::Result<()> {
init_logging();
@ -21,6 +22,7 @@ async fn main() -> anyhow::Result<()> {
server::run(config).await
}
/// Installs the tracing subscriber using `RUST_LOG`, defaulting to `info`.
fn init_logging() {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
fmt().with_env_filter(filter).init();

View File

@ -24,6 +24,7 @@ pub enum ProtocolError {
}
impl fmt::Display for ProtocolError {
/// Formats protocol errors into messages suitable for logs and clients.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => write!(f, "io error: {err}"),
@ -36,6 +37,7 @@ impl fmt::Display for ProtocolError {
}
impl std::error::Error for ProtocolError {
/// Exposes the wrapped I/O error when the protocol failure came from I/O.
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(err) => Some(err),
@ -45,11 +47,13 @@ impl std::error::Error for ProtocolError {
}
impl From<io::Error> for ProtocolError {
/// Converts stream read failures into protocol errors.
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
/// Reads the initial open request from a QUIC stream.
pub async fn read_open_request<R>(reader: &mut R) -> Result<OpenRequest, ProtocolError>
where
R: AsyncRead + Unpin,
@ -75,6 +79,7 @@ where
})
}
/// Encodes an open request using the legacy Erlang payload format.
pub fn encode_open_request(
host: &str,
port: u16,
@ -108,6 +113,7 @@ pub fn encode_open_request(
Ok(out)
}
/// Writes a successful open response before raw relay bytes begin.
pub async fn write_open_ack<W>(writer: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
@ -116,6 +122,7 @@ where
writer.flush().await
}
/// Writes a failed open response with a bounded error message.
pub async fn write_open_error<W>(writer: &mut W, message: impl AsRef<[u8]>) -> io::Result<()>
where
W: AsyncWrite + Unpin,
@ -133,6 +140,7 @@ where
writer.flush().await
}
/// Reads a length-prefixed UTF-8 string field from the stream.
async fn read_string_field<R>(reader: &mut R, field: &'static str) -> Result<String, ProtocolError>
where
R: AsyncRead + Unpin,
@ -142,6 +150,7 @@ where
Ok(value.to_owned())
}
/// Reads a length-prefixed byte field from the stream.
async fn read_bytes_field<R>(reader: &mut R, _field: &'static str) -> Result<Vec<u8>, ProtocolError>
where
R: AsyncRead + Unpin,
@ -152,6 +161,7 @@ where
Ok(bytes)
}
/// Appends a big-endian unsigned 16-bit integer to an output buffer.
fn write_u16(out: &mut Vec<u8>, value: u16) {
out.extend_from_slice(&value.to_be_bytes());
}
@ -163,6 +173,7 @@ mod tests {
use super::*;
#[tokio::test]
/// Verifies compatibility with the old Erlang open-request payload.
async fn decodes_old_open_request_payload() {
let payload = encode_open_request("example.com", 443, b"admin", b"secret").unwrap();
let mut reader = payload.as_slice();
@ -176,6 +187,7 @@ mod tests {
}
#[tokio::test]
/// Ensures only the request prefix is consumed before relay bytes.
async fn leaves_following_stream_bytes_unconsumed() {
let mut payload = encode_open_request("example.com", 443, b"admin", b"secret").unwrap();
payload.extend_from_slice(b"GET / HTTP/1.1\r\n\r\n");
@ -190,6 +202,7 @@ mod tests {
}
#[tokio::test]
/// Rejects a request that encodes an invalid zero port.
async fn rejects_zero_port() {
let payload = encode_open_request("example.com", 1, b"admin", b"secret").unwrap();
let mut payload = payload;

View File

@ -20,6 +20,7 @@ pub struct RelayContext {
pub connect_timeout: Duration,
}
/// Handles one QUIC bidirectional stream as one outbound TCP relay.
pub async fn handle_stream(
remote: SocketAddr,
mut send: SendStream,
@ -68,6 +69,7 @@ pub async fn handle_stream(
}
}
/// Opens the requested target TCP connection within the configured timeout.
async fn open_tcp(request: &OpenRequest, connect_timeout: Duration) -> anyhow::Result<TcpStream> {
let addr = format!("{}:{}", request.host, request.port);
let tcp = timeout(connect_timeout, TcpStream::connect(&addr))
@ -78,6 +80,7 @@ async fn open_tcp(request: &OpenRequest, connect_timeout: Duration) -> anyhow::R
Ok(tcp)
}
/// Copies bytes between the QUIC stream and the target TCP socket.
async fn relay(
remote: SocketAddr,
request: OpenRequest,

View File

@ -11,6 +11,7 @@ use crate::{
tls,
};
/// Starts the QUIC endpoint and accepts incoming relay connections.
pub async fn run(config: Config) -> anyhow::Result<()> {
config.validate()?;
let listen_addr = config.listen_addr()?;
@ -59,6 +60,7 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
Ok(())
}
/// Accepts bidirectional streams for a single QUIC connection.
async fn handle_connection(
connection: quinn::Connection,
context: RelayContext,

View File

@ -1,11 +1,14 @@
use std::{fs::File, io::BufReader, sync::Arc};
use anyhow::{Context, bail};
use quinn::ServerConfig;
use quinn::{ServerConfig, crypto::rustls::QuicServerConfig};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use crate::config::TlsConfig;
const ALPN_RELAYKIT: &[u8] = b"relaykit/1.0";
/// Builds the QUIC server TLS configuration from files or a self-signed cert.
pub fn build_server_config(config: &TlsConfig) -> anyhow::Result<ServerConfig> {
let (certs, key) = match (&config.cert_path, &config.key_path) {
(Some(cert_path), Some(key_path)) => (
@ -16,9 +19,18 @@ pub fn build_server_config(config: &TlsConfig) -> anyhow::Result<ServerConfig> {
_ => bail!("tls cert_path and key_path must both be set, or self_signed must be true"),
};
ServerConfig::with_single_cert(certs, key).context("failed to build QUIC server config")
let mut server_crypto = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.context("failed to build rustls server config")?;
server_crypto.alpn_protocols = vec![ALPN_RELAYKIT.to_vec()];
Ok(ServerConfig::with_crypto(Arc::new(
QuicServerConfig::try_from(server_crypto).context("failed to build QUIC server config")?,
)))
}
/// Loads a PEM certificate chain for rustls.
fn load_cert_chain(path: &std::path::Path) -> anyhow::Result<Vec<CertificateDer<'static>>> {
let mut reader = BufReader::new(
File::open(path).with_context(|| format!("failed to open {}", path.display()))?,
@ -30,6 +42,7 @@ fn load_cert_chain(path: &std::path::Path) -> anyhow::Result<Vec<CertificateDer<
Ok(certs)
}
/// Loads one PEM private key for rustls.
fn load_private_key(path: &std::path::Path) -> anyhow::Result<PrivateKeyDer<'static>> {
let mut reader = BufReader::new(
File::open(path).with_context(|| format!("failed to open {}", path.display()))?,
@ -38,6 +51,7 @@ fn load_private_key(path: &std::path::Path) -> anyhow::Result<PrivateKeyDer<'sta
.with_context(|| format!("{} does not contain a private key", path.display()))
}
/// Generates an ephemeral localhost certificate for development runs.
fn generate_self_signed() -> anyhow::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>
{
let certified = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()])?;
@ -46,6 +60,7 @@ fn generate_self_signed() -> anyhow::Result<(Vec<CertificateDer<'static>>, Priva
Ok((vec![cert], key))
}
/// Applies QUIC transport settings that depend on runtime configuration.
pub fn apply_transport_config(
server_config: &mut ServerConfig,
idle_timeout: std::time::Duration,