Two follow-ups to the previous v3.4 commit (ba8d6b7):
## #49 — client uses BridgeEndpoint ports as authoritative
BridgesDiscoveryWatcher now keeps a second snapshot
(`Arc<RwLock<Vec<BridgeEndpoint>>>`) for the per-transport endpoints carried by
v3.4 manifests, alongside the existing flat-bridges snapshot for v3.3
compatibility. `endpoints_snapshot()` and `primary_endpoint()` expose it to the
client.
In `client::run`, immediately after the watcher loads, the primary endpoint's
per-transport ports override the dial-time `dial_cfg.endpoints.{tcp,quic,udp}`
*ports*. The IP stays whatever the dialer already resolved (server_addr /
bridge list). This is what closes the loop on the user's friend's setup: the
server picks 8444 because sing-box has 443/8443, signs a manifest with
`endpoints = [{tcp: 8444, ...}]`, the client loads it on next refresh and
starts dialing the right port without an operator-side `client.toml` edit.
When the manifest has no `endpoints` field (old v3.3 format, or operator
chose not to publish per-transport ports), no override is applied and the
client.toml `[transport] *_port` values are used as before.
## #45 — silent client exit on broken connection
Root cause confirmed in `AuraRouter::run`:
- the inbound task did `let pkt = inbound_conn.recv_packet().await?;`, so any
recv error returned silently via `?`
- the `to_tun_tx` channel sender dropped, `to_tun_rx.recv()` returned `None`
- the outbound `select!` arm matched `None => break Ok(())`
- the router returned `Ok(())`, the client's `run()` returned `Ok(())`, the
process exited 0 with no log, no error message
We saw this empirically when the user disabled a co-resident VPN that had been
routing AuraVPN's UDP/444 traffic — the underlying QUIC socket broke, the
inbound task hit recv error, and the whole client vanished.
Fix:
- Inbound task now logs the error at `error` level with the underlying
`recv_packet` cause before exiting.
- The outbound `select!`'s `None` arm now returns an Err (not Ok(())) so the
caller knows the tunnel died and `aura client` exits non-zero — which is
what a supervisor (systemd, launchd, or a future auto-redial loop) wants to
see.
- The router waits up to 200ms for the inbound task to land cleanly before
returning, so its error / panic is logged instead of being swallowed by
`abort()`.
Existing tests still pass (12/12 in aura-tunnel router tests). Tested
manually: with the fix, killing the underlying transport now produces a
"peer connection broke (recv_packet failed): …" error line and a non-zero
exit, instead of silent process disappearance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
- 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>