feat(transport,cli,tunnel): v3.4 port auto-detect + bug fixes from live test

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>
This commit is contained in:
xah30
2026-05-29 17:14:45 +03:00
parent a173ced9b2
commit ba8d6b796f
20 changed files with 1267 additions and 110 deletions
+19 -3
View File
@@ -40,7 +40,7 @@ use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex as StdMutex};
use aura_tunnel::{RouteAction, RouteTable};
use aura_tunnel::{PacketCounters, RouteAction, RouteTable};
use ipnetwork::IpNetwork;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@@ -57,12 +57,17 @@ pub const DEFAULT_SOCKET: &str = "/tmp/aura-admin.sock";
pub const DEFAULT_SOCKET: &str = r"\\.\pipe\aura-admin";
/// Live tunnel statistics shared between the data path and the admin listener.
///
/// The two packet counters are `Arc<AtomicU64>` so the same atomics can be cloned into the
/// [`aura_tunnel::AuraRouter`] (via [`Stats::counters`]) and bumped from the data path. The admin
/// `Status` handler reads them through this struct; `aura status` sees live numbers because both
/// sides are looking at the same memory.
#[derive(Debug, Default)]
pub struct Stats {
/// Packets received from the peer (inbound, toward the TUN).
pub rx_packets: AtomicU64,
pub rx_packets: Arc<AtomicU64>,
/// Packets sent to the peer (outbound, from the TUN).
pub tx_packets: AtomicU64,
pub tx_packets: Arc<AtomicU64>,
/// Verified peer identity, set once a connection is established.
pub peer_id: StdMutex<Option<String>>,
}
@@ -79,6 +84,17 @@ impl Stats {
*g = id;
}
}
/// Hand out a [`PacketCounters`] handle pointing at the same `tx`/`rx` atomics.
///
/// The CLI passes this into [`aura_tunnel::AuraRouter::with_stats`] / the per-client server
/// router so the data path bumps the same counters the admin `Status` handler reads.
pub fn counters(&self) -> PacketCounters {
PacketCounters {
tx: Arc::clone(&self.tx_packets),
rx: Arc::clone(&self.rx_packets),
}
}
}
/// A parallel record of admin-configured rules, so `route_list` can enumerate them (the library