diff --git a/smolmix/examples/tunnel_https.rs b/smolmix/examples/tunnel_https.rs index fd80126095..a789b568c4 100644 --- a/smolmix/examples/tunnel_https.rs +++ b/smolmix/examples/tunnel_https.rs @@ -59,7 +59,7 @@ async fn main() -> Result<(), BoxError> { // Hand the TLS stream to hyper for proper HTTP/1.1 handling. let (mut sender, conn) = hyper::client::conn::http1::handshake(TokioIo::new(tls)).await?; - tokio::spawn(conn); + let conn_handle = tokio::spawn(conn); let req = Request::get(path) .header("Host", host) @@ -94,6 +94,10 @@ async fn main() -> Result<(), BoxError> { let slowdown = mixnet_duration.as_millis() as f64 / clearnet_duration.as_millis().max(1) as f64; info!(" Slowdown: {slowdown:.1}x"); + // Drop the sender so hyper knows we're done, then wait for the connection task. + drop(sender); + let _ = conn_handle.await; + tunnel.shutdown().await; Ok(()) } diff --git a/smolmix/examples/tunnel_websocket.rs b/smolmix/examples/tunnel_websocket.rs index ba6230b87b..d59be5d9e7 100644 --- a/smolmix/examples/tunnel_websocket.rs +++ b/smolmix/examples/tunnel_websocket.rs @@ -47,12 +47,15 @@ async fn main() -> Result<(), BoxError> { .install_default() .expect("Failed to install rustls crypto provider"); - // Resolve hostname via clearnet DNS (smolmix-dns will handle this later). + // WARNING: This resolves the hostname via clearnet DNS, leaking the target + // to a local observer. In production, resolve DNS through the tunnel itself + // (see tunnel_dns.rs) or use smolmix-dns when available. We use clearnet + // here only because this example needs a known IP before the tunnel is up. let addr = tokio::net::lookup_host(format!("{WS_HOST}:443")) .await? .next() .ok_or("DNS resolution failed")?; - info!("Resolved {WS_HOST} -> {addr}"); + info!("Resolved {WS_HOST} -> {addr} (via clearnet DNS)"); let connector = tls_connector(); let domain = ServerName::try_from(WS_HOST)?.to_owned(); diff --git a/smolmix/src/lib.rs b/smolmix/src/lib.rs index 815f456a84..8c30d74dd6 100644 --- a/smolmix/src/lib.rs +++ b/smolmix/src/lib.rs @@ -9,7 +9,7 @@ mod error; mod tunnel; pub use error::SmolmixError; -pub use tunnel::{NetworkEnvironment, TcpStream, Tunnel, UdpSocket}; +pub use tunnel::{IpPair, NetworkEnvironment, Recipient, TcpStream, Tunnel, UdpSocket}; /// Initialise the default tracing/logging subscriber. pub fn init_logging() { diff --git a/smolmix/src/tunnel.rs b/smolmix/src/tunnel.rs index 70aa2eab41..4a2ba27ec9 100644 --- a/smolmix/src/tunnel.rs +++ b/smolmix/src/tunnel.rs @@ -12,7 +12,7 @@ use std::net::SocketAddr; use std::sync::Arc; -use nym_ip_packet_requests::IpPair; +pub use nym_ip_packet_requests::IpPair; use nym_sdk::ipr_wrapper::IpMixStream; use smoltcp::iface::Config; use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr, Ipv4Address}; @@ -27,6 +27,7 @@ use tokio_smoltcp::{Net, NetConfig}; // Re-export so users only need `use smolmix::*` — no direct dep on nym-sdk or tokio-smoltcp. pub use nym_sdk::ipr_wrapper::NetworkEnvironment; +pub use nym_sdk::mixnet::Recipient; pub use tokio_smoltcp::{TcpStream, UdpSocket}; struct ShutdownState { @@ -69,6 +70,22 @@ impl Tunnel { Self::from_stream(ipr_stream).await } + /// Create a new tunnel connected to a specific IPR exit node. + /// + /// Use this for testing against a known exit gateway, or when you want to + /// bypass automatic IPR discovery: + /// ```ignore + /// let ipr: Recipient = "gateway-address...".parse()?; + /// let tunnel = Tunnel::new_with_ipr(NetworkEnvironment::Mainnet, ipr).await?; + /// ``` + pub async fn new_with_ipr( + env: NetworkEnvironment, + ipr_address: Recipient, + ) -> Result { + let ipr_stream = IpMixStream::new_with_ipr(env, ipr_address).await?; + Self::from_stream(ipr_stream).await + } + /// Create a tunnel from a pre-configured [`IpMixStream`]. /// /// Use this if you need to customize the mixnet client (e.g. custom gateway,