ba8d6b796f
Live macOS test against the production server uncovered six bugs (one of which turned out to be a port collision with sing-box, not a real bug); this commit addresses all of them and adds v3.4 port discovery so the same collision is handled transparently next time. ## v3.4 server port-discovery - Defaults moved off 443/444 to 8443/8443/8444 (TransportSection::default, ServerInitOpts, ProvisionClientOpts, CLI flags). 443 is heavily contested in practice (sing-box, Hysteria2, reverse proxies) and the previous default silently lost the bind when a co-tenant was already there. - MultiServer::bind_with_outer_or_scan: scans forward up to DEFAULT_PORT_SCAN_MAX (20) candidates per transport when the requested port is occupied; QUIC keeps walking if it lands on the custom-UDP port. - MultiServer::bound_addrs(): the actual addresses each transport bound to. - Server logs the bound addresses and writes a runtime snapshot (server.toml.runtime.json) when they differ from the requested ones, so `aura sign-bridges` can re-sign the bridges manifest later. - BridgeManifest gains an optional `endpoints: Vec<BridgeEndpoint>` field with per-transport ports. Backward-compatible: old v3.3 clients ignore the field and continue to use the v1 `bridges` line. - `aura sign-bridges --endpoints HOST:tcp=N:quic=N:udp=N` to mint v3.4 manifests; bridges line is auto-synthesised for v3.3 clients. ## Bug fixes from the live test - macOS TUN naming (#41): the tun crate rejects names that don't match ^utun[0-9]+$. On macOS we now substitute `""` (kernel auto-assigns utunN), capture the assigned name via inner.tun_name(), and propagate it through to os_routes::OsRouteGuard::install — so `route add -interface utunN` uses the real interface, not "aura0". - Packet counters (#42): Stats { tx_packets, rx_packets } are now actually bumped by the data path. `aura status` shows live numbers instead of permanent zeros. - render_client_toml schema (#44): provisioner emits proper `[[tunnel.split.vpn]] cidr = "..."` / `[[tunnel.split.direct]]` blocks from new --vpn-cidrs / --direct-cidrs flags. The v3.3 `vpn_cidrs = [...]` flat array was silently ignored by serde, leaving users with `rules: 0` even when their CIDRs looked right. - #43 / #46 (TCP/443 dial early-eof / no payload back): diagnosed as the sing-box port collision, not an Aura bug. The v3.4 port-scan path makes it go away — the server picks a free port and clients learn it from the manifest. ## Test coverage Three new unit tests for the port-scanner (UDP busy, TCP busy, zero budget); two new tests for v3.4 BridgeManifest round-trip with endpoints; one integration test for the new `[[tunnel.split.vpn]]` rendering; tests for the runtime-state file write/read round-trip; agent-added router-counter tests in aura-tunnel/tests/routes.rs. cargo test --workspace, cargo clippy --workspace -- -D warnings, and cargo fmt --check all pass. #45 (silent client exit when underlying QUIC transport breaks) is still outstanding — needs deeper investigation; deferred to a follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
87 lines
3.4 KiB
Rust
87 lines
3.4 KiB
Rust
//! aura-tunnel — the Aura VPN data-plane tunnel (project §8).
|
|
//!
|
|
//! This crate turns a host's IP traffic into something the encrypted transport can carry, and back
|
|
//! again. It has four pieces:
|
|
//!
|
|
//! * [`AuraTun`] — a cross-platform layer-3 TUN device (Linux/macOS via the `tun` crate; Windows via
|
|
//! `wintun`, `cfg`-gated). See [`tun`](mod@crate::tun).
|
|
//! * [`RouteTable`] / [`RouteAction`] — a longest-prefix-match split-tunnel routing table deciding
|
|
//! VPN-vs-direct per destination IP. See [`routes`](mod@crate::routes).
|
|
//! * [`AuraDns`] — a hickory-backed resolver that registers resolved domain addresses as host
|
|
//! routes in a shared [`RouteTable`]. See [`dns`](mod@crate::dns).
|
|
//! * [`AuraRouter`] — the run-loop bridging the TUN device and an
|
|
//! [`aura_proto::PacketConnection`]. See [`router`](mod@crate::router).
|
|
//!
|
|
//! ## Wiring it together (for the CLI)
|
|
//!
|
|
//! The router is generic over the [`PacketIo`] device seam and shares the routing table and the
|
|
//! packet connection by `Arc`:
|
|
//!
|
|
//! ```no_run
|
|
//! # async fn demo(conn: std::sync::Arc<dyn aura_proto::PacketConnection>) -> anyhow::Result<()> {
|
|
//! use std::sync::Arc;
|
|
//! use tokio::sync::RwLock;
|
|
//! use aura_tunnel::{AuraDns, AuraRouter, AuraTun, RouteAction, RouteTable};
|
|
//!
|
|
//! // 1. Build a shared routing table (default: everything through the VPN).
|
|
//! let routes = Arc::new(RwLock::new(RouteTable::new(RouteAction::Vpn)));
|
|
//! routes.write().await.add_cidr("192.168.0.0/16".parse()?, RouteAction::Direct);
|
|
//!
|
|
//! // 2. Optionally resolve domains into host routes.
|
|
//! let mut dns = AuraDns::new(Arc::clone(&routes)).await?;
|
|
//! dns.resolve_and_register("example.com", RouteAction::Direct).await?;
|
|
//!
|
|
//! // 3. Create the TUN device (needs privileges).
|
|
//! let tun = AuraTun::create("aura0", "10.7.0.2".parse()?, 24, 1420).await?;
|
|
//!
|
|
//! // 4. Build the router from the TUN, the table, and the connection, then run it.
|
|
//! let router = AuraRouter::new(tun, routes, conn);
|
|
//! router.run().await?;
|
|
//! # Ok(())
|
|
//! # }
|
|
//! ```
|
|
|
|
#![cfg_attr(not(windows), forbid(unsafe_code))]
|
|
#![warn(missing_docs)]
|
|
|
|
pub mod dns;
|
|
pub mod router;
|
|
pub mod routes;
|
|
pub mod tun;
|
|
|
|
pub use dns::AuraDns;
|
|
pub use router::{dst_ip, AuraRouter, PacketCounters};
|
|
pub use routes::{RouteAction, RouteTable};
|
|
pub use tun::{AuraTun, PacketIo};
|
|
|
|
use thiserror::Error;
|
|
|
|
/// Errors produced by the tunnel data plane.
|
|
///
|
|
/// The router and DNS surfaces mostly return [`anyhow::Result`] (they compose I/O, the `tun`/`wintun`
|
|
/// backends, hickory, and the [`aura_proto::PacketConnection`] contract, all of which already carry
|
|
/// rich context). This enum names the tunnel-specific failure modes for callers that want to match
|
|
/// on them, and converts cleanly from the underlying I/O and resolver errors.
|
|
#[derive(Debug, Error)]
|
|
pub enum TunnelError {
|
|
/// Creating or configuring the TUN/wintun device failed.
|
|
#[error("TUN device error: {0}")]
|
|
Device(String),
|
|
|
|
/// An I/O error while reading from or writing to the TUN device.
|
|
#[error("TUN I/O error: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// The requested TUN address/prefix was not a valid network.
|
|
#[error("invalid TUN address or prefix: {0}")]
|
|
InvalidAddress(#[from] ipnetwork::IpNetworkError),
|
|
|
|
/// DNS resolution failed.
|
|
#[error("DNS resolution error: {0}")]
|
|
Dns(#[from] hickory_resolver::error::ResolveError),
|
|
|
|
/// The underlying encrypted packet connection failed.
|
|
#[error("packet connection error: {0}")]
|
|
Connection(String),
|
|
}
|