From b6607195f4a79b50815798de28db9794fe5aa419 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Fri, 3 Jul 2026 11:01:14 +0800 Subject: [PATCH] fix deps --- Cargo.lock | 270 ++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 4 +- src/auth.rs | 9 ++ src/config.rs | 9 ++ src/main.rs | 2 + src/protocol.rs | 13 +++ src/relay.rs | 3 + src/server.rs | 2 + src/tls.rs | 19 +++- 9 files changed, 284 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc7434a..e90566d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 27d4d15..e9a82c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/auth.rs b/src/auth.rs index 0f859e4..55f8744 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -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"))); } diff --git a/src/config.rs b/src/config.rs index eef3cee..266494d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 { 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 { 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, diff --git a/src/main.rs b/src/main.rs index eb08abe..afa1b5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(); diff --git a/src/protocol.rs b/src/protocol.rs index 0845def..1da023e 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -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 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(reader: &mut R) -> Result 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(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(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(reader: &mut R, field: &'static str) -> Result 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(reader: &mut R, _field: &'static str) -> Result, 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, 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; diff --git a/src/relay.rs b/src/relay.rs index e3b3c73..874c3ce 100644 --- a/src/relay.rs +++ b/src/relay.rs @@ -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 { 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, diff --git a/src/server.rs b/src/server.rs index d206557..04686c5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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, diff --git a/src/tls.rs b/src/tls.rs index 914a37d..fe1b096 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -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 { 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 { _ => 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>> { 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 anyhow::Result> { 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 anyhow::Result<(Vec>, PrivateKeyDer<'static>)> { let certified = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()])?; @@ -46,6 +60,7 @@ fn generate_self_signed() -> anyhow::Result<(Vec>, 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,