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>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
//! Verifies the client dialer's handover: when an earlier transport in the order is unreachable,
|
||||
//! `dial` moves on to the next and connects.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
use aura_pki::AuraCa;
|
||||
use aura_proto::{ClientConfig, PacketConnection, ServerConfig};
|
||||
use aura_transport::{dial, DialConfig, Endpoints, TcpOpts, TransportMode, UdpOpts, UdpServer};
|
||||
|
||||
fn make_configs() -> (ServerConfig, ClientConfig) {
|
||||
let ca = AuraCa::generate("Aura Test CA").expect("generate CA");
|
||||
let server = ca.issue_server_cert("localhost").expect("issue server cert");
|
||||
let client = ca.issue_client_cert("client-dial").expect("issue client cert");
|
||||
let ca_pem = ca.ca_cert_pem();
|
||||
(
|
||||
ServerConfig {
|
||||
ca_cert_pem: ca_pem.clone(),
|
||||
server_cert_pem: server.cert_pem,
|
||||
server_key_pem: server.key_pem,
|
||||
},
|
||||
ClientConfig {
|
||||
ca_cert_pem: ca_pem,
|
||||
client_cert_pem: client.cert_pem,
|
||||
client_key_pem: client.key_pem,
|
||||
server_name: "localhost".to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dial_falls_back_from_dead_tcp_to_udp() {
|
||||
let (scfg, ccfg) = make_configs();
|
||||
|
||||
// A real UDP server (the working fallback target).
|
||||
let udp_server =
|
||||
UdpServer::bind("127.0.0.1:0".parse().unwrap(), scfg, UdpOpts::default()).expect("bind udp");
|
||||
let udp_addr = udp_server.local_addr().expect("udp addr");
|
||||
let srv = tokio::spawn(async move {
|
||||
let conn = udp_server.accept().await.expect("server accept");
|
||||
let p = conn.recv_packet().await.expect("server recv");
|
||||
conn.send_packet(&p).await.expect("server echo");
|
||||
});
|
||||
|
||||
// Port 1 on loopback has nothing listening → TCP connect is refused fast.
|
||||
let dead_tcp: SocketAddr = "127.0.0.1:1".parse().unwrap();
|
||||
|
||||
let cfg = DialConfig {
|
||||
endpoints: Endpoints {
|
||||
udp: Some(udp_addr),
|
||||
tcp: Some(dead_tcp),
|
||||
quic: None,
|
||||
},
|
||||
sni: "cdn.example.com".to_string(),
|
||||
// Deliberately try the (dead) TCP first to force the handover to UDP.
|
||||
order: vec![TransportMode::Tcp, TransportMode::Udp],
|
||||
udp: UdpOpts::default(),
|
||||
tcp: TcpOpts::default(),
|
||||
attempt_timeout: Duration::from_secs(3),
|
||||
};
|
||||
|
||||
let (conn, mode) = dial(ccfg, cfg).await.expect("dial should fall back to UDP");
|
||||
assert_eq!(
|
||||
mode,
|
||||
TransportMode::Udp,
|
||||
"must hand over to UDP after TCP is refused"
|
||||
);
|
||||
|
||||
conn.send_packet(b"hello-fallback").await.expect("send");
|
||||
let echoed = conn.recv_packet().await.expect("recv");
|
||||
assert_eq!(echoed, b"hello-fallback");
|
||||
|
||||
srv.await.expect("server task");
|
||||
}
|
||||
Reference in New Issue
Block a user