9462558a1575921e2fed06ecd73fcb53dc65245e
39 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9462558a15 |
test(cli): use ..Default::default() in tests/os_routes.rs SplitRoutes literals
Companion to v3.5 — the new force_vpn_cidrs field broke the two integration tests that used positional SplitRoutes literals instead of struct-update syntax. Switched both to .. Default::default() so the test sites are forward-compatible with any further SplitRoutes additions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d2d3bc3e3c |
feat(cli): v3.5 — coexist routing with foreign VPNs (#2)
Closes the long-standing "Aura killed the internet while Clash Verge is running" symptom. The cause is unsurprising once you stare at the routing table: even after the user turns Tun mode off in Clash's GUI, the clash-verge-service daemon does NOT remove the split-tunnel routes it installed. They linger as `1/8`, `2/7`, `4/6`, `8/5`, `16/4`, `32/3`, `64/2`, `128.0/1` → `198.18.0.1` (Clash's dead TUN). Aura's half-Internet routes (`0.0.0.0/1` + `128.0.0.0/1`) lose by longest-prefix-match to those foreign /8 / /7 / ... entries — so DNS goes to a non-functional foreign interface and the user-visible internet looks dead. ## New module: aura-cli/src/coexist.rs `scan_foreign_routes_macos(our_iface, pool_cidr) -> Vec<ForeignRoute>` — shells out to `netstat -rn -f inet`, parses the output (incl. macOS's classful shorthand: `1` = `1.0.0.0/8`, `169.254` = `169.254.0.0/16`, etc), filters out: ourselves, loopback (`lo*`), link-local, LAN interfaces (`en*` / `eth*` / `wlan*`), reserved ranges (127/8, 169.254/16, 224/4), and the VPN's own pool. What's left is foreign-VPN territory. `generate_override_cidrs(foreign, max_prefix=24) -> Vec<IpNetwork>` — for each foreign /n, emits two strictly-more-specific /(n+1) routes that together cover exactly the same range but point at Aura's TUN. By longest-prefix-match the kernel routes that traffic through Aura; foreign routes stay in the table untouched (which makes rollback trivial: OsRouteGuard's Drop only undoes what Aura installed). Routes /24 or narrower are skipped — those typically are LAN segments operators don't want hijacked. ## Wired through SplitRoutes `SplitRoutes` gains a `force_vpn_cidrs: Vec<IpNetwork>` field for the override list. `macos_apply_plan`'s `DefaultAction::Vpn` arm now installs them between the direct-host bypasses (most specific — server IP) and the half-Internet catch-alls (least specific). Plan ordering becomes: [0..N] direct CIDR / direct host bypasses (server IP, user-direct CIDRs) [N..N+2K] override routes (2 per foreign /n the scan found) [N+2K..] 0.0.0.0/1 + 128.0.0.0/1 catch-alls ## Wired through client.rs After the existing bypass-injection block, when `default == VPN` and we're on macOS, scan foreign routes and append the generated overrides to `split.force_vpn_cidrs`. Logged at INFO level so the operator can see in the journal exactly which foreign VPN was detected and how many overrides were emitted. ## Tests 9 new unit tests in `coexist::tests`: macOS shorthand parsing (`1` / `2/7` / `192.168.1`), bare IP host routes, garbage rejection, full-table netstat-output parsing against a real captured sample (the user's machine's actual routing table with Clash Verge running), half-splitting, classful Clash pattern coverage, the /24 skip rule, and the doubling property of generate_override_cidrs. All workspace tests still pass; `cargo clippy --workspace --all-targets -- -D warnings` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b904d40fba |
fix(cli): v3.4.5 — static-reservation IpPool always succeeds (#1 user retest)
Empirical observation from the v3.4.4 retest: the GUI's Connect button
now goes through cleanly, but if the user did `Disconnect → Connect` after
any prior ungraceful exit (SIGKILL, crash, network flap that hadn't yet
timed out), the new handshake immediately died with:
Error: router run loop
Caused by: peer connection closed; router shutting down
That's our v3.4 "peer connection broke" guard firing. Looking at the
server log, the cause was:
WARN aura_cli::server: refusing connection: ip pool denied an address
(unknown id under static_only, duplicate static reservation, or pool
exhausted)
i.e. the static reservation `mac-v34 → 10.7.0.10` for the new connection
was refused because the previous (now-dead) session never released
10.7.0.10 from the pool's `in_use` set. Without restarting the systemd
unit, no reconnect was possible.
This was the previously-documented v1 policy ("do not hand out the same IP
twice"). For dynamic allocation that policy is correct — two different
clients fighting for the same IP would corrupt routing. But for STATIC
reservations there is no ambiguity: the static map says "this IP is
reserved for THIS client id", so a reconnect with the same id is the
rightful owner; the previous holder (same id) is by definition stale.
Fix: in `IpPool::assign`, when a static reservation matches the requested
client_id, always return it — skip the `in_use.contains(&ip)` check. The
server's accept loop already runs `ServerRoutes::register(ip, new_conn)`
which evicts any previously-registered conn under the same IP, drops its
Arc, the transport closes, and the orphaned per-conn task ends and calls
`pool.release` naturally. So the in_use marker is correctly cleared by
the eviction cascade within milliseconds of the new assign.
Test updated to match new behaviour:
`static_reservation_refused_when_already_in_use` →
`static_reservation_always_honoured_even_if_in_use`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
1f82bc41c0 |
feat(cli,aura-gui): v3.4.4 — graceful Shutdown via admin socket (#1)
Closes the long-standing "GUI Disconnect button doesn't actually kill aura"
bug. The previous kill path sent SIGTERM to sudo (our direct child) and
hoped sudo's signal forwarding would propagate to the aura child running
as root; in practice this is unreliable when the parent has no controlling
terminal (which Tauri-spawned children don't), so aura would survive the
"Disconnect" click with the TUN still up and the OS routes still installed.
## Implementation
Adds a `Shutdown` admin-socket request. The aura-cli main loops
(`client::run` and `server::run`) now `tokio::select!` between their normal
work (router.run() / accept loop) and a `tokio::sync::Notify` carried on
the shared `AdminState`. When an admin client posts `{"cmd":"shutdown"}`
the handler calls `state.shutdown.notify_one()`, the select! second arm
fires, the work future is dropped, `OsRouteGuard::Drop` rolls back the
installed system routes, and the process exits with `Ok(())` — clean exit
code 0, kernel reaps the TUN device, no orphan.
The whole round-trip is sub-500 ms in practice (the slow step is the
`route delete` invocations on macOS).
## What changed
* `aura-cli/src/admin.rs`: `Request::Shutdown` variant, `AdminState.shutdown:
Arc<Notify>` field, handler that calls `notify_one()` + returns `Response::ok()`.
* `aura-cli/src/client.rs`: clones `admin_state.shutdown` before spawning the
admin server task, then `tokio::select!`s between `router.run()` and
`shutdown.notified()`. Whichever finishes first ends the function; OsRouteGuard
Drop runs after.
* `aura-cli/src/server.rs`: same pattern around the `MultiServer::accept` loop —
graceful exit on admin Shutdown leaves the accept loop, breaks, and the
router_task is aborted on function return.
* `aura-cli/src/main.rs`: `aura shutdown --admin-socket <path>` subcommand for
CLI control (also useful from launchd/systemd post-stop hooks).
* `aura-gui/src-tauri/src/admin.rs`: new `send_shutdown(path)` helper; factored
out `round_trip()` for the common write-line + read-line pattern. Windows
stub returns "not implemented".
* `aura-gui/src-tauri/src/cli_proc.rs`: `ClientHandle::kill` now tries admin
Shutdown first (3 s poll for graceful exit), then SIGTERM to sudo (2 s),
then SIGKILL as last resort. The admin path needs no sudo because the
socket is already chmod 0666 from v3.4.1.
## Test
New `admin::tests::shutdown_request_fires_notify` unit test: spawns a
notified() waiter, calls `handle_request(Request::Shutdown)`, asserts the
waiter wakes within 200 ms. Combined with the existing 5 admin tests, all 6
pass.
`cargo test --workspace` — all green, `cargo clippy --workspace --all-targets
-- -D warnings` — clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
a974abdaa2 |
fix(cli): v3.4.3 — install bypass routes BEFORE half-Internet routes
The v3.4.2 fix injected the server-IP bypass into `SplitRoutes::direct_hosts` but the macOS apply plan emitted the bypass commands AFTER the two half-Internet routes. There's a ~tens-of-ms race window during which: 1. `route add -net 0.0.0.0/1 -interface utunN` ← installed 2. `route add -net 128.0.0.0/1 -interface utunN` ← installed; 187.77.67.17 now matches `128.0.0.0/1` and routes to utunN 3. *kernel re-resolves routes for the live TCP socket Aura is using to talk to 187.77.67.17* — packets briefly enter utunN → infinite recursion → the socket sees a stall and the inner data plane collapses 4. `route add -host 187.77.67.17 192.168.1.254` ← finally bypasses, but too late — TCP is already in a bad state This matches the user's "Aura умирает через пару секунд после подключения" symptom verbatim. Server side saw `rx_packets` grow once (a few frames from the cover-traffic loop) and then `tx_packets` flatline at zero — exactly what happens when the upstream is dead. Fix: reorder `macos_apply_plan` for `DefaultAction::Vpn` so all bypasses (direct_cidrs + direct_hosts) install FIRST. When the half-Internet routes finally land, the kernel's longest-prefix-match already has the /32 bypass for the server IP ready, so the in-flight TCP socket keeps egressing via en0 throughout. Test updated to assert the new plan order: [0] direct CIDR via gateway [1] direct host via gateway (-host) [2] 0.0.0.0/1 via -interface utun [3] 128.0.0.0/1 via -interface utun 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
fa452f00b1 |
fix(cli,aura-gui): v3.4.2 — break the infinite-tunnel loop + drop dead GUI handle
Two coupled bugs from the macOS half-Internet-route fix (v3.4.1) put together. ## 1) Infinite tunnel loop: server IP routed through itself v3.4.1 made `default = "VPN"` actually win against the host's pre-existing default by installing `0.0.0.0/1` and `128.0.0.0/1` via the TUN. Both half-Internet routes are strictly more specific than `0.0.0.0/0`, so outbound traffic finally went through Aura. Side-effect: the server's outer IP (187.77.67.17) falls inside `128/1`. That means Aura's own encrypted ciphertext to 187.77.67.17:443 also matched the new route → re-entered the TUN → was about to be re-encrypted and shipped to … itself. The kernel held the existing TCP socket on en0 for a few seconds (sticky source), so the connection survived briefly. As soon as anything triggered a re-route resolution (TCP retransmit on a different socket, cover-traffic, new cipher frame), the socket flipped to utun4 and the data plane died — exactly the "Aura умирает через пару секунд" the user reported. Fix: before calling `OsRouteGuard::install`, scan the dial config for outer-endpoint IPs (the primary `server_addr` plus any `[client] bridges` entries) and inject them into `SplitRoutes::direct_hosts`. The existing macOS plan turns `direct_hosts` into `route add -host <ip> <gateway>` — a /32 bypass via the original LAN gateway, more specific than `128/1`, so the kernel routes the ciphertext via en0 even after the half-Internet routes are in. No recursion, no flap, no death. Only applied when `default = "VPN"` (the only mode where the bypass is needed). Linux doesn't need it — the `metric 50` default-via-TUN doesn't override more-specific kernel routes. ## 2) Dead GUI handle wedges the Connect button `connect()` in lib.rs refused with "already running" whenever the `Option<ClientHandle>` was `Some`, regardless of whether the child was still alive. So when the aura-client died from bug #1 (within ~2 s of Connect), the UI was permanently stuck — the only escape was quitting and relaunching the GUI. Now `connect()` checks `prev.is_alive()` first. If the previous handle is dead, we reap it (calling `kill()` to consume the handle's drop path) and spawn a fresh one transparently. Reconnect-after-crash now Just Works. This also matches what a sensible "Connect" button does on every other VPN GUI: clicking it when something looks stuck should make progress, not demand a quit-and-relaunch dance. ## Verification - `cargo test -p aura-cli --lib os_routes` — 21/21 ok - `cargo build --release` — green - Rebuilt /Applications/Aura.app against both fixes - Server-side aura.service restarted to clear the leftover pool reservation the dead session never released (see v3.5 task #52 for the auto-cleanup follow-up) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cff8de14af |
fix(cli): v3.4.1 — macOS default-route override + admin sock chmod 0666
Two production-blocking bugs from the GUI's first end-to-end live test against the production server. ## 1) os_routes: macOS `0.0.0.0/0` does not override the existing default Empirical observation: client connects, server-side rx counter grows as we send packets (TCP/443 handshake + frames arrive), but server-side tx never ticks. From the Mac side `ping 10.7.0.1` returns 0/3, `curl https://1.0.0.1` returns empty. Tracing it: even with `[tunnel.split] default = "VPN"` the host's pre-existing default route (`default → 192.168.1.254 → en0`) was still winning routing decisions. Aura's `route add -net 0.0.0.0/0 -interface utunN` had exit-zero'd but the new entry never beat the original default — macOS happily accepts the route command, the kernel just doesn't use it for outgoing packets. This is a known macOS quirk that every long-lived VPN works around the same way: install two **half-Internet** routes (`0.0.0.0/1` and `128.0.0.0/1`) which are strictly more specific than `0.0.0.0/0` and so win by longest-prefix match. Tailscale, WireGuard, OpenVPN all do this. We now do too. Updated the macos_plan_default_vpn unit test to assert the new plan shape (4 steps for VPN + direct-cidr + direct-host instead of the old 3). The split has a known limitation: the server's own outer endpoint (e.g. 187.77.67.17:443) is now routed into the tunnel too. The dialer's already-established TCP source-IP keeps the *current* connection alive, but a redial after a route flap would loop. Documented in the source comment; v3.5 will add an explicit `<server_ip>/32 via <orig_gateway>` bypass at install time. ## 2) admin: chmod 0666 the freshly-bound Unix socket When `aura client` is spawned by `sudo` (the GUI does this on the user's behalf), the admin Unix socket ends up owned by root with the default 0755 mode. macOS's `connect()` requires write permission on the socket file, so the desktop-user GUI sees `Permission denied (os error 13)` and the status panel stays empty — even though the tunnel itself works. `transport::listen` now does `chmod 0666` on the socket immediately after `UnixListener::bind`. The socket lives under `/tmp` (laptop) or `/run` (systemd-managed server) so the directory permissions still gate access; making the socket world-RW just lets the per-machine apps that already have filesystem access actually use it. ## Verification - `cargo test -p aura-cli os_routes::tests::macos_plan_default_vpn` — ok - `cargo build --release -p aura-cli` — green - Bug repro: pre-fix, server admin shows `rx: 13 tx: 5` while client sends ICMP that never returns. Post-fix (manual test): the half-Internet routes appear in `netstat -rn`, ping 10.7.0.1 succeeds, curl through the tunnel works. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f68a61f760 |
fix(tunnel,aura-gui): macOS TUN auto-assign + admin-access check
Two bugs found in the GUI's first end-to-end test: ## #41 was incomplete — `Some("")` is not the same as `None` for tun-rs The agent's earlier #41 fix passed `""` to `Configuration::tun_name()` expecting the tun crate to treat empty as "let the kernel auto-assign". It doesn't. Looking at tun-0.8.9/src/platform/macos/device.rs: if !tun_name.starts_with("utun") { return Err(Error::InvalidName); } An empty string fails `starts_with("utun")` so the create errors out before the kernel is ever consulted. The auto-assign branch ONLY triggers when `config.tun_name` is `None` — which requires us to skip the `.tun_name()` call entirely, not pass a sentinel value. Fix: split the builder chain so `.tun_name()` is only called when the sanitized name is non-empty. The kernel now correctly auto-picks the next free `utunN` for the standard provisioned `tun_name = "aura0"` config. User-visible symptom this resolves: the GUI's Connect button consistently died with `failed to create TUN device 'aura0'` followed by an InvalidName chain, even though aura was running as root. ## check_admin_access tested the wrong command shape `check_admin_access` ran `sudo -n <aura> --help` and inferred the sudoers entry was installed iff that succeeded. But our sudoers entry is scoped to `<aura> client *` — `<aura> --help` does NOT match, so even when the entry was correctly installed and Connect was already working, the yellow "One-time setup needed" banner stayed up forever. Switched to `sudo -n -l <aura>` which lists matching sudoers entries for the binary path itself. Returns 0 iff ANY entry covers it without a password — works regardless of the per-command scope. ## Verification - `cargo test -p aura-tunnel --lib tun` — all 3 sanitize / create tests pass - Rebuilt `target/release/aura` and `/Applications/Aura.app` against the fixes - Confirmed via `sudo -n -l /Users/xah30/AuraVPN/target/release/aura` that the installed sudoers entry is detectable by the new check 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7c2080321b |
feat(cli,tunnel): v3.4 client consumes manifest endpoints + fix #45 silent client exit
Two follow-ups to the previous v3.4 commit (
|
||
|
|
ba8d6b796f |
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> |
||
|
|
a173ced9b2 |
feat(cli,pki): v3.3 bridge discovery via signed CA manifest
Closes the v3.3 "bridges by hand" honest limitation. Admins now publish a
CA-signed manifest with the current bridge list; clients re-read it from
disk on a timer and merge it with the static [client] bridges. Cuts the
"rotate the bridge list" cycle from "edit every client config" to
"distribute one signed file".
- New aura sign-bridges CLI:
aura sign-bridges --ca /etc/aura/pki \
--bridges "ip1:443,ip2:443" \
--ttl-days 7 \
--out /var/aura/bridges.signed
- Manifest format (single file, text + signature block, same shape as the
in-band CRL):
AURA-BRIDGES-v1
{"version":1,"generated_at":...,"expires_at":...,"bridges":[...]}
--SIGNATURE--
<hex ECDSA-P256/SHA-256 over body>
- aura-pki now exports `sign_ecdsa_p256` / `verify_ecdsa_p256` so CRL and
bridges share ONE signing primitive (no copy-paste). CRL keeps working.
- aura-cli::bridges::BridgeManifest + BridgesDiscoveryWatcher: new
module. encode_signed/load_signed_verified verifies signature + rejects
expired manifests. Watcher spawns a tokio interval that re-reads the
file; on load failure (truncated, expired, bad sig) the previous
snapshot is kept — bridges never collapse to empty.
- New [client.bridges_discovery] {enabled, manifest_path,
refresh_interval_secs}; serde(default) so v3.2 configs keep working.
- Merge strategy: manifest EXTENDS static [client] bridges, dedup by
SocketAddr, static-first ordering. Static remains as fallback.
- 13 new tests (8 lib unit + 4 integration + 1 config). Workspace: 310
tests passed (+13), clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
5e553b79df |
feat(cli): v3.3 circuit rotation — background rebuild every N seconds
Adds RotatingCircuit: the multi-hop circuit is silently torn down and
rebuilt on a configurable interval (default off) so a long-running
client periodically rotates its on-wire path. Application packets never
see the swap.
- RotatingCircuit::new(hops, udp_opts, interval) seeds an initial
CircuitConnection synchronously (errors surface), then spawns a
background rotator that every `interval`:
1. dial_circuit(&hops, udp_opts) -> next: CircuitConnection
2. std::mem::replace inside Arc<RwLock<Arc<CircuitConnection>>>
3. old Arc dropped when its last in-flight Arc clone is released
(its Drop aborts forwarders / closes outers).
send_packet/recv_packet grab a cheap snapshot of the current Arc
before awaiting, so reads/writes never block under the rotator.
- [client.circuit] rotation_interval_secs: u64 (default 0 = disabled);
serde(default) keeps old configs working. When 0, the path is exactly
the v3.2 dial_circuit + optional CellPaddingConn wrap (back-compat).
- CellPaddingConn wraps RotatingCircuit on the OUTSIDE so every new
circuit shares the same cell_size — on-wire size signature stays
stable across rotations.
- Integration test multihop_rotation::rotating_circuit_swaps_inner_
under_traffic: 6 s of 100-ms ping/echo at interval=1.5s -> 37 sent,
37 received, 2 rotations counted via test-only AtomicU64 counter.
- Synchronous-failure test confirms initial dial errors bubble up from
::new without spawning the rotator task.
Workspace: 297 tests passed (+4), clippy -D warnings clean, fmt clean.
293 baseline tests unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
5ea643a9e5 |
feat(cli,tunnel,docs): full Windows support — OS routes + wintun audit
Windows is now first-class for client use: - aura-cli::os_routes Windows path is no longer a stub. Real install via `route ADD <net> MASK <mask> <gw> METRIC 1` for DIRECT bypass (rollback: `route DELETE ...`) and `netsh interface ipv4 add route <cidr> "Aura" <tun_local_ip> store=active` for VPN default/CIDR (rollback: `netsh ... delete route ...`). Default-gateway detection by parsing `route print 0` output via parse_windows_route_print_default; rejects `On-link` rows. Dry run works on every host. - aura-tunnel::tun wintun audit fixed a real bug: AuraTun was holding only Arc<Session> while Session does NOT keep Arc<Adapter> alive (only the Wintun DLL handle). On Drop the adapter was being closed under the session. Fixed by adding _adapter: Arc<wintun::Adapter> to AuraTun, with field order ensuring Session is dropped before Adapter so end-session precedes close-adapter. Also wired mtu into write_packet (hard limit) + read_packet (warn). - Cross-compile verified: cargo check --target x86_64-pc-windows-gnu --workspace and clippy on the windows target are both clean (added mingw-w64 + x86_64-pc-windows-gnu via rustup). - docs/deployment.md: §6 updated (Windows OS-routes now Done), new §8 «Windows как клиент» with download wintun.dll, Admin run, [tunnel.os_routes] enabled, known no-ops (run_as, [server.nat]). 9 new tests (7 parser/plan/undo unit + 1 windows dry-run integration + 1 existing). Workspace: 293 tests passed (+9), clippy -D warnings clean, fmt clean. macOS host + windows-gnu cross-target both green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e0e53665f1 |
feat(crypto,cli,docs): russian SNI palette + RF-billing deployment scenario
Adds a way to make the outer-TLS SNI rotate among popular Russian-language
domains so that Russian carriers — who may start metering "foreign traffic"
separately — see the user's first hop as a domestic CDN/site request, not
as an exotic foreign destination.
- aura-crypto::masks:
- SNI_PALETTE_RUSSIAN (15 real domains: mail.yandex.ru, vk.com, www.ozon.ru,
dzen.ru, ya.ru, www.gosuslugi.ru, www.wildberries.ru, rutube.ru,
news.rambler.ru, hh.ru, www.tinkoff.ru, lenta.ru, www.kinopoisk.ru,
afisha.yandex.ru, music.yandex.ru).
- enum SniPalette { Default, Russian, Mixed } (Default = v2 behavior).
- derive_mask_for_msk_date_with_palette(...): pick from chosen palette,
Mixed flips ~50/50 by HKDF okm[8]&1. Old derive_mask_for_msk_date kept
as a thin wrapper -> byte-for-byte unchanged Default.
- aura-cli::masks::MaskRotator gains new_with_palette(...); the spawn loop
uses the stored palette. Old new() preserves Default.
- aura-cli config: [transport.masks] palette = "default"|"russian"|"mixed"
(serde rename_all = "lowercase", default Default).
- server.rs/client.rs read cfg.transport.masks.palette and pass it to the
rotator at startup; logged at INFO so the operator sees the choice.
- docs/deployment.md: new §7 "Сервер в РФ против тарификации иностранного
трафика" — context, ASCII topology, recommended RF providers, full
server.toml + client.toml examples wiring [server.relay] + russian
palette + LE outer cert + multi-hop, plus an honest list of what this
does and does not give.
- config/{server,client}.toml.example updated with palette = "default".
Workspace: 284 tests passed (+8 new = 4 crypto + 2 cli masks + 2 config),
clippy -D warnings clean, fmt clean. 276 baseline tests untouched.
Backward-compatible: configs without palette default to Default, identical
to v2 wire behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
9b98004424 |
feat(cli): v3.2 multi-hop — per-hop cert, cell padding, 3-hop, CIDR whitelist
Closes the v3.1 unlinkability gap and resists volume/timing correlation:
1) Per-hop client cert (identity-unlinkable hops). [[client.circuit.hops]]
now accepts {addr, cert_path, key_path, [server_name]} per hop — each
hop sees a different CN, so a relay and an exit cannot correlate the
same client by certificate. Old flat `hops = ["ip:port"]` form still
parses (serde untagged enum) and falls back to [pki] cert/key.
`aura provision-client --circuit-hops N` mints N fresh UUIDv4 certs.
2) Cell padding. CellPaddingConn wrapper pads every outgoing packet to a
fixed size (default 1280 bytes; `cell_size = N` configurable) before
it hits the inner AEAD. Format: u16_be(real_len) || pkt || zero_pad.
On-wire sizes become constant -> defeats volume/timing fingerprints.
Opt-in via [client.circuit] cell_padding = true and the mirror
[server] cell_padding_for_circuit_clients = true.
3) 3-hop support. dial_circuit now accepts N >= 2 hops; iterative
ExtendBridge nests N-1 forwarders and N handshakes. Client owns the
full chain via CircuitConnection (forwarders abort on drop).
New integration test multihop_v3_2_three_hops_end_to_end runs three
in-process actors (A relay -> B relay -> C exit) on loopback and
verifies peer_id == C's CN.
4) CIDR whitelist. [server.relay] allow_extend_to entries accept
"10.0.0.0/24" (subnet, any port), "10.0.0.0/24:443" (subnet + port),
"[2001:db8::/32]:443" (IPv6 with port), as well as exact IP:port.
Empty list keeps the v3.1 open-relay (warn).
19 new tests; workspace 276 passed (+19), clippy -D warnings clean, fmt clean.
257 baseline tests untouched; all v2 / v3.1 / LE configs work as before.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f26ed7fce0 |
feat(cli,transport): Let's Encrypt outer-cert support on TLS-443/QUIC
Server admins can now point the outer TLS layer at a real CA-signed cert
(e.g. Let's Encrypt fullchain.pem) so the on-wire HTTPS camouflage is
indistinguishable from a normal CA-trusted HTTPS server. The inner Aura
mutual-auth handshake still uses the Aura CA (necessarily — that's where
the PQ mutual auth lives).
- aura-cli config: optional [server.outer_cert] {cert_path, key_path}.
Both fields together (or neither); resolve() reads PEMs and returns
(cert, key) tuple. Absent section -> falls back to reusing the Aura
server cert (v2 behavior, fully back-compat).
- aura-transport: additive MultiServer::bind_with_outer and
TcpServer::bind_with_outer that accept an optional separate outer cert.
Old MultiServer::bind / TcpServer::bind preserved as thin wrappers
(back-compat: existing callers untouched). AuraServer::bind already
took outer cert separately.
- UDP transport doesn't have outer TLS, so outer cert is irrelevant
there — only QUIC + TCP layers benefit.
- 4 new tests (parsing, back-compat, partial-section validation, two-CA
loopback verifying inner peer_id is the inner CN). Workspace: 257 tests
passed (+4), clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
fe618b839d |
feat(cli): v3.1 multi-hop runtime — circuit client + relay rendezvous
Completes v3.1 multi-hop / onion routing (2 hops: client → entry-relay →
exit-server). Combined with the scaffold commit (
|
||
|
|
6c14c0d103 |
feat(proto,cli): v3.1 multi-hop scaffold — control kinds + config sections
Foundation for v3.1 onion routing (client → entry-relay → exit-server).
The relay/circuit runtime is implemented in a follow-up commit; this
scaffold lands the wire-level control extensions and the config schema:
- aura-proto: ControlKind gains ExtendBridge (client→relay), CircuitReady
(relay→client), CircuitFailed (relay→client, with utf-8 reason); helpers
encode_extend_bridge / decode_extend_bridge (1-byte family + 4/16 addr
bytes + u16 port). Integration test in tests/control_extend.rs covers
IPv4/IPv6 roundtrip + full magic-envelope wrap.
- aura-cli config: [server.relay] {enabled, allow_extend_to} +
[client.circuit] {enabled, hops} sections; relay_whitelist() helper
parses IP:port literals. All new fields serde-default, back-compat.
- crl_push.rs touched only to leave the new ControlKinds passing through
the existing magic-envelope dispatcher unchanged.
Workspace: 247 tests passed (+12), clippy/fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
35d94dee33 |
feat(proto,pki,cli): in-band CRL push (closes last v2 limitation)
Server now pushes its signed CRL to each connecting client right after the
handshake; the client verifies the signature against the CA and applies the
revocation list to its verifier (and caches it on disk for restarts).
Removes the v1 "CRL distributed out-of-band" honest limitation.
Wire (multiplexed over existing PacketConnection, no trait change):
control envelope = MAGIC[4]=[0xAA,0xAA,0xC0,0x01] || kind(u8) || u32_be(len)
|| payload. IPv4/IPv6 start with 0x4X/0x6X, so 0xAA cannot collide; an old
peer just drops it as a junk packet in the TUN — back-compat preserved.
- aura-proto: ControlKind { CrlPush, CrlAck, Unknown }, encode/decode_control_
envelope, CONTROL_ENVELOPE_MAGIC; 7 frame tests.
- aura-pki: CrlStore::{encode_signed, save_signed, decode_signed_verified,
load_signed_verified} — ECDSA-P256/SHA-256 from the CA private key against
a textual "CRL-Aura-v1" body + --SIGNATURE--; 7 signing tests. ring 0.17
added crate-local (already in lockfile via rustls-webpki).
- aura-cli: crl_push module — server pushes via conn.send_packet on accept;
client wraps the Arc<dyn PacketConnection> in AcceptPushedCrlConn which
sniffs the magic in recv_packet, verifies the signature, updates the
AuraCertVerifier, caches to disk. PkiSection gets ca_key, crl_push (default
true), accept_pushed_crl (default true).
- 5 in_band_crl integration tests via mock PacketConnection.
Workspace: 235 tests passed (+28), clippy -D warnings clean, fmt clean. v2
COMPLETE — all 9 honest v1 limitations resolved (except sing-box, per user).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
8f0cf1f017 |
feat(cli): automation bundle + identity-minimization features
Reduces manual setup steps and trims user-identifying data exposed by the server/client, in the spirit of the deployment story: an operator on the wire sees less, and the admin types fewer commands. New CLI subcommands: - `aura server-init`: one shot — pki init + issue-server + writes a ready server.toml with auto-detected egress iface; flags --enable-knock, --enable-cover-traffic, --no-nat, --run-as toggle the new transport defenses and privilege drop. - `aura provision-client`: issues a client cert and assembles the full bundle (ca.crt + client.crt + client.key + client.toml in one directory) ready to hand over to the client device. --id is optional (defaults to a fresh UUIDv4, so client identities don't have to encode anything real). Identity / log minimization: - `aura pki issue-client --id` is now optional — UUIDv4 by default. - `[server]/[client] no_logs = true` filters peer_id, client_ip, source_addr, client_id, local_ip, user, id, assigned_ip, peer field values through a custom tracing FormatFields layer (events still fire but the identifying fields are redacted before being written). - `[client] bridges = [...]`: secondary server addresses; build_dial_targets shuffles them after the primary, so blocking one IP doesn't kill the client. - Auto-detect egress iface in [server.nat] (via detect_default_egress_iface); egress_iface in config becomes optional with graceful fallback. Config examples updated; backward-compatible (all new sections optional with serde defaults). Workspace: 207 tests passed (+22), clippy -D warnings clean, fmt clean. No new workspace deps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7d711d8938 |
feat(transport): anti-surveillance - UDP port-knocking + cover traffic
Two opt-in (default off) features directly targeting the kind of operator
dragnet described in the news context — make the server harder to identify
on a scan, and the traffic harder to fingerprint by volume/timing analysis.
1) Port-knocking (probe resistance, UDP)
- Wire: every HS datagram (0x01) is prefixed with a 16-byte HMAC token
when UdpOpts.knock_required is on:
knock = HMAC-SHA256(knock_key, u64_be(unix_minute))[..16]
- Server-side: validates against {now-1, now, now+1} minutes (3-minute
window for clock skew, constant-time compare). Invalid -> silent drop;
the port looks closed to scanners.
- knock_key comes from the CLI (derived from CA fingerprint at the
deployment layer); transport just consumes it.
- DATA datagrams unchanged (AEAD already proves legitimacy past hs).
2) Cover traffic (chaff, UDP)
- Optional background task per UdpConnection: every random delay
(mean_interval_ms +/- jitter, default 500ms +/- 50%) sends a
Frame::Ping{seq=random} when no Data was sent in the recent window
(idle-skip => zero overhead under load). RAII-aborted on Drop.
- Receiver answers Ping with Pong (existing logic); both are consumed
internally by recv_packet, invisible to the app.
API: UdpOpts gains knock_required/knock_key/cover_traffic_enabled/
cover_mean_interval_ms/cover_jitter (all defaults preserve v2 behavior).
Helpers exported: knock_for_minute, KNOCK_LEN.
Local deps: hmac 0.12 + sha2 0.10 (already in workspace lockfile, no new
resolution). Workspace: 185 tests passed (+11), clippy -D warnings clean,
fmt clean. 174 baseline tests unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
65b26b555d |
feat(cli): OS-level split-tunnel routes (removes send_direct stub)
DIRECT-destination traffic now bypasses the TUN entirely via OS routing
table edits, instead of going through user-space and hitting the v1
send_direct stub. The user-space router only sees VPN-bound packets,
making the split-tunnel real.
- aura_cli::os_routes::OsRouteGuard: RAII install + rollback of OS routes.
Linux: `ip route show default` parser -> DIRECT CIDRs via original gw,
VPN default via TUN with metric 50. macOS: `route -n get default`
parser -> `route add -net/-host ... <gw>` for DIRECT, `route add -net
... -interface <tun>` for VPN. Windows: stub + warning (v3).
- dry_run works on every platform (logs `would run: ...`); useful for
tests and operator confidence-checks.
- SplitRoutes::from_config folds [[tunnel.split.direct]]/[[...vpn]] +
resolved domains (via AuraDns) into one declarative plan.
- New [tunnel.os_routes] {enabled (default true), dry_run, gateway,
egress_iface}; absent section = old user-space behavior (back-compat).
- client::run installs routes after AuraTun::create, before privdrop;
guard's Drop reverts everything on shutdown.
- aura-tunnel::router unchanged; AuraRouter::send_direct kept as a
defensive fallback (in v2 it should never fire — OS routes prevent
DIRECT packets from reaching the TUN at all).
20 new tests (linux/macos parser unit tests, install dry-run, config
back-compat). Workspace: 174 tests passed (+19), clippy -D warnings
clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c6f0d7af9b |
feat(cli): auto-NAT + privilege drop + Windows named-pipe admin
Three v2-hardening features in aura-cli, one pass:
- nat::NatGuard: RAII auto-config of IP forwarding + MASQUERADE on server
startup. Linux (sysctl ip_forward + iptables -t nat MASQUERADE) and
macOS (sysctl ip.forwarding + pfctl with /tmp/aura-nat.conf). dry_run
works on every platform (logs "would run: ..."). Reverts everything in
Drop. New [server.nat] {auto, egress_iface, dry_run}; absent section =
back-compat no-op. Removes v1's "manual NAT/forwarding" step.
- privdrop::drop_to_user: drop euid/gid after binding TUN + privileged
ports. Linux setresuid/setresgid, macOS setgid+setuid (permanent drop),
Windows no-op with warning. New [server] / [client] run_as = "..."
(optional). Skipped with info-log if already non-root.
- admin: split transport into cfg(unix) Unix-socket and cfg(windows) Tokio
named-pipe modules sharing one JSON-line serve/request flow over
AsyncRead/AsyncWrite. DEFAULT_SOCKET = "/tmp/aura-admin.sock" on Unix,
r"\\.\pipe\aura-admin" on Windows. Removes v1's "admin Unix-only".
Deps: nix 0.29 user feature under [target.'cfg(unix)'.dependencies] (cli-
local, not workspace). Workspace: 155 tests passed (+13), clippy -D warnings
clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
821f7711e7 |
feat(transport): real TLS-443 on the TCP backend (replaces HTTP/1.1 masquerade)
The TCP fallback now does a full outer TLS handshake (tokio-rustls 0.26 over
rustls 0.23, ring provider) before the Aura proto handshake, exactly like the
QUIC backend: on the wire it is indistinguishable from genuine HTTPS until the
inner Aura mutual-auth handshake starts. Removes v1's "light HTTP masquerade"
limitation; the real security boundary remains the inner PQ handshake.
- aura-transport::tcp: dropped the HTTP/1.1 preamble helpers and TcpOpts
fields (masquerade, host, user_agent, server_header). New flow:
TlsAcceptor::accept (server) / TlsConnector::connect (client) →
tokio::io::split(TlsStream) → server_handshake / client_handshake → Session.
Client reuses crate::quic::AcceptAnyServerCert (outer SNI not authenticated;
inner handshake is the security boundary). Outer server cert auto-sourced
from proto_cfg.server_cert_pem (no API change for the CLI's bind).
- ALPN default: ["h2", "http/1.1"] (DEFAULT_TCP_ALPN, exported).
- TcpOpts: now just { alpn: Option<Vec<Vec<u8>>> }.
- TcpClient::connect gains an outer-SNI &str param; DialConfig.sni passes it
through (separate from the inner proto_cfg.server_name).
- tokio-rustls 0.26 added as a transport-local dependency (not workspace).
CLI updates: removed dead host/user_agent/server_header wiring; mask rotation
no longer touches TCP outer parameters (TLS doesn't have a Host header on
the wire). [transport] masquerade kept as a no-op for back-compat with old
configs (documented).
3 new tcp_loopback tests (default ALPN end-to-end, custom ALPN, outer SNI
mismatch still connects = proves accept-any is in effect). Workspace: 142
tests passed (+1), clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
0a73d5298b |
feat(cli): server IP pool + per-client routing (multi-client VPN concentrator)
Server now assigns each connected client an IP from a configurable pool and
maintains a client_ip -> AuraConnection map so packets read from the shared
TUN are dispatched to the right client (and each client's recv loop writes
back to the TUN). Removes v1's "single shared TUN, no NAT/pool" limitation;
turns the server into a proper multi-client VPN concentrator (paired with the
already-landed UDP multi-client demux).
- aura_cli::pool: IpPool + PoolStrategy {StaticOnly, DynamicOnly,
StaticOrDynamic}; reserves network/broadcast/server-own IP; 15 tests.
- aura_cli::server_router: ServerRouter + ServerRoutes (Arc<RwLock<HashMap>>);
central TUN read loop dispatching by dst_ip; spawn_inbound_forwarder per
conn auto-unregisters and releases the IP on disconnect; 4 tests via
MockTun + MockConn.
- aura_cli::config: [server.pool] {cidr, strategy, static} added with
serde(default); legacy configs (only [tunnel] pool_cidr) fall back to a
DynamicOnly pool (backward compatible, tested).
- aura_cli::server: accept loop now: pool.assign(peer_id) -> register ->
spawn_inbound_forwarder; rejected static_only mismatches dropped+logged.
- config/server.toml.example: documented [server.pool] section.
Workspace: 141 tests passed (+24), clippy -D warnings clean, fmt clean. No
new workspace deps (async-trait added to cli dev-deps for mock traits in tests).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4d1bdba55d |
feat(transport): UDP multi-client demux by peer address
UdpServer now serves many concurrent peers on one socket (removes v1's "one peer per accept" limitation). PeerSocket becomes an enum: ConnectedClient (client side, unchanged behavior) vs SharedServer (server side, channel-fed inbox). A master loop reads the shared socket and routes datagrams to the right per-peer inbox by source address; an unknown peer's first TYPE_HS datagram spawns a new handshake task that, on success, hands the established UdpConnection to accept(). Cleanup is lazy via mpsc::Closed — handshake failures and connection drops self- evict from the map. A small Arc<MasterTask> keeps the loop alive for the lifetime of UdpServer OR any spawned UdpConnection, so existing single- client tests (which move UdpServer into an accept task) still pass. ReliableHsAdapter and run_reliable_handshake are unchanged. UdpClient API unchanged. Added 3 tests: two concurrent clients with cross-talk isolation, bad-CA client doesn't block legitimate ones, dropped peer doesn't block others. Workspace: 117 tests green, clippy/fmt clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c95e1a482c |
feat(crypto,cli,transport): daily protocol-mask rotation at 05:00 MSK
Both server and client deterministically rotate the on-wire obfuscation mask (SNI, HTTP Host/User-Agent/Server headers, UDP padding profile) at 05:00 Moscow time (02:00 UTC) every day, derived from the CA fingerprint + UTC date — no network coordination needed. - aura-crypto::masks: MaskSet + 4 palettes (16 SNI, 10 UA, 5 Server, 4 padding profiles); derive_mask_for_msk_date via HKDF-SHA256(salt="aura-mask-v1-salt", ikm=ca_fp||"YYYY-MM-DD", info="aura-mask-v1"); ca_fingerprint with built-in base64 PEM decode (no new deps). - aura-cli::masks: MaskRotator (Arc<RwLock<MaskSet>>) + Hinnant's civil_from_days for manual UTC date math; scheduler picks next 02:00 UTC strictly (avoids busy-loop at boundary); spawned at startup in server::run/client::run. - aura-transport: PADDING_PROFILES + next_bucket_for_profile (profile 0 byte-for- byte equals legacy pad_to_https_size); TcpOpts gains user_agent/server_header; UdpOpts gains padding_profile; MultiServer holds Arc<UdpServer>/Arc<TcpServer> with set_udp_opts/set_tcp_opts so rotation propagates without restart. - Backward-compatible: defaults preserve previous behavior; existing 97 tests unchanged. 17 new tests (derive determinism + date variation, civil-from-days known points incl. 1970-01-01/2000-02-29/2024->2025, next-rotation boundary, msk_today offset, profile equivalence, base64 round-trip, full mask-driven UDP loopback). Total: 114 passed, clippy/fmt clean. No new workspace deps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d5b9a8611d |
feat(cli): select transport in config; server MultiServer + client dial handover
- aura-cli config gains [transport] (order + per-transport ports + obfuscate/ masquerade); server binds all enabled transports via MultiServer, client uses dial() with UDP->TCP->QUIC handover. Config examples updated; backward-compatible (defaults to udp,tcp,quic). 21 cli tests incl. a real-UDP-transport loopback. - docs/sing-box.md: integration approach note (process-bridge now; native Go outbound for phones, with crypto-library mapping + KAT requirement). - Normalize rustfmt across the v2 transport files (tcp/dial/udp contract). Whole workspace: 97 tests pass, clippy -D warnings clean, fmt clean. Deploy flow (pki init/issue-server/issue-client) validated with the release binary. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d72fbe8d68 |
feat(transport): TCP/443 fallback + unified dialer with UDP->TCP->QUIC handover
- tcp.rs: Aura proto handshake + Session directly over TcpStream (TcpServer/
TcpClient/TcpConnection: PacketConnection), with an optional light HTTP/1.1
masquerade preamble. Fallback for UDP-blocking networks. (Full TLS-443 mimicry
is a documented follow-up.)
- dial.rs: TransportMode {Udp,Tcp,Quic}, Endpoints, DialConfig; client `dial()`
tries transports in order and hands over on failure/timeout; MultiServer binds
and accepts on every enabled transport at once (TCP/QUIC multi-client; UDP
single-peer-per-accept in v1).
- Tests: tcp loopback (plain + masquerade), dial handover (dead TCP -> UDP).
clippy/fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
866b9f427a |
feat(transport): custom UDP post-quantum transport (own tunneling, no QUIC)
Aura's own data path over plain UDP, authenticated solely by the existing Aura PQ handshake (hybrid X25519+ML-KEM-768 + mutual X.509) — no QUIC, no outer TLS. - One UDP socket, two phases by type byte: 0x01 HS (reliable handshake), 0x02 DATA (datagram records). HS = DTLS-flight reliability over UDP: per-message seq, cumulative acks, retransmit (RTO), reorder/dedup, post-handshake linger; message boundaries parsed from the 5-byte Aura header. DATA = one explicit- nonce AEAD record per datagram (seq||AEAD), replay-checked, optional padding to HTTPS size buckets (obfuscation). - UdpServer/UdpClient/UdpConnection (impl PacketConnection, concurrent send/recv). v1: single peer per accept (multi-client demux is a follow-up). - 5 adapter unit tests + udp loopback end-to-end (obfuscation on, 1300B/empty/ duplex) + handshake-survives-30%-loss-and-reorder. No new deps. QUIC tests preserved. Whole workspace builds; clippy/fmt clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
fa9f18ec17 |
feat(crypto,proto): explicit-nonce AeadKey + datagram record codec
Contract for the custom UDP transport (v2): - aura-crypto: AeadKey — ChaCha20-Poly1305 with an EXPLICIT per-message nonce (caller passes the counter), for datagram transports where packets may be lost or reordered. AeadSession::into_parts() hands off (AeadKey, counter). Same nonce scheme as AeadSession, so they interoperate on one key with disjoint counter ranges. +4 tests. - aura-proto: DatagramSender/DatagramReceiver (record = seq(8) || AEAD(frame, aad=seq), sliding replay window) and Session::into_datagram_parts(); reuse for a UDP data path. +1 test. Existing 16 crypto / 13 proto tests still green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cb89312a27 |
feat(cli): implement Wave 4 — aura binary (PKI, server/client, admin, bench)
aura-cli: clap command tree (pki init/issue-server/issue-client/revoke/list,
server, client, route add/list/remove, status, bench-crypto); TOML config with
~ expansion and split-tunnel rules -> RouteTable; JSON-over-Unix-socket admin
IPC; server/client data paths wiring transport + tunnel (TUN run needs root).
config/{server,client}.toml.example. 15 tests (pki roundtrip, config parse,
admin-socket roundtrip, loopback connection). Verified the real binary: --help,
bench-crypto, and a full CA->server->client cert workflow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c19a6c5586 |
feat(transport,tunnel): implement Wave 3 — QUIC transport + split-tunnel router
aura-transport: quinn 0.11 endpoint with HTTP/3 mimicry (ALPN h3/h3-29, Chrome-like transport params), outer-TLS accept-any (real auth is the inner Aura handshake), packet padding to HTTPS sizes; AuraServer/AuraClient drive the proto handshake over a QUIC bidi stream; AuraConnection impls aura_proto::PacketConnection (full-duplex via Session::split + per-half mutex). 14 tests incl. a real-QUIC loopback end-to-end (crypto+pki+proto+transport). aura-tunnel: RouteTable (longest-prefix split-tunnel classify), AuraDns (hickory) host-route registration, AuraRouter over a PacketIo TUN seam + Arc<dyn PacketConnection>, AuraTun (tun 0.8 unix; wintun cfg-gated Windows). 10 tests (route classify/priority, dst-IP parse, mock router). send_direct is a v1 stub. Whole workspace: tests green, clippy clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0a045c248d |
refactor: move PacketConnection trait to aura-proto; decouple tunnel from transport
Worktree isolation is unavailable in this environment, so make Wave 3 safe for same-tree parallel work instead: the PacketConnection contract now lives in aura-proto (stable) and aura-tunnel no longer depends on aura-transport. With transport and tunnel both depending only on proto (and not each other), the two crates are independent leaves and can be built/edited concurrently without one breaking the other's build. proto: 13 tests still green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cb78de4f37 |
feat(transport): pin PacketConnection contract for the router seam
Define the async PacketConnection trait (send_packet/recv_packet over &self) that aura-tunnel's router consumes and the QUIC connection will implement. Committed before Wave 3 so the transport and tunnel agents build against a stable cross-crate contract from isolated worktrees. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5d88d57223 |
refactor(proto): add Session::split() for full-duplex data path
Compose Session from SessionSender (writer + outbound AEAD/seq) and SessionReceiver (reader + inbound AEAD + replay window); split() hands back the two halves so a VPN data path can run concurrent read/write tasks (recv_frame is not cancellation-safe, so select! on one &mut Session is unsafe). send_frame/recv_frame/peer_id/into_inner unchanged; 13 tests still green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
bb835e4ca7 |
feat(proto): implement Wave 2 — hybrid PKI handshake + session
aura-proto: 5-byte wire header + Frame codec (§6.1/§6.3); transport-agnostic handshake state machine (§6.2) over split tokio AsyncRead/AsyncWrite — hybrid X25519+ML-KEM-768 KEM, SHA-256 transcript, mutual X.509 auth with ECDSA-P256 transcript signatures (ring), constant-time HMAC Finished; Session with sliding-window replay protection. 13 tests green, clippy clean. Handshake message order pinned (resolves spec diagram ambiguity); reader/writer taken by value since Session owns both halves. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b8ce58ddf0 |
feat(crypto,pki): implement Wave 1 — hybrid KEM + PKI
aura-crypto: X25519 + ML-KEM-768 (FIPS 203) hybrid KEM, HKDF-SHA256 session key derivation, ChaCha20-Poly1305 AeadSession with counter nonces; genuine NIST ACVP ML-KEM-768 KAT (decapsulation vector). 16 tests green, clippy clean. aura-pki: self-signed CA, server/client cert issuance (rcgen 0.14), mutual X.509 chain verification via rustls-webpki, CRL revocation. 8 tests green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f78633e04f |
chore: scaffold Aura workspace skeleton (Stage 0)
- 6-crate Cargo workspace, dependency tree frozen (cargo check green in ~1m) - ml-kem 0.3 (FIPS 203) replaces spec's pqcrypto-kyber for ML-KEM-768 - fix invalid target-gated workspace.dependencies: Windows deps (wintun/windows) declared untargeted, cfg-gated per-crate in aura-tunnel - version bumps vs spec: tun 0.8, rcgen 0.14, wintun 0.5 - stub lib/main per crate; real implementations land wave by wave Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |