//! `aura client`: connect to an Aura server and route host traffic through the tunnel. //! //! ## v1 data path //! 1. Load `client.toml`, read the `[pki]` PEM files, build [`aura_proto::ClientConfig`]. //! 2. Build a shared [`RouteTable`] from `[tunnel.split]` (default action + direct/vpn CIDR rules); //! record domain rules for resolution. //! 3. [`AuraClient::connect`] to `[client] server_addr`, presenting `[client] sni` as the outer //! (mimicry) hostname. //! 4. Resolve any split-tunnel domain rules via [`AuraDns`] into host routes (best-effort). //! 5. Create the local TUN ([`AuraTun::create`]) on `[tunnel] local_ip/prefix` and run //! [`AuraRouter`] to bridge the TUN and the connection. //! 6. Start the admin IPC listener over the same shared [`RouteTable`] + [`Stats`]. //! //! ## Privilege / scope notes (NOT auto-tested) //! * Creating the TUN ([`AuraTun::create`]) needs **root**; the live router path runs only in a //! privileged execution, so it is not covered by unit tests (the loopback test covers the //! connection path short of the TUN). //! * Domain resolution performs real DNS queries and so is not unit-tested either. use std::path::Path; use std::sync::Arc; use anyhow::Context; use aura_transport::AuraClient; use aura_tunnel::{AuraDns, AuraRouter, AuraTun}; use tokio::sync::RwLock; use crate::admin::{self, AdminState, Stats}; use crate::config::ClientConfigFile; /// Entry point for `aura client --config ` (and optional `--admin-socket`). pub async fn run(config_path: &Path, admin_socket: &str) -> anyhow::Result<()> { let cfg = ClientConfigFile::load(config_path)?; let server_addr = cfg.server_socket_addr()?; let local_ip = cfg.local_ip()?; let proto_cfg = cfg.to_proto()?; let (table, domains) = cfg.build_route_table()?; tracing::info!( name = %cfg.client.name, %server_addr, sni = %cfg.client.sni, %local_ip, dns = ?cfg.tunnel.dns, mimicry_padding = cfg.mimicry.padding, "starting Aura client" ); // Snapshot the configured CIDR rules for the admin mirror before moving the table behind the // lock. (We rebuild the parsed CIDRs from the config rather than reaching into the table.) let cidr_mirror = collect_cidr_rules(&cfg); let routes = Arc::new(RwLock::new(table)); let stats = Arc::new(Stats::new()); // Connect (outer QUIC + inner Aura mutual-auth handshake). let conn = AuraClient::connect(server_addr, &cfg.client.sni, proto_cfg) .await .context("connecting to Aura server")?; let peer = conn.peer_id().map(str::to_owned); stats.set_peer_id(peer.clone()); tracing::info!(peer = ?peer, "connected and authenticated to server"); // Resolve split-tunnel domain rules into host routes (best-effort; failures are logged). if !domains.is_empty() { match AuraDns::new(Arc::clone(&routes)).await { Ok(mut dns) => { for (domain, action) in &domains { match dns.resolve_and_register(domain, *action).await { Ok(ips) => { tracing::info!( domain, count = ips.len(), ?action, "resolved domain rule" ) } Err(e) => { tracing::warn!(domain, error = %e, "failed to resolve domain rule") } } } } Err(e) => tracing::warn!(error = %e, "could not start resolver for domain rules"), } } // Admin IPC over the shared table/stats. let admin_state = AdminState::new( Arc::clone(&routes), Arc::clone(&stats), cidr_mirror, domains.clone(), ); let admin_path = admin_socket.to_string(); tokio::spawn(async move { if let Err(e) = admin::serve(&admin_path, admin_state).await { tracing::error!(error = %e, "admin IPC listener exited"); } }); // Create the TUN and run the router (needs root). let tun = AuraTun::create( &cfg.tunnel.tun_name, local_ip, cfg.tunnel.prefix, cfg.tunnel.mtu, ) .await .context("creating TUN device (needs root)")?; tracing::info!(tun = %cfg.tunnel.tun_name, "TUN device up; routing traffic"); let router = AuraRouter::new(tun, routes, conn.into_dyn()); router.run().await.context("router run loop")?; Ok(()) } /// Re-parse the `[tunnel.split]` CIDR rules into `(IpNetwork, RouteAction)` pairs for the admin /// mirror. Invalid CIDRs were already rejected by [`ClientConfigFile::build_route_table`], so this /// silently skips any that somehow fail to re-parse. fn collect_cidr_rules( cfg: &ClientConfigFile, ) -> Vec<(ipnetwork::IpNetwork, aura_tunnel::RouteAction)> { use aura_tunnel::RouteAction; let mut out = Vec::new(); for (rules, action) in [ (&cfg.tunnel.split.direct, RouteAction::Direct), (&cfg.tunnel.split.vpn, RouteAction::Vpn), ] { for rule in rules { if let Some(cidr) = &rule.cidr { if let Ok(net) = cidr.parse() { out.push((net, action)); } } } } out }