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>
This commit is contained in:
xah30
2026-05-25 18:26:39 +03:00
parent 0a045c248d
commit c19a6c5586
14 changed files with 1887 additions and 4 deletions
+124
View File
@@ -0,0 +1,124 @@
//! End-to-end integration test over genuine QUIC on loopback.
//!
//! Proves that aura-crypto + aura-pki + aura-proto + aura-transport integrate: we mint a real CA,
//! issue a server cert (SAN "localhost") and a client cert, bind an [`AuraServer`] on an
//! OS-assigned loopback port, connect an [`AuraClient`] (with a camouflage SNI distinct from the
//! verified server name), run accept + connect concurrently, then push packets both directions
//! through the [`PacketConnection`] API and assert payload integrity.
use std::sync::Arc;
use aura_pki::AuraCa;
use aura_proto::{ClientConfig, PacketConnection, ServerConfig};
use aura_transport::{AuraClient, AuraConnection, AuraServer};
/// The DNS name baked into the server cert SAN and verified by the inner Aura handshake.
const SERVER_NAME: &str = "localhost";
/// A deliberately different outer SNI, to prove the mimicry hostname is independent of the
/// inner-verified server name.
const CAMOUFLAGE_SNI: &str = "cdn.example.com";
#[tokio::test]
async fn end_to_end_quic_loopback() {
// --- PKI: CA + server cert + client cert ------------------------------------------------
let ca = AuraCa::generate("Aura Test CA").expect("generate CA");
let server_cert = ca
.issue_server_cert(SERVER_NAME)
.expect("issue server cert");
let client_cert = ca
.issue_client_cert("client-001")
.expect("issue client cert");
let ca_pem = ca.ca_cert_pem();
let server_cfg = ServerConfig {
ca_cert_pem: ca_pem.clone(),
server_cert_pem: server_cert.cert_pem.clone(),
server_key_pem: server_cert.key_pem.clone(),
};
let client_cfg = ClientConfig {
ca_cert_pem: ca_pem.clone(),
client_cert_pem: client_cert.cert_pem.clone(),
client_key_pem: client_cert.key_pem.clone(),
server_name: SERVER_NAME.to_string(),
};
// --- Bind the server on 127.0.0.1:0 and read the OS-assigned port -----------------------
// The outer QUIC (mimicry) cert reuses the Aura server cert PEM, as the brief suggests.
let server = AuraServer::bind(
"127.0.0.1:0".parse().unwrap(),
&server_cert.cert_pem,
&server_cert.key_pem,
server_cfg,
)
.expect("bind server");
let server_addr = server.local_addr().expect("server local_addr");
assert_ne!(server_addr.port(), 0, "OS should assign a real port");
// --- Run accept + connect concurrently --------------------------------------------------
let accept_task = tokio::spawn(async move { server.accept().await });
let connect_task =
tokio::spawn(
async move { AuraClient::connect(server_addr, CAMOUFLAGE_SNI, client_cfg).await },
);
let server_conn: AuraConnection = accept_task
.await
.expect("accept task join")
.expect("server accept");
let client_conn: AuraConnection = connect_task
.await
.expect("connect task join")
.expect("client connect");
// The mutual-auth handshake should have established peer identities both ways.
assert_eq!(
server_conn.peer_id(),
Some("client-001"),
"server should learn the client's verified CN"
);
// Share both ends as trait objects, proving `Arc<dyn PacketConnection>` usability.
let server_conn: Arc<dyn PacketConnection> = Arc::new(server_conn);
let client_conn: Arc<dyn PacketConnection> = Arc::new(client_conn);
// --- Client -> Server: several packets, assert integrity --------------------------------
let c2s: Vec<Vec<u8>> = vec![
b"hello server".to_vec(),
vec![0u8; 1500], // larger-than-bucket payload
(0..=255u8).collect(), // every byte value
b"".to_vec(), // empty packet
];
for pkt in &c2s {
client_conn.send_packet(pkt).await.expect("client send");
let got = server_conn.recv_packet().await.expect("server recv");
assert_eq!(&got, pkt, "client->server payload mismatch");
}
// --- Server -> Client: several packets, assert integrity --------------------------------
let s2c: Vec<Vec<u8>> = vec![
b"hello client".to_vec(),
vec![0xABu8; 777],
b"final packet".to_vec(),
];
for pkt in &s2c {
server_conn.send_packet(pkt).await.expect("server send");
let got = client_conn.recv_packet().await.expect("client recv");
assert_eq!(&got, pkt, "server->client payload mismatch");
}
// --- Concurrent full-duplex: both directions in flight at once --------------------------
let s = server_conn.clone();
let c = client_conn.clone();
let dup_server = tokio::spawn(async move {
s.send_packet(b"duplex-from-server").await.unwrap();
s.recv_packet().await.unwrap()
});
let dup_client = tokio::spawn(async move {
c.send_packet(b"duplex-from-client").await.unwrap();
c.recv_packet().await.unwrap()
});
let server_got = dup_server.await.expect("dup server join");
let client_got = dup_client.await.expect("dup client join");
assert_eq!(server_got, b"duplex-from-client");
assert_eq!(client_got, b"duplex-from-server");
}