diff --git a/Cargo.toml b/Cargo.toml index b9d2c8a..9afe7a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rustls = { version = "0.23", features = ["prefer-post-quantum"] } shadow-rs = "0.38" tokio = { version = "1.43", features = ["full"] } rustls-pemfile = "2.2.0" -tracing = "0.1.41" +tracing = { version = "0.1.41", features = ["log-always"] } trust-dns-resolver = { version = "0.23.2", features = ["tokio", "tokio-rustls", "rustls", "dns-over-rustls"] } [build-dependencies] diff --git a/src/main.rs b/src/main.rs index 9815f5c..5e3c9d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,8 @@ mod pman; mod quic; use std::fs::File; -use std::net::SocketAddr; +use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; use bunt::println; @@ -21,13 +22,14 @@ use log::{debug, error, info}; use pman::{init_process_manager, ProcessCommand, ProcessManager}; use shadow_rs::shadow; use std::sync::{Arc, Mutex, OnceLock}; -use anyhow::{bail, Context}; +use std::time::{Duration, Instant}; +use anyhow::{anyhow, bail, Context, Error}; use quinn::crypto::rustls::QuicServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use tokio::{signal, sync::mpsc::Sender}; +use tracing::{info_span, Instrument}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use trust_dns_resolver::{Resolver, TokioAsyncResolver}; -use crate::quic::ALPN_QUIC_HTTP; use crate::quic::server::handle_connection; shadow!(build); @@ -59,6 +61,7 @@ enum Commands { #[command(about = "Start client as GUI")] GuiClient, Devtest, + #[command(about = "Start server")] Server { /// file to log TLS keys to for debugging #[clap(long = "keylog")] @@ -121,6 +124,7 @@ async fn main() -> anyhow::Result<()> { match args.command { Commands::CliClient => { + let custom_certs_fut = read_opt_server_cert(); let host = config().get_string("remote_host")?; let port = config().get::("remote_port")?; let resolver = TokioAsyncResolver::tokio( @@ -129,10 +133,52 @@ async fn main() -> anyhow::Result<()> { ); let response = resolver.lookup_ip(&host).await?; let ip_addr = response.iter().next().expect("no addresses returned!"); - let ip_sock = SocketAddr::new(ip_addr, port); - let client_endpoint = quic::client::make_client_endpoint(ip_sock.clone(), None).unwrap(); - let client = client_endpoint.connect(ip_sock, &host)?.await?; + info!("Remote host {} resolved to {:?}", host, ip_addr); + let server_sock = SocketAddr::new(ip_addr, port); + let client_sock = if server_sock.is_ipv6() { + SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0,0,0,0,0,0,0,1)), 0) + } else { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 0) + }; + info!("Client address: {:?}", client_sock); + let custom_certs = custom_certs_fut.await?; + let client_endpoint = quic::client::make_client_endpoint(client_sock.clone(), custom_certs)?; + let client = client_endpoint.connect(server_sock, &host)?.await?; println!("[client] connected: addr={}", client.remote_address()); + loop { + let stream = client.open_bi().await; + let (mut send, mut recv) = match stream { + Err(quinn::ConnectionError::ApplicationClosed { .. }) => { + info!("connection closed"); + return Ok(()); + } + Err(e) => { + return bail!(e); + } + Ok(s) => s, + }; + send.write_all("ping".as_bytes()).await.expect("TODO: panic message"); + send.finish()?; + + let response_start = Instant::now(); + let resp = recv + .read_to_end(usize::MAX) + .await + .map_err(|e| anyhow!("failed to read response: {}", e))?; + + let duration = response_start.elapsed(); + eprintln!( + "response received in {:?} - {} KiB/s", + duration, + resp.len() as f32 / (duration_secs(&duration) * 1024.0) + ); + std::io::stdout().write_all(&resp).unwrap(); + std::io::stdout().flush().unwrap(); + client.close(0u32.into(), b"done"); + + // Give the server a fair chance to receive the close packet + client_endpoint.wait_idle().await; + } } Commands::Shadow => { @@ -215,7 +261,6 @@ async fn main() -> anyhow::Result<()> { //TODO change to auth .with_no_client_auth() .with_single_cert(certs, key)?; - server_crypto.alpn_protocols = ALPN_QUIC_HTTP.iter().map(|&x| x.into()).collect(); if keylog { server_crypto.key_log = Arc::new(rustls::KeyLogFile::new()); } @@ -226,7 +271,7 @@ async fn main() -> anyhow::Result<()> { transport_config.max_concurrent_uni_streams(0_u8.into()); let endpoint = quinn::Endpoint::server(server_config, listen)?; - eprintln!("listening on {}", endpoint.local_addr()?); + eprintln!("listening on {}", endpoint.local_addr().unwrap()); while let Some(conn) = endpoint.accept().await { if connection_limit @@ -257,3 +302,24 @@ async fn main() -> anyhow::Result<()> { println!("exit"); Ok(()) } + +async fn read_opt_server_cert() -> anyhow::Result>> { + let cert_path_res = config().get_string("remote_cert"); + if cert_path_res.is_err() { + return Ok(None); + } + let cert_path = cert_path_res?; + let res = tokio::fs::read(cert_path).await; + if let Err(e) = res { + error!("failed to read certificate: {reason}", reason = e.to_string()); + Err(Error::from(e)) + } else { + info!("read certificate successfully read"); + Ok(Some(res?)) + } +} + + +fn duration_secs(x: &Duration) -> f32 { + x.as_secs() as f32 + x.subsec_nanos() as f32 * 1e-9 +} \ No newline at end of file diff --git a/src/quic/client.rs b/src/quic/client.rs index e65d327..9fd68ba 100644 --- a/src/quic/client.rs +++ b/src/quic/client.rs @@ -1,6 +1,8 @@ use std::error::Error; use std::net::SocketAddr; use std::sync::Arc; +use anyhow::anyhow; +use log::info; use quinn::{ClientConfig, Endpoint}; use rustls::pki_types::CertificateDer; @@ -10,13 +12,11 @@ use rustls::pki_types::CertificateDer; /// /// - server_certs: a list of trusted certificates in DER format. fn configure_client( - server_certs: Option<&[&[u8]]>, + server_certs: Option>, ) -> anyhow::Result { if let Some(server_certs) = server_certs { let mut certs = rustls::RootCertStore::empty(); - for cert in server_certs { - certs.add(CertificateDer::from(*cert))?; - } + certs.add(CertificateDer::from(server_certs.as_slice()))?; Ok(ClientConfig::with_root_certificates(Arc::new(certs))?) } else { @@ -32,10 +32,35 @@ fn configure_client( #[allow(unused)] pub fn make_client_endpoint( bind_addr: SocketAddr, - server_certs: Option<&[&[u8]]>, + server_certs: Option>, ) -> anyhow::Result { let client_cfg = configure_client(server_certs)?; let mut endpoint = Endpoint::client(bind_addr)?; endpoint.set_default_client_config(client_cfg); Ok(endpoint) +} + +pub(crate) async fn handle_request( + (mut send, mut recv): (quinn::SendStream, quinn::RecvStream), +) -> anyhow::Result<()> { + let req = recv + .read_to_end(64 * 1024) + .await + .map_err(|e| anyhow!("failed reading request: {}", e))?; + let mut escaped = String::new(); + for &x in &req[..] { + let part = std::ascii::escape_default(x).collect::>(); + escaped.push_str(std::str::from_utf8(&part)?); + } + tracing::info!(content = %escaped); + // Execute the request + let resp = "ping {}"; + // Write the response + send.write_all(&resp.as_bytes()) + .await + .map_err(|e| anyhow!("failed to send response: {}", e))?; + // Gracefully terminate the stream + send.finish()?; + info!("complete"); + Ok(()) } \ No newline at end of file diff --git a/src/quic/mod.rs b/src/quic/mod.rs index 2fd7691..92b78e2 100644 --- a/src/quic/mod.rs +++ b/src/quic/mod.rs @@ -1,4 +1,2 @@ pub(crate) mod server; -pub(crate) mod client; - -pub const ALPN_QUIC_HTTP: &[&[u8]] = &[b"hq-29"]; \ No newline at end of file +pub(crate) mod client; \ No newline at end of file diff --git a/src/quic/server.rs b/src/quic/server.rs index b2e11f3..4eab9f2 100644 --- a/src/quic/server.rs +++ b/src/quic/server.rs @@ -95,7 +95,7 @@ pub async fn handle_connection(conn: quinn::Incoming) -> anyhow::Result<()> { Ok(()) } -async fn handle_request( +pub(crate) async fn handle_request( (mut send, mut recv): (quinn::SendStream, quinn::RecvStream), ) -> anyhow::Result<()> { let req = recv