mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-10 21:06:14 -08:00
Overhaul the WASI guest data plane for throughput and add the host capabilities it relies on. The externally driven Tokio runtime now runs its timer pre-turn only when a tracked deadline has expired, and all WASI-reachable timers (STUN, port mapping, WebClient, UDP flow cleanup) go through the portable time facade so conditional timer driving cannot starve them. Data plane: - Move read/write deadlines onto TCP and UDP resources with one ABI setter per direction, reuse a single expiration timer per resource, and drop timeout arguments from the four hot data-plane submissions (ABI v3). Checked absolute instants treat unrepresentable finite timeouts as unbounded instead of panicking. - Batch host traffic: vectored TCP frame writes combine queued slices into one host operation, and reads request a bounded 64 KiB while retaining excess bytes in the stream buffer. - Complete TCP writes inside the guest with cancellation-safe writes, reporting the completed prefix before honoring cancellation or timeout so hosts never replay bytes. - Repoll smoltcp egress immediately on zero poll delay, enlarge virtual UDP receive queues to 128 KiB payload with 128 metadata slots, and bound UDP session receive buffers to 8 KiB plus one byte while keeping oversized-datagram detection. Host integration: - Add optional algorithm-neutral AEAD seal/open imports with the ring backend as fallback, and pin the ring AES-128-GCM wire vector so the Go host stays interoperable. - Forward instance events to hosts through one best-effort, synchronous, non-blocking import. - Add a repository-owned build entry point for the Go host artifact: Binaryen 131 at -O4 with cached, SHA-256-verified official archives.
832 lines
28 KiB
Rust
832 lines
28 KiB
Rust
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
|
|
use pnet_packet::{
|
|
MutablePacket,
|
|
ip::IpNextHeaderProtocols,
|
|
ipv4::{self, MutableIpv4Packet},
|
|
tcp::{self, MutableTcpPacket, TcpFlags},
|
|
};
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
|
|
use super::*;
|
|
use crate::{
|
|
config::peers::PeerRuntimeSnapshot,
|
|
config::{IpPrefix, NetworkIdentity},
|
|
host::{
|
|
packet::{HostPacketReceiver, host_packet_channel},
|
|
testkit::TestHost,
|
|
},
|
|
peers::peer_manager::PortablePeerManagerConfig,
|
|
tunnel::ring::RingTunnelRegistry,
|
|
};
|
|
|
|
fn test_gateway() -> Arc<DataPlaneRuntime<TestHost>> {
|
|
let runtime_config = CoreRuntimeConfigStore::new(
|
|
crate::config::runtime::CoreRuntimeConfig::default(),
|
|
Arc::new(PeerRuntimeSnapshot::default()),
|
|
);
|
|
let host = Arc::new(TestHost::default());
|
|
let (packet_sender, packet_recv) = mpsc::channel(16);
|
|
Arc::new(DataPlaneRuntime {
|
|
operation: Mutex::new(()),
|
|
runtime_started: AtomicBool::new(false),
|
|
runtime_guard: DataPlaneIoGuard::new(),
|
|
runtime_config,
|
|
peer_manager: Weak::new(),
|
|
transport_proxy: None,
|
|
host: host.clone(),
|
|
socket_context: SocketContext::default(),
|
|
runtime_tasks: Arc::new(std::sync::Mutex::new(JoinSet::new())),
|
|
packet_sender,
|
|
packet_recv: Arc::new(Mutex::new(packet_recv)),
|
|
net: Arc::new(Mutex::new(None)),
|
|
entries: Arc::new(FlowTable::default()),
|
|
data_plane_consumers: Arc::new(DataPlaneConsumers::new()),
|
|
data_plane_net_ready: tokio::sync::watch::channel(false).0,
|
|
pipeline_guard: Mutex::new(None),
|
|
})
|
|
}
|
|
|
|
struct DataPlaneEndpoint {
|
|
gateway: Arc<DataPlaneRuntime<TestHost>>,
|
|
peer_manager: Arc<PeerManagerCore>,
|
|
_packet_receiver: HostPacketReceiver,
|
|
ip: cidr::Ipv4Inet,
|
|
}
|
|
|
|
fn data_plane_endpoint(host: Arc<TestHost>, ip: cidr::Ipv4Inet) -> DataPlaneEndpoint {
|
|
const NETWORK_NAME: &str = "gateway-data-plane";
|
|
|
|
let mut runtime = PeerRuntimeSnapshot::default().runtime;
|
|
runtime.core.node.peer_id = None;
|
|
runtime.core.node.network_name = NETWORK_NAME.to_owned();
|
|
runtime.core.routes.ipv4 = Some(
|
|
IpPrefix::new(IpAddr::V4(ip.address()), ip.network_length())
|
|
.expect("test IPv4 prefix should be valid"),
|
|
);
|
|
runtime.network_identity = NetworkIdentity {
|
|
network_name: NETWORK_NAME.to_owned(),
|
|
network_secret: Some("shared-secret".to_owned()),
|
|
network_secret_digest: None,
|
|
};
|
|
let peer_config = PortablePeerManagerConfig::new(runtime);
|
|
let runtime_config = CoreRuntimeConfigStore::new(
|
|
crate::config::runtime::CoreRuntimeConfig::default(),
|
|
Arc::new(peer_config.snapshot.clone()),
|
|
);
|
|
let (packet_sender, packet_receiver) = host_packet_channel();
|
|
let peer_manager = Arc::new(
|
|
PeerManagerCore::new_portable_for_test(peer_config, packet_sender)
|
|
.expect("build portable peer manager"),
|
|
);
|
|
let gateway = DataPlaneRuntime::new(
|
|
runtime_config,
|
|
peer_manager.clone(),
|
|
None,
|
|
host,
|
|
SocketContext::default(),
|
|
);
|
|
|
|
DataPlaneEndpoint {
|
|
gateway,
|
|
peer_manager,
|
|
_packet_receiver: packet_receiver,
|
|
ip,
|
|
}
|
|
}
|
|
|
|
async fn setup_data_plane_pair() -> (DataPlaneEndpoint, DataPlaneEndpoint) {
|
|
let host = Arc::new(TestHost::default());
|
|
let a = data_plane_endpoint(host.clone(), "10.126.126.1/24".parse().unwrap());
|
|
let b = loop {
|
|
let b = data_plane_endpoint(host.clone(), "10.126.126.2/24".parse().unwrap());
|
|
if b.peer_manager.my_peer_id() != a.peer_manager.my_peer_id() {
|
|
break b;
|
|
}
|
|
};
|
|
|
|
let (run_a, run_b) = tokio::join!(a.peer_manager.run(), b.peer_manager.run());
|
|
run_a.unwrap();
|
|
run_b.unwrap();
|
|
let (start_a, start_b) = tokio::join!(a.gateway.start_runtime(), b.gateway.start_runtime());
|
|
start_a.unwrap();
|
|
start_b.unwrap();
|
|
|
|
let registry = Arc::new(RingTunnelRegistry::default());
|
|
let listener_id = uuid::Uuid::new_v4();
|
|
let mut listener = registry.bind(listener_id).unwrap();
|
|
let client_tunnel = registry.connect(listener_id).unwrap().into_tunnel();
|
|
let server_tunnel = listener.accept().await.unwrap().into_tunnel();
|
|
let (client, server) = tokio::join!(
|
|
b.peer_manager.add_client_tunnel(client_tunnel, true),
|
|
a.peer_manager.add_tunnel_as_server(server_tunnel, true),
|
|
);
|
|
client.unwrap();
|
|
server.unwrap();
|
|
|
|
tokio::time::timeout(Duration::from_secs(10), async {
|
|
loop {
|
|
if a.peer_manager
|
|
.list_route_snapshots()
|
|
.await
|
|
.iter()
|
|
.any(|route| {
|
|
route.peer_id == b.peer_manager.my_peer_id()
|
|
&& route.ipv4_addr == Some(b.ip.into())
|
|
})
|
|
{
|
|
break;
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
}
|
|
})
|
|
.await
|
|
.expect("Ring peers did not exchange routes");
|
|
|
|
(a, b)
|
|
}
|
|
|
|
async fn stop_data_plane_pair(a: &DataPlaneEndpoint, b: &DataPlaneEndpoint) {
|
|
tokio::join!(a.gateway.stop_runtime(), b.gateway.stop_runtime());
|
|
tokio::join!(
|
|
a.peer_manager.clear_resources(),
|
|
b.peer_manager.clear_resources()
|
|
);
|
|
}
|
|
|
|
async fn wait_for_session_completion(
|
|
session: &DataPlaneSession<TestHost>,
|
|
) -> DataPlaneCompletionDescriptor {
|
|
tokio::time::timeout(Duration::from_secs(10), session.completion_notified())
|
|
.await
|
|
.expect("data-plane session completion timed out");
|
|
let completions = session.drain_completions(1);
|
|
assert_eq!(completions.len(), 1);
|
|
completions[0]
|
|
}
|
|
|
|
fn build_tcp_packet(src: SocketAddr, dst: SocketAddr) -> Vec<u8> {
|
|
let mut buf = vec![0u8; 40];
|
|
let src_ip = match src.ip() {
|
|
IpAddr::V4(ip) => ip,
|
|
IpAddr::V6(_) => panic!("test only supports ipv4"),
|
|
};
|
|
let dst_ip = match dst.ip() {
|
|
IpAddr::V4(ip) => ip,
|
|
IpAddr::V6(_) => panic!("test only supports ipv4"),
|
|
};
|
|
|
|
{
|
|
let mut ip_packet = MutableIpv4Packet::new(&mut buf).unwrap();
|
|
ip_packet.set_version(4);
|
|
ip_packet.set_header_length(5);
|
|
ip_packet.set_total_length(40);
|
|
ip_packet.set_ttl(64);
|
|
ip_packet.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
|
ip_packet.set_source(src_ip);
|
|
ip_packet.set_destination(dst_ip);
|
|
|
|
let mut tcp_packet = MutableTcpPacket::new(ip_packet.payload_mut()).unwrap();
|
|
tcp_packet.set_source(src.port());
|
|
tcp_packet.set_destination(dst.port());
|
|
tcp_packet.set_data_offset(5);
|
|
tcp_packet.set_flags(TcpFlags::SYN | TcpFlags::ACK);
|
|
tcp_packet.set_window(65535);
|
|
tcp_packet.set_checksum(tcp::ipv4_checksum(
|
|
&tcp_packet.to_immutable(),
|
|
&src_ip,
|
|
&dst_ip,
|
|
));
|
|
|
|
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
|
|
}
|
|
|
|
buf
|
|
}
|
|
|
|
fn build_udp_followup_fragment(src: Ipv4Addr, dst: Ipv4Addr) -> Vec<u8> {
|
|
let mut buf = vec![0u8; 28];
|
|
{
|
|
let mut ip_packet = MutableIpv4Packet::new(&mut buf).unwrap();
|
|
ip_packet.set_version(4);
|
|
ip_packet.set_header_length(5);
|
|
ip_packet.set_total_length(28);
|
|
ip_packet.set_ttl(64);
|
|
ip_packet.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
|
ip_packet.set_fragment_offset(1);
|
|
ip_packet.set_source(src);
|
|
ip_packet.set_destination(dst);
|
|
ip_packet
|
|
.payload_mut()
|
|
.copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
|
|
|
|
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
|
|
}
|
|
|
|
buf
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_tcp_pingpong() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let timeout = Duration::from_secs(10);
|
|
let mut listener = b.gateway.data_plane_tcp_bind(0, timeout).await.unwrap();
|
|
let listen_addr = SocketAddr::new(b.ip.address().into(), listener.local_addr().port());
|
|
|
|
let accept = tokio::spawn(async move {
|
|
let (mut stream, _peer) = listener.accept().await.unwrap();
|
|
let mut buf = [0u8; 4];
|
|
stream.read_exact(&mut buf).await.unwrap();
|
|
assert_eq!(&buf, b"ping");
|
|
stream.write_all(b"pong").await.unwrap();
|
|
stream.flush().await.unwrap();
|
|
});
|
|
|
|
let mut client = a
|
|
.gateway
|
|
.data_plane_tcp_connect(listen_addr, timeout)
|
|
.await
|
|
.unwrap();
|
|
client.write_all(b"ping").await.unwrap();
|
|
client.flush().await.unwrap();
|
|
let mut buf = [0u8; 4];
|
|
client.read_exact(&mut buf).await.unwrap();
|
|
assert_eq!(&buf, b"pong");
|
|
accept.await.unwrap();
|
|
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_sessions_complete_tcp_operations_end_to_end() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let session_a = DataPlaneSession::new(&a.gateway);
|
|
let session_b = DataPlaneSession::new(&b.gateway);
|
|
session_a.start().unwrap();
|
|
session_b.start().unwrap();
|
|
|
|
let bind = session_b
|
|
.submit_tcp_bind(0, Some(Duration::from_secs(10)))
|
|
.unwrap();
|
|
let completion = wait_for_session_completion(&session_b).await;
|
|
assert_eq!(completion.operation_id, bind);
|
|
let (listener, listen_addr) = session_b
|
|
.take_result_with(bind, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::TcpBound {
|
|
listener,
|
|
local_addr,
|
|
}) => Some((*listener, *local_addr)),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
let accept = session_b
|
|
.submit_tcp_accept(listener, Some(Duration::from_secs(10)))
|
|
.unwrap();
|
|
let connect = session_a
|
|
.submit_tcp_connect(listen_addr, Some(Duration::from_secs(10)))
|
|
.unwrap();
|
|
let (connect_completion, accept_completion) = tokio::join!(
|
|
wait_for_session_completion(&session_a),
|
|
wait_for_session_completion(&session_b),
|
|
);
|
|
assert_eq!(connect_completion.operation_id, connect);
|
|
assert_eq!(accept_completion.operation_id, accept);
|
|
let client = session_a
|
|
.take_result_with(connect, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::TcpConnected { stream, .. }) => Some(*stream),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
let server = session_b
|
|
.take_result_with(accept, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::TcpAccepted { stream, .. }) => Some(*stream),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
let read = session_b.submit_tcp_read(server, 16).unwrap();
|
|
let write = session_a
|
|
.submit_tcp_write(client, b"ping".to_vec())
|
|
.unwrap();
|
|
let (write_completion, read_completion) = tokio::join!(
|
|
wait_for_session_completion(&session_a),
|
|
wait_for_session_completion(&session_b),
|
|
);
|
|
assert_eq!(write_completion.operation_id, write);
|
|
assert_eq!(read_completion.operation_id, read);
|
|
let written = session_a
|
|
.take_result_with(write, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::TcpWritten { len }) => Some(*len),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
let received = session_b
|
|
.take_result_with(read, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::TcpRead { data, eof }) if !eof => Some(data.clone()),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(written, 4);
|
|
assert_eq!(received, b"ping");
|
|
|
|
let blocked_read = session_b.submit_tcp_read(server, 16).unwrap();
|
|
session_b.close_resource(server);
|
|
let close_completion = wait_for_session_completion(&session_b).await;
|
|
assert_eq!(close_completion.operation_id, blocked_read);
|
|
assert_eq!(
|
|
close_completion.status,
|
|
DataPlaneCompletionStatus::Error(DataPlaneErrorKind::HandleClosed)
|
|
);
|
|
let close_error = session_b
|
|
.take_result_with(blocked_read, |outcome| outcome.as_ref().err().copied())
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(close_error, DataPlaneErrorKind::HandleClosed);
|
|
|
|
let stopped_read = session_a.submit_tcp_read(client, 16).unwrap();
|
|
session_a.stop();
|
|
let stop_completion = wait_for_session_completion(&session_a).await;
|
|
assert_eq!(stop_completion.operation_id, stopped_read);
|
|
assert_eq!(
|
|
stop_completion.status,
|
|
DataPlaneCompletionStatus::Error(DataPlaneErrorKind::InstanceStopped)
|
|
);
|
|
|
|
session_a.close_resource(client);
|
|
session_b.close_resource(listener);
|
|
session_b.stop();
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_sessions_report_udp_truncation() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let session_a = DataPlaneSession::new(&a.gateway);
|
|
let session_b = DataPlaneSession::new(&b.gateway);
|
|
session_a.start().unwrap();
|
|
session_b.start().unwrap();
|
|
|
|
let bind_a = session_a.submit_udp_bind(0, None).unwrap();
|
|
let bind_b = session_b.submit_udp_bind(0, None).unwrap();
|
|
let (completion_a, completion_b) = tokio::join!(
|
|
wait_for_session_completion(&session_a),
|
|
wait_for_session_completion(&session_b),
|
|
);
|
|
assert_eq!(completion_a.operation_id, bind_a);
|
|
assert_eq!(completion_b.operation_id, bind_b);
|
|
let (socket_a, addr_a) = session_a
|
|
.take_result_with(bind_a, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::UdpBound { socket, local_addr }) => {
|
|
Some((*socket, *local_addr))
|
|
}
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
let (socket_b, addr_b) = session_b
|
|
.take_result_with(bind_b, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::UdpBound { socket, local_addr }) => {
|
|
Some((*socket, *local_addr))
|
|
}
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
let warmup = session_b
|
|
.submit_udp_send(socket_b, addr_a, b"warmup".to_vec())
|
|
.unwrap();
|
|
wait_for_session_completion(&session_b).await;
|
|
session_b
|
|
.take_result_with(warmup, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::UdpSent { len }) => Some(*len),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
let receive = session_b.submit_udp_receive(socket_b, 2).unwrap();
|
|
let send = session_a
|
|
.submit_udp_send(socket_a, addr_b, b"ping".to_vec())
|
|
.unwrap();
|
|
let (send_completion, receive_completion) = tokio::join!(
|
|
wait_for_session_completion(&session_a),
|
|
wait_for_session_completion(&session_b),
|
|
);
|
|
assert_eq!(send_completion.operation_id, send);
|
|
assert_eq!(receive_completion.operation_id, receive);
|
|
let (data, peer_addr, truncated) = session_b
|
|
.take_result_with(receive, |outcome| match outcome {
|
|
Ok(DataPlaneOperationResult::UdpReceived {
|
|
data,
|
|
peer_addr,
|
|
truncated,
|
|
}) => Some((data.clone(), *peer_addr, *truncated)),
|
|
_ => None,
|
|
})
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(data, b"pi");
|
|
assert_eq!(peer_addr, addr_a);
|
|
assert!(truncated);
|
|
|
|
session_a.close_resource(socket_a);
|
|
session_b.close_resource(socket_b);
|
|
session_a.stop();
|
|
session_b.stop();
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn public_tcp_connect_never_falls_back_to_an_unrelated_host() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host, "10.126.131.1/24".parse().unwrap());
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
|
|
let error = match endpoint
|
|
.gateway
|
|
.data_plane_tcp_connect("192.0.2.10:443".parse().unwrap(), Duration::from_secs(1))
|
|
.await
|
|
{
|
|
Ok(_) => panic!("public data plane unexpectedly used a Host TCP route"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(error.kind(), DataPlaneErrorKind::NoOverlayRoute);
|
|
assert_eq!(endpoint.gateway.host.tcp_binds.load(Ordering::Relaxed), 0);
|
|
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn listener_and_accepted_stream_own_independent_flow_lifetimes() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let timeout = Duration::from_secs(10);
|
|
let mut listener = b.gateway.data_plane_tcp_bind(0, timeout).await.unwrap();
|
|
let listen_addr = SocketAddr::new(b.ip.address().into(), listener.local_addr().port());
|
|
|
|
let (accepted, client) = tokio::join!(
|
|
listener.accept(),
|
|
a.gateway.data_plane_tcp_connect(listen_addr, timeout),
|
|
);
|
|
let (mut server, peer_addr) = accepted.unwrap();
|
|
let mut client = client.unwrap();
|
|
|
|
assert_eq!(client.local_addr(), peer_addr);
|
|
assert_eq!(a.gateway.entries.count(), 1);
|
|
assert_eq!(b.gateway.entries.count(), 2);
|
|
|
|
drop(listener);
|
|
assert_eq!(b.gateway.entries.count(), 1);
|
|
|
|
client.write_all(b"after-listener-drop").await.unwrap();
|
|
client.flush().await.unwrap();
|
|
let mut buf = [0u8; 19];
|
|
server.read_exact(&mut buf).await.unwrap();
|
|
assert_eq!(&buf, b"after-listener-drop");
|
|
|
|
drop(client);
|
|
assert_eq!(a.gateway.entries.count(), 0);
|
|
drop(server);
|
|
assert_eq!(b.gateway.entries.count(), 0);
|
|
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_udp_pingpong() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let timeout = Duration::from_secs(10);
|
|
let socket_a = a.gateway.data_plane_udp_bind(0, timeout).await.unwrap();
|
|
let socket_b = b.gateway.data_plane_udp_bind(0, timeout).await.unwrap();
|
|
let addr_a = SocketAddr::new(a.ip.address().into(), socket_a.local_addr().port());
|
|
let addr_b = SocketAddr::new(b.ip.address().into(), socket_b.local_addr().port());
|
|
|
|
socket_b.send_to(b"warmup", addr_a).await.unwrap();
|
|
socket_a.send_to(b"ping", addr_b).await.unwrap();
|
|
let mut buf = [0u8; 16];
|
|
let (len, from) = tokio::time::timeout(timeout, socket_b.recv_from(&mut buf))
|
|
.await
|
|
.expect("receive ping timed out")
|
|
.unwrap();
|
|
assert_eq!(&buf[..len], b"ping");
|
|
assert_eq!(from, addr_a);
|
|
|
|
socket_b.send_to(b"pong", addr_a).await.unwrap();
|
|
loop {
|
|
let (len, from) = tokio::time::timeout(timeout, socket_a.recv_from(&mut buf))
|
|
.await
|
|
.expect("receive pong timed out")
|
|
.unwrap();
|
|
if &buf[..len] == b"pong" {
|
|
assert_eq!(from, addr_b);
|
|
break;
|
|
}
|
|
}
|
|
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn udp_socket_drop_releases_every_destination_flow() {
|
|
let (a, b) = setup_data_plane_pair().await;
|
|
let timeout = Duration::from_secs(10);
|
|
let socket = a.gateway.data_plane_udp_bind(0, timeout).await.unwrap();
|
|
let first = SocketAddr::new(b.ip.address().into(), 31001);
|
|
let second = SocketAddr::new(b.ip.address().into(), 31002);
|
|
|
|
socket.send_to(b"one", first).await.unwrap();
|
|
socket.send_to(b"two", second).await.unwrap();
|
|
assert_eq!(a.gateway.entries.count(), 2);
|
|
|
|
drop(socket);
|
|
assert_eq!(a.gateway.entries.count(), 0);
|
|
|
|
stop_data_plane_pair(&a, &b).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn udp_socket_owns_a_host_port_reservation() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host.clone(), "10.126.132.1/24".parse().unwrap());
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
|
|
let socket = endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_secs(1))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(socket.local_addr().port(), 20002);
|
|
assert_eq!(host.udp_binds.load(Ordering::Relaxed), 1);
|
|
|
|
drop(socket);
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn final_data_plane_lease_releases_net_and_same_ipv4_reacquires_it() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host, "10.126.127.1/24".parse().unwrap());
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
|
|
let socket = endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_secs(1))
|
|
.await
|
|
.unwrap();
|
|
assert!(endpoint.gateway.net.lock().await.is_some());
|
|
|
|
drop(socket);
|
|
tokio::time::timeout(Duration::from_secs(1), async {
|
|
loop {
|
|
if endpoint.gateway.net.lock().await.is_none() {
|
|
break;
|
|
}
|
|
tokio::task::yield_now().await;
|
|
}
|
|
})
|
|
.await
|
|
.expect("final data-plane lease did not release smoltcp net");
|
|
|
|
let socket = endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_secs(1))
|
|
.await
|
|
.expect("same IPv4 generation should be recreated");
|
|
assert!(endpoint.gateway.net.lock().await.is_some());
|
|
|
|
drop(socket);
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn immediate_consumer_reacquire_never_leases_closing_generation() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host, "10.126.133.1/24".parse().unwrap());
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
let destination: SocketAddr = "10.126.133.2:31001".parse().unwrap();
|
|
|
|
for _ in 0..20 {
|
|
let socket = endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_secs(1))
|
|
.await
|
|
.expect("consumer should acquire the current smoltcp generation");
|
|
socket
|
|
.send_to(b"generation-probe", destination)
|
|
.await
|
|
.expect("new consumer must not inherit a closing generation");
|
|
drop(socket);
|
|
tokio::task::yield_now().await;
|
|
}
|
|
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ipv4_change_closes_existing_generation_with_typed_error() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host, "10.126.128.1/24".parse().unwrap());
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
let socket = endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_secs(1))
|
|
.await
|
|
.unwrap();
|
|
|
|
endpoint.gateway.runtime_config.update_peer_with(|peer| {
|
|
peer.runtime.core.routes.ipv4 =
|
|
Some(IpPrefix::new("10.126.129.1".parse().unwrap(), 24).unwrap());
|
|
});
|
|
|
|
let mut buf = [0u8; 1];
|
|
let error = tokio::time::timeout(Duration::from_secs(1), socket.recv_from(&mut buf))
|
|
.await
|
|
.expect("old generation receive did not wake")
|
|
.unwrap_err();
|
|
let data_plane_error = error
|
|
.get_ref()
|
|
.and_then(|error| error.downcast_ref::<DataPlaneError>())
|
|
.expect("generation close must preserve the typed data-plane error");
|
|
assert_eq!(data_plane_error.kind(), DataPlaneErrorKind::NetworkChanged);
|
|
|
|
drop(socket);
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn readiness_timeout_has_stable_error_kind() {
|
|
let host = Arc::new(TestHost::default());
|
|
let endpoint = data_plane_endpoint(host, "10.126.130.1/24".parse().unwrap());
|
|
endpoint
|
|
.gateway
|
|
.runtime_config
|
|
.update_peer_with(|peer| peer.runtime.core.routes.ipv4 = None);
|
|
endpoint.peer_manager.run().await.unwrap();
|
|
endpoint.gateway.start_runtime().await.unwrap();
|
|
|
|
let error = match endpoint
|
|
.gateway
|
|
.data_plane_udp_bind(0, Duration::from_millis(1))
|
|
.await
|
|
{
|
|
Ok(_) => panic!("data-plane bind unexpectedly succeeded without an IPv4 address"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(error.kind(), DataPlaneErrorKind::DeadlineExceeded);
|
|
|
|
endpoint.gateway.stop_runtime().await;
|
|
endpoint.peer_manager.clear_resources().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_consumes_modified_data_when_entry_matches() {
|
|
let gateway = test_gateway();
|
|
|
|
let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 1)), 40000);
|
|
let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 3)), 22);
|
|
let entry = FlowKey {
|
|
src: local,
|
|
dst: remote,
|
|
kind: TCP_ENTRY,
|
|
};
|
|
gateway.entries.insert(
|
|
entry,
|
|
FlowData::Tcp {
|
|
_reservation: Arc::new(()),
|
|
},
|
|
);
|
|
|
|
for packet_type in [
|
|
PacketType::DataWithKcpSrcModified,
|
|
PacketType::DataWithQuicSrcModified,
|
|
] {
|
|
let mut packet = ZCPacket::new_with_payload(&build_tcp_packet(remote, local));
|
|
packet.fill_peer_manager_hdr(1, 1, packet_type as u8);
|
|
|
|
let result = gateway.try_process_packet_from_peer(packet).await;
|
|
assert!(result.is_none());
|
|
|
|
let mut receiver = gateway.packet_recv.lock().await;
|
|
let received = receiver.try_recv().unwrap();
|
|
assert_eq!(
|
|
received.peer_manager_header().unwrap().packet_type,
|
|
packet_type as u8
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_passes_through_unmatched_or_malformed_modified_data() {
|
|
let gateway = test_gateway();
|
|
gateway.entries.insert(
|
|
FlowKey {
|
|
src: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 1)), 40000),
|
|
dst: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 3)), 22),
|
|
kind: TCP_ENTRY,
|
|
},
|
|
FlowData::Tcp {
|
|
_reservation: Arc::new(()),
|
|
},
|
|
);
|
|
|
|
let unmatched_local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 1)), 40001);
|
|
let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 3)), 22);
|
|
let mut unmatched_packet =
|
|
ZCPacket::new_with_payload(&build_tcp_packet(remote, unmatched_local));
|
|
unmatched_packet.fill_peer_manager_hdr(1, 2, PacketType::DataWithKcpSrcModified as u8);
|
|
let result = gateway.try_process_packet_from_peer(unmatched_packet).await;
|
|
assert!(result.is_some());
|
|
|
|
let mut malformed_packet = ZCPacket::new_with_payload(&[0u8; 8]);
|
|
malformed_packet.fill_peer_manager_hdr(1, 2, PacketType::DataWithQuicSrcModified as u8);
|
|
let result = gateway.try_process_packet_from_peer(malformed_packet).await;
|
|
assert!(result.is_some());
|
|
|
|
let mut receiver = gateway.packet_recv.lock().await;
|
|
assert!(receiver.try_recv().is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_passes_through_non_loopback_modified_data_when_entry_matches() {
|
|
let gateway = test_gateway();
|
|
|
|
let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 1)), 40000);
|
|
let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 3)), 22);
|
|
let entry = FlowKey {
|
|
src: local,
|
|
dst: remote,
|
|
kind: TCP_ENTRY,
|
|
};
|
|
gateway.entries.insert(
|
|
entry,
|
|
FlowData::Tcp {
|
|
_reservation: Arc::new(()),
|
|
},
|
|
);
|
|
|
|
let mut packet = ZCPacket::new_with_payload(&build_tcp_packet(remote, local));
|
|
packet.fill_peer_manager_hdr(1, 2, PacketType::DataWithKcpSrcModified as u8);
|
|
|
|
let result = gateway.try_process_packet_from_peer(packet).await;
|
|
assert!(result.is_some());
|
|
|
|
let mut receiver = gateway.packet_recv.lock().await;
|
|
assert!(receiver.try_recv().is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_plane_mirrors_fragmented_udp_when_entry_matches() {
|
|
let gateway = test_gateway();
|
|
|
|
let local = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 1)), 40000);
|
|
let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 144, 144, 3)), 53);
|
|
gateway.entries.insert(
|
|
FlowKey {
|
|
src: local,
|
|
dst: remote,
|
|
kind: UDP_ENTRY,
|
|
},
|
|
FlowData::Udp,
|
|
);
|
|
assert_eq!(gateway.entries.count(), 1);
|
|
|
|
let mut packet = ZCPacket::new_with_payload(&build_udp_followup_fragment(
|
|
match remote.ip() {
|
|
IpAddr::V4(ip) => ip,
|
|
IpAddr::V6(_) => unreachable!(),
|
|
},
|
|
match local.ip() {
|
|
IpAddr::V4(ip) => ip,
|
|
IpAddr::V6(_) => unreachable!(),
|
|
},
|
|
));
|
|
packet.fill_peer_manager_hdr(1, 2, PacketType::Data as u8);
|
|
|
|
let result = gateway.try_process_packet_from_peer(packet).await;
|
|
assert!(result.is_some());
|
|
|
|
let mut receiver = gateway.packet_recv.lock().await;
|
|
let received = receiver.try_recv().unwrap();
|
|
assert_eq!(
|
|
received.peer_manager_header().unwrap().packet_type,
|
|
PacketType::Data as u8
|
|
);
|
|
}
|