Commit Graph

37 Commits

Author SHA1 Message Date
xah30 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>
2026-05-29 17:14:45 +03:00
xah30 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>
2026-05-27 21:39:23 +03:00
xah30 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>
2026-05-27 21:25:05 +03:00
xah30 a070da0be9 feat(singbox-aura,tools): Go port of Aura UDP client + KAT bridge to Rust
Lays the foundation for sing-box mobile clients (Option B from
docs/sing-box.md): an independent Go module that speaks the AuraVPN wire
protocol byte-for-byte. Proof of equivalence is in KAT tests cross-loaded
from a Rust-side deterministic vector exporter.

- tools/export-kat (new Rust bin in workspace): captures a handshake +
  derived keys + a sealed datagram record + a knock token using seeded
  RNGs (rand::rngs::StdRng + ml-kem's *_deterministic public API), emits
  JSON. Reproducible byte-for-byte.
- singbox-aura/ (new Go module, ~3000 LOC, 22 files):
  - aura/frame: 5-byte protocol header + Frame{Data,Ping,Pong,Close,
    Control} + magic envelope (0xAA,0xAA,0xC0,0x01) — encode/decode
    matching aura-proto::frame.
  - aura/crypto: hybrid X25519 + ML-KEM-768 (stdlib crypto/ecdh +
    crypto/mlkem on Go 1.24+; falls back to circl on older Go via a
    documented swap), HKDF-SHA256 derive_session_keys, ChaCha20-Poly1305
    with the **LE(u64 counter) || [0;4]** nonce scheme that matches
    aura-crypto::AeadKey/AeadSession.
  - aura/handshake: client_handshake state machine reproducing protocol.md
    §6.2 exactly (CH→SH→ServerAuth→ClientAuth→Finished×2; transcript hash;
    ECDSA-P256 transcript signature; HMAC-SHA256 Finished).
  - aura/session: DatagramSender/Receiver + 64-wide sliding replay window.
  - aura/transport: reliable HS-adapter (DTLS-flight retransmit) + UDP
    datagram data path + 16-byte HMAC port-knock with ±1-minute window.
  - aura/outbound: sing-box-shaped shim (interface signatures only — sing-
    box upstream registration is one more step, documented in README).
  - cmd/aura-client: standalone Go binary; reads client.toml via
    pelletier/go-toml/v2 and connects to a real aura server. Validates
    end-to-end interop with the Rust side.
- KAT: 6 comparisons against Rust vectors — session_keys (HKDF), hybrid
  KEM ek/encaps roundtrip, c2s + s2c Finished HMAC, sealed datagram
  record at seq=2 (incl. 16-byte Poly1305 tag), knock token. All byte-
  for-byte.

Go: 29 tests across 5 packages, all green. Only deps: golang.org/x/crypto
and pelletier/go-toml/v2. Rust: 293 tests still green; tools/export-kat
added to workspace members.

v1 limits documented in singbox-aura/README.md: UDP-only (no TCP/QUIC
fallback yet), no cell padding / cover traffic, no relay/exit role, no
multi-hop, sing-box upstream-registration sketch (vendor sagernet/sing-box +
init() RegisterOutbound) for follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 21:14:23 +03:00
xah30 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>
2026-05-27 21:14:23 +03:00
xah30 1893e24174 docs: README is now the full deployment guide
Expanded the root README to include the complete setup instructions
(crates table + intro + the full 7-section deployment guide that
previously lived only in docs/deployment.md, with the doc-link paths
adjusted for a root-level README and a list of every v2/v3 feature
plus the RF entry-relay scenario). docs/deployment.md is preserved for
in-tree navigation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:38:39 +03:00
xah30 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>
2026-05-27 20:29:18 +03:00
xah30 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>
2026-05-27 20:07:12 +03:00
xah30 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>
2026-05-27 19:35:22 +03:00
xah30 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 (6c14c0d), the property
holds: entry-relay knows the client IP + client_id but cannot decrypt the
data; exit knows the destination but sees the relay's IP as source.

- aura-cli::circuit: dial_circuit(&[entry, exit], proto_cfg, udp_opts) →
  CircuitConnection. Connects to entry as a normal UdpClient, sends an
  ExtendBridge control envelope, awaits CircuitReady, then runs a SECOND
  Aura handshake to the exit through a local loopback UDP proxy — the
  forwarder ferries datagrams between that proxy socket and the outer
  relay PacketConnection. The inner handshake therefore authenticates the
  EXIT cert (verified by the integration test asserting
  circuit.peer_id() == "localhost-exit"); the relay never sees the inner
  session keys.
- aura-cli::relay: rendezvous(conn, whitelist) -> Bridged{bridge} |
  Fallback{first_pkt} | Refused. 2-second window after handshake to receive
  ExtendBridge. Whitelist enforced; CircuitFailed on miss. Empty whitelist
  logs a warning and runs open. Timeout / non-control → Fallback so the
  same server can be both relay (for circuit clients) and exit (for direct
  clients) simultaneously.
- aura-cli::client: when [client.circuit] enabled → dial_circuit; falls
  back to normal aura_transport::dial when disabled.
- aura-cli::server: relay rendezvous wired before pool/CRL/router path.
  run_bridge spawns two forwarder tasks (conn↔bridge UDP socket).
- 3 integration tests: end-to-end (with peer_id assertion), whitelist
  rejection, back-compat (relay disabled → Err). 3 unit tests in relay.rs.

Workspace: 253 tests passed (247 baseline + 6 new), clippy -D warnings clean,
fmt clean. No new workspace deps. All 28 tracked tasks (v1 + v2 + v3.1) now
complete.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 13:16:07 +03:00
xah30 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>
2026-05-27 12:54:12 +03:00
xah30 b98752b3eb docs(deployment): v2 complete - in-band CRL + anti-surveillance + automation
deployment.md §6 updated:
- Moved CRL from "remaining" to "resolved" (now in-band via signed
  control-envelope with magic prefix).
- Added bullets for the new v2 features: port-knocking + cover traffic
  (anti-surveillance), `aura server-init` / `aura provision-client`
  (automation), `no_logs` field redaction, `bridges` list.
- Remaining honest limits trimmed to genuine v3 work: native Go phone
  client (sing-box, explicitly excluded by user) and multi-hop routing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:36:00 +03:00
xah30 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>
2026-05-27 12:35:16 +03:00
xah30 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>
2026-05-27 12:14:57 +03:00
xah30 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>
2026-05-27 11:50:16 +03:00
xah30 278bcced95 docs(deployment): reflect v2 state — resolved limitations + remaining ones
§5: TCP/443 fallback now described as real outer TLS-443 (was the lighter
HTTP/1.1 masquerade in v1).

§6 rewritten "Честные ограничения v1" -> "v2 — что устранено и что остаётся":
- Resolved: UDP multi-client demux, server IP pool + per-client routing,
  OS-level split-tunnel (no more send_direct stub), real TLS-443, auto-NAT,
  privilege drop, Windows admin named pipe, daily protocol-mask rotation
  at 05:00 MSK.
- Remaining honest limits: TUN creation still needs root (privilege drop
  shrinks the window), IPv6 in OS routes / iptables not yet, Windows OS
  routes stubbed, CRL still out-of-band (in-band push deferred), native
  phone client via sing-box still a plan, no auto-detect of egress iface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 11:11:07 +03:00
xah30 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>
2026-05-27 02:20:30 +03:00
xah30 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>
2026-05-27 02:09:38 +03:00
xah30 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>
2026-05-27 01:53:02 +03:00
xah30 75e350e870 chore: update Cargo.lock for aura-cli async-trait dev-dep 2026-05-27 01:41:56 +03:00
xah30 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>
2026-05-27 01:41:29 +03:00
xah30 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>
2026-05-27 01:27:06 +03:00
xah30 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>
2026-05-27 01:11:45 +03:00
xah30 083c441e4c docs: rewrite all documentation in Russian + add deployment guide
- docs/protocol.md, pki.md, split-tunnel.md, sing-box.md переведены на русский
  и сверены с текущим кодом (транспорт v2: свой UDP + TCP/443 + QUIC fallback,
  handover; PKI; split-tunnel; sing-box-план).
- docs/deployment.md (новый, 369 строк): пошаговое руководство для удалённого
  сервера — сборка, PKI init/issue-server/issue-client (проверено бинарём),
  server.toml/client.toml на основе фактических config/*.example, firewall +
  NAT/IP-форвардинг, sudo-запуск, бандл клиента (ca.crt + client.crt + client.key
  + server addr/sni), на каком транспорте идёт трафик, ограничения v1.
- README.md (новый, корень): краткий обзор + таблица крейтов + быстрый старт.

Всё на русском (проза); команды/идентификаторы/конфиги — как есть.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:42:08 +03:00
xah30 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>
2026-05-25 21:41:59 +03:00
xah30 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>
2026-05-25 19:15:31 +03:00
xah30 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>
2026-05-25 19:10:43 +03:00
xah30 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>
2026-05-25 18:57:56 +03:00
xah30 46513354c0 docs: add protocol, PKI, and split-tunnel documentation
docs/protocol.md, docs/pki.md, docs/split-tunnel.md — written from the actual
implementation (pinned handshake order, ML-KEM-768/FIPS 203, seq||AEAD records
with replay window, QUIC/H3 mimicry) including honest v1 limitations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 18:40:19 +03:00
xah30 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>
2026-05-25 18:36:13 +03:00
xah30 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>
2026-05-25 18:26:39 +03:00
xah30 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>
2026-05-25 18:13:37 +03:00
xah30 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>
2026-05-25 18:10:17 +03:00
xah30 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>
2026-05-25 18:07:55 +03:00
xah30 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>
2026-05-25 18:05:11 +03:00
xah30 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>
2026-05-25 17:55:06 +03:00
xah30 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>
2026-05-25 17:42:40 +03:00