diff --git a/Cargo.lock b/Cargo.lock index b0d4590..8b1c86a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -665,6 +665,7 @@ dependencies = [ "dirs", "env_logger", "futures", + "libc", "log", "quinn", "quinn-proto", diff --git a/src/args/mod.rs b/src/args/mod.rs index 0b078d2..2a15c69 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::collections::HashMap; use std::env::var; use std::net::{SocketAddr, ToSocketAddrs}; @@ -12,11 +12,11 @@ pub fn parse_env_addr() -> Result<(SocketAddr, SocketAddr)> { let ss_local_addr = format!("{}:{}", ss_local_host, ss_local_port) .to_socket_addrs()? .next() - .unwrap(); + .ok_or_else(|| anyhow!("resolved no local socket address"))?; let ss_remote_addr = format!("{}:{}", ss_remote_host, ss_remote_port) .to_socket_addrs()? .next() - .unwrap(); + .ok_or_else(|| anyhow!("resolved no remote socket address"))?; Ok((ss_local_addr, ss_remote_addr)) } @@ -39,12 +39,15 @@ pub fn parse_env_opts() -> Result> { fn parse_plugin_options(options: &str) -> HashMap { let mut plugin_options = HashMap::::new(); - let opts: Vec<&str> = options.split(';').collect(); - // FIXME: backslash is not escaped in this plugin - for opt in opts { - let o: Vec<&str> = opt.splitn(2, '=').collect(); - plugin_options.insert(o[0].to_string(), o[1].to_string()); + for opt in options.split(';').filter(|s| !s.is_empty()) { + let mut parts = opt.splitn(2, '='); + let Some(key) = parts.next() else { continue }; + if key.is_empty() { + continue; + } + let value = parts.next().unwrap_or_default(); + plugin_options.insert(key.to_string(), value.to_string()); } plugin_options @@ -84,6 +87,15 @@ mod tests { assert_eq!(opts.get("secret").unwrap(), "second"); } + #[test] + fn test_parse_plugin_options_malformed_is_ignored_or_empty() { + let opts = parse_plugin_options("host=example.com;flag_only;=no_key;empty="); + assert_eq!(opts.get("host").unwrap(), "example.com"); + assert_eq!(opts.get("flag_only").unwrap(), ""); + assert_eq!(opts.get("empty").unwrap(), ""); + assert!(opts.get("").is_none()); + } + #[test] #[serial] fn test_parse_env_addr_valid() { diff --git a/src/client.rs b/src/client.rs index 4972dff..e55274c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,7 +7,7 @@ use tokio::net::{TcpListener, TcpStream}; use anyhow::{anyhow, Result}; use clap::Parser; use futures::future::try_join; -use log::{error, info}; +use log::{error, info, warn}; use quinn::ConnectionError; use quinn::Endpoint; @@ -92,8 +92,14 @@ async fn main() -> Result<()> { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), }; - for certs in rustls_native_certs::load_native_certs().expect("could not load platform certs") { - roots.add(certs).unwrap(); + let native_certs = rustls_native_certs::load_native_certs(); + for err in native_certs.errors { + warn!("error loading native cert: {:?}", err); + } + for cert in native_certs.certs { + if let Err(err) = roots.add(cert) { + warn!("skipping invalid native cert: {:?}", err); + } } let mut client_crypto = rustls::ClientConfig::builder() @@ -143,32 +149,32 @@ async fn transfer( mut inbound: TcpStream, vpn_mode: bool, ) -> Result<()> { - let new_conn = endpoint - .connect(*remote, &host)? - .await - .map_err(|e| { - if e == ConnectionError::TimedOut { - match create_udp_socket(vpn_mode) { - Ok(socket) => { - let addr = socket.local_addr().unwrap(); - let ret = endpoint.rebind(socket); - match ret { - Ok(_) => { + let new_conn = endpoint.connect(*remote, &host)?.await.map_err(|e| { + if e == ConnectionError::TimedOut { + match create_udp_socket(vpn_mode) { + Ok(socket) => { + let addr = socket.local_addr().ok(); + let ret = endpoint.rebind(socket); + match ret { + Ok(_) => { + if let Some(addr) = addr { info!("rebinding to: {}", addr); - } - Err(e) => { - error!("rebind fail: {:?}", e); + } else { + info!("rebinding to new local socket"); } } - } - Err(e) => { - error!("failed to create socket for rebind: {:?}", e); + Err(e) => { + error!("rebind fail: {:?}", e); + } } } + Err(e) => { + error!("failed to create socket for rebind: {:?}", e); + } } - anyhow!("failed to connect: {:?}", e) - }) - .unwrap(); + } + anyhow!("failed to connect: {:?}", e) + })?; let (mut ri, mut wi) = inbound.split(); let (mut wo, mut ro) = new_conn diff --git a/src/server.rs b/src/server.rs index 5aee92c..82f7371 100644 --- a/src/server.rs +++ b/src/server.rs @@ -147,7 +147,9 @@ async fn main() -> Result<()> { conn.refuse(); } else if options.stateless_retry && !conn.remote_address_validated() { info!("requiring connection to validate its address"); - conn.retry().unwrap(); + if let Err(e) = conn.retry() { + error!("failed to request stateless retry: {:?}", e); + } } else { info!("accepting connection"); let fut = handle_connection(remote.clone(), conn);