Files
EasyTier/easytier-core/src/gateway/dataplane/mod.rs
T
FrankHan 6e3460db7d fix(port-forward): support wildcard userspace listeners
Keep the existing Host listener intact while adding a DataPlane listener for force-smoltcp IPv4 wildcard rules. Keep literal loopback destinations on the local Host path instead of exporting them through an exit node.
2026-09-04 02:34:12 +08:00

979 lines
35 KiB
Rust

//! Data-plane access built on top of the core gateway smoltcp stack.
//!
//! This module exposes TCP streams and UDP sockets (mainly for FFI callers that
//! send traffic through EasyTier without creating OS-level proxy listeners).
//!
//! Typical usage:
//!
//! ```ignore
//! let instance = CoreInstance::new(...);
//! instance.start().await?;
//!
//! let socket = instance.data_plane_udp_bind(local_port, timeout).await?;
//! socket.send_to(buf, peer_addr).await?;
//! ```
use std::{
any::Any,
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{
Arc, Weak,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use smoltcp::wire::{IpProtocol, Ipv4Packet, TcpPacket};
use tokio::{
select,
sync::{Mutex, mpsc},
task::JoinSet,
};
use crate::{
config::runtime::CoreRuntimeConfigStore,
foundation::task::reap_joinset_background,
gateway::{
proxy::{
traits::TcpProxyStream,
wrapped_transport::{WrappedTransportKind, WrappedTransportProxyModule},
},
smoltcp::{Net, UdpSocket},
},
packet::{PacketType, ZCPacket},
peers::{
PeerPacketFilter,
peer_manager::{PeerManagerCore, PipelineRegistrationGuard},
},
socket::{
SocketContext,
tcp::{
TcpBindOptions, TcpConnectOptions, TcpListenOptions, TcpSocketPurpose,
VirtualTcpListener, VirtualTcpListenerFactory, VirtualTcpSocket,
VirtualTcpSocketFactory,
},
udp::{UdpBindOptions, VirtualUdpSocket, VirtualUdpSocketFactory},
},
};
mod deadline;
mod error;
mod flow;
mod operation;
mod packet;
mod resource;
mod route;
mod session;
mod stack;
mod tcp;
#[cfg(test)]
mod tests;
mod udp;
use self::{
deadline::{DataPlaneDeadline, DataPlaneIoDeadline},
error::DataPlaneResult,
flow::{FlowKey, FlowKind, FlowLease, FlowTable},
packet::{PeerPacketRoute, tcp_flags},
resource::{DataPlaneConsumers, DataPlaneIoGuard, DataPlaneLease},
route::{
DataPlaneRoutePolicy, DataPlaneTcpRoute, DataPlaneTcpRouteInput,
DataPlaneTransportPreference,
},
stack::SmoltcpPlane,
};
pub(crate) use self::resource::DataPlaneConsumerLease;
use self::tcp::DataPlaneTcpStreamRoute;
pub use self::{
error::{DataPlaneError, DataPlaneErrorKind},
operation::{
DataPlaneCompletionDescriptor, DataPlaneCompletionStatus, DataPlaneOperationId,
DataPlaneOperationKind, DataPlaneOperationOutcome, DataPlaneOperationResult,
DataPlaneResourceId,
},
session::{DataPlaneSession, DataPlaneSessionLimits},
tcp::{DataPlaneTcpListener, DataPlaneTcpStream},
udp::DataPlaneUdpSocket,
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct DataPlaneTcpConnectOptions {
policy: DataPlaneRoutePolicy,
transport: DataPlaneTransportPreference,
purpose: TcpSocketPurpose,
source_hint: Option<SocketAddr>,
deadline: DataPlaneDeadline,
}
impl DataPlaneTcpConnectOptions {
fn public(timeout: Duration) -> Self {
Self::public_with_timeout(Some(timeout))
}
fn public_with_timeout(timeout: Option<Duration>) -> Self {
Self::public_with_deadline(DataPlaneDeadline::from_optional_timeout(timeout))
}
fn public_with_deadline(deadline: DataPlaneDeadline) -> Self {
Self {
policy: DataPlaneRoutePolicy::OverlayOnly,
transport: DataPlaneTransportPreference::SmoltcpOnly,
purpose: TcpSocketPurpose::DataPlane,
source_hint: None,
deadline,
}
}
pub(crate) fn gateway(
timeout: Duration,
purpose: TcpSocketPurpose,
source_hint: SocketAddr,
) -> Self {
Self {
policy: DataPlaneRoutePolicy::OverlayOrDirect,
transport: DataPlaneTransportPreference::PreferKcp,
purpose,
source_hint: Some(source_hint),
deadline: DataPlaneDeadline::from_timeout(timeout),
}
}
}
pub(super) struct DataPlaneUdpIo(UdpSocket);
impl DataPlaneUdpIo {
pub async fn send_to(&self, buf: &[u8], addr: SocketAddr) -> Result<usize, std::io::Error> {
self.0.send_to(buf, addr).await
}
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr), std::io::Error> {
self.0.recv_from(buf).await
}
pub async fn recv_from_limited(
&self,
max_len: usize,
) -> Result<(Vec<u8>, SocketAddr, bool), std::io::Error> {
self.0.recv_from_limited(max_len).await
}
}
pub(super) enum FlowData {
Tcp {
_reservation: Arc<dyn Any + Send + Sync>,
},
// a data-plane routing entry that owns no resource. the entry_type in the
// key distinguishes a listen route from an actively outbound route.
DataPlaneRoute,
Udp,
}
const UDP_ENTRY: FlowKind = FlowKind::Udp;
const TCP_ENTRY: FlowKind = FlowKind::Tcp;
const TCP_LISTEN_ENTRY: FlowKind = FlowKind::TcpListen;
type FlowSet = Arc<FlowTable<FlowData>>;
type DataPlaneTcpIo = Box<dyn TcpProxyStream>;
pub(crate) struct DataPlaneRuntime<H>
where
H: VirtualTcpSocketFactory + VirtualTcpListenerFactory + VirtualUdpSocketFactory,
{
operation: Mutex<()>,
pub(super) runtime_started: AtomicBool,
runtime_guard: DataPlaneIoGuard,
runtime_config: CoreRuntimeConfigStore,
pub(super) peer_manager: Weak<PeerManagerCore>,
pub(super) transport_proxy: Option<Weak<WrappedTransportProxyModule>>,
pub(super) host: Arc<H>,
pub(super) socket_context: SocketContext,
pub(super) runtime_tasks: Arc<std::sync::Mutex<JoinSet<()>>>,
packet_sender: mpsc::Sender<ZCPacket>,
packet_recv: Arc<Mutex<mpsc::Receiver<ZCPacket>>>,
net: Arc<Mutex<Option<SmoltcpPlane>>>,
pub(super) entries: FlowSet,
data_plane_consumers: Arc<DataPlaneConsumers>,
// Tracks whether the smoltcp `net` is ready for data-plane callers.
data_plane_net_ready: tokio::sync::watch::Sender<bool>,
pipeline_guard: Mutex<Option<PipelineRegistrationGuard>>,
}
#[async_trait::async_trait]
impl<H> PeerPacketFilter for DataPlaneRuntime<H>
where
H: VirtualTcpSocketFactory + VirtualTcpListenerFactory + VirtualUdpSocketFactory,
{
async fn try_process_packet_from_peer(&self, packet: ZCPacket) -> Option<ZCPacket> {
if self.entries.is_idle() {
if tracing::enabled!(tracing::Level::TRACE)
&& let Some(hdr) = packet.peer_manager_header()
&& matches!(
hdr.packet_type,
x if x == PacketType::Data as u8
|| x == PacketType::DataWithKcpSrcModified as u8
|| x == PacketType::DataWithQuicSrcModified as u8
)
{
if let Ok(ipv4) = Ipv4Packet::new_checked(packet.payload()) {
let (tcp_src_port, tcp_dst_port, tcp_flags) =
if ipv4.next_header() == IpProtocol::Tcp {
TcpPacket::new_checked(ipv4.payload())
.ok()
.map(|tcp| {
(
Some(tcp.src_port()),
Some(tcp.dst_port()),
Some(tcp_flags(&tcp)),
)
})
.unwrap_or((None, None, None))
} else {
(None, None, None)
};
tracing::trace!(
packet_type = hdr.packet_type,
from_peer_id = hdr.from_peer_id.get(),
to_peer_id = hdr.to_peer_id.get(),
ipv4_src = %ipv4.src_addr(),
ipv4_dst = %ipv4.dst_addr(),
next_protocol = ?ipv4.next_header(),
?tcp_src_port,
?tcp_dst_port,
?tcp_flags,
entry_count = 0,
"data plane fast gate passed packet from peer"
);
} else {
tracing::trace!(
packet_type = hdr.packet_type,
from_peer_id = hdr.from_peer_id.get(),
to_peer_id = hdr.to_peer_id.get(),
entry_count = 0,
"data plane fast gate passed non-ipv4 packet from peer"
);
}
}
return Some(packet);
}
let route = self.entries.route_peer_packet(&packet, true);
let (entry_key, tcp_flags) = match route {
PeerPacketRoute::Pass => return Some(packet),
PeerPacketRoute::Unmatched { entry, tcp_flags } => {
tracing::trace!(
entry_key = ?entry,
?tcp_flags,
ipv4_src = %entry.dst.ip(),
ipv4_dst = %entry.src.ip(),
entry_count = self.entries.count(),
"data plane has no flow for packet from peer"
);
return Some(packet);
}
PeerPacketRoute::Deliver { entry, tcp_flags } => (entry, tcp_flags),
PeerPacketRoute::FragmentedUdp { source, mirror } => {
let source: IpAddr = source.into();
tracing::trace!(
is_in_entries = mirror,
"ipv4 src = {:?}, check need send both smoltcp and kernel tun",
source
);
if mirror {
// if the packet is fragmented, no matther what the payload is, need send it to both smoltcp and kernel tun. because
// we cannot determine the udp port of the packet.
match self.packet_sender.try_send(packet.clone()) {
Ok(()) => tracing::trace!(
?source,
entry_count = self.entries.count(),
"data plane delivered fragmented packet from peer to smoltcp"
),
Err(err) => tracing::trace!(
?source,
?err,
entry_count = self.entries.count(),
"data plane failed to deliver fragmented packet from peer to smoltcp"
),
}
}
return Some(packet);
}
};
tracing::trace!(
?entry_key,
?tcp_flags,
ipv4_src = %entry_key.dst.ip(),
ipv4_dst = %entry_key.src.ip(),
entry_count = self.entries.count(),
"data plane found entry for packet from peer"
);
match self.packet_sender.try_send(packet) {
Ok(()) => tracing::trace!(
?entry_key,
?tcp_flags,
entry_count = self.entries.count(),
"data plane delivered packet from peer to smoltcp"
),
Err(err) => tracing::trace!(
?entry_key,
?tcp_flags,
?err,
entry_count = self.entries.count(),
"data plane failed to deliver packet from peer to smoltcp"
),
}
None
}
}
impl<H> DataPlaneRuntime<H>
where
H: VirtualTcpSocketFactory + VirtualTcpListenerFactory + VirtualUdpSocketFactory,
{
pub(crate) fn new(
runtime_config: CoreRuntimeConfigStore,
peer_manager: Arc<PeerManagerCore>,
transport_proxy: Option<&Arc<WrappedTransportProxyModule>>,
host: Arc<H>,
socket_context: SocketContext,
) -> Arc<Self> {
let (packet_sender, packet_recv) = mpsc::channel(1024);
Arc::new(Self {
operation: Mutex::new(()),
runtime_started: AtomicBool::new(false),
runtime_guard: DataPlaneIoGuard::new(),
runtime_config,
peer_manager: Arc::downgrade(&peer_manager),
transport_proxy: transport_proxy.map(Arc::downgrade),
host,
socket_context,
runtime_tasks: Arc::new(std::sync::Mutex::new(JoinSet::new())),
packet_recv: Arc::new(Mutex::new(packet_recv)),
packet_sender,
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),
})
}
fn runtime_ipv4(runtime_config: &CoreRuntimeConfigStore) -> Option<cidr::Ipv4Inet> {
let prefix = runtime_config
.snapshot()
.peer
.runtime
.core
.routes
.ipv4
.clone()?;
let IpAddr::V4(address) = prefix.address else {
return None;
};
cidr::Ipv4Inet::new(address, prefix.prefix_len).ok()
}
pub(crate) fn is_local_virtual_ip(&self, ip: IpAddr) -> bool {
Self::runtime_ipv4(&self.runtime_config)
.is_some_and(|inet| IpAddr::V4(inet.address()) == ip)
}
async fn run_net_update_task(self: &Arc<Self>) {
let net = self.net.clone();
let runtime_config = self.runtime_config.clone();
let peer_manager = self.peer_manager.clone();
let packet_recv = self.packet_recv.clone();
let entries = self.entries.clone();
let data_plane_consumers = self.data_plane_consumers.clone();
let data_plane_net_ready = self.data_plane_net_ready.clone();
self.runtime_tasks.lock().unwrap().spawn(async move {
let mut prev_ipv4 = None;
let mut peer_changes = runtime_config.subscribe_peer_runtime_changes();
loop {
let data_plane_active = data_plane_consumers.has_consumers();
if !data_plane_active {
let old_net = {
let mut net_guard = net.lock().await;
// New leases and the zero-consumer teardown decision
// are serialized while this net slot is held. A bind
// can retain this generation or wait for its
// replacement, but cannot receive a stale generation.
if data_plane_consumers.has_consumers() {
continue;
}
net_guard.take()
};
if let Some(old_net) = &old_net {
old_net.close(DataPlaneErrorKind::HandleClosed);
}
let had_net = old_net.is_some();
prev_ipv4 = None;
let cleared = entries.clear();
tracing::trace!(
had_net,
data_plane_active,
removed_entries = cleared.removed,
entry_count = cleared.count.current,
entries_len = entries.len(),
"data plane waiting for consumers"
);
let _ = data_plane_net_ready.send_replace(false);
select! {
_ = peer_changes.changed() => {}
_ = data_plane_consumers.changed() => {}
}
continue;
}
let cur_ipv4 = Self::runtime_ipv4(&runtime_config);
if prev_ipv4 != cur_ipv4 {
let old_ipv4 = prev_ipv4;
prev_ipv4 = cur_ipv4;
tracing::trace!(
?old_ipv4,
?cur_ipv4,
old_entry_count = entries.count(),
old_entries_len = entries.len(),
"data plane resetting flows for ipv4 change"
);
let _ = data_plane_net_ready.send_replace(false);
let old_net = net.lock().await.take();
if let Some(old_net) = old_net {
old_net.close(DataPlaneErrorKind::NetworkChanged);
}
let cleared = entries.clear();
tracing::trace!(
?old_ipv4,
?cur_ipv4,
removed_entries = cleared.removed,
new_entry_count = cleared.count.current,
new_entries_len = entries.len(),
"data plane reset flows complete"
);
if let Some(cur_ipv4) = cur_ipv4 {
net.lock().await.replace(SmoltcpPlane::new(
cur_ipv4,
peer_manager.clone(),
packet_recv.clone(),
));
tracing::trace!(
?cur_ipv4,
entry_count = entries.count(),
entries_len = entries.len(),
"data plane installed smoltcp net"
);
// Wake any data-plane callers waiting in
// `wait_data_plane_net` for the smoltcp net to appear.
let _ = data_plane_net_ready.send_replace(true);
} else {
tracing::trace!(
entry_count = entries.count(),
entries_len = entries.len(),
"data plane removed smoltcp net"
);
}
}
select! {
_ = peer_changes.changed() => {}
_ = data_plane_consumers.changed() => {}
}
}
});
}
async fn start_runtime_inner(self: &Arc<Self>) -> anyhow::Result<()> {
let Some(peer_manager) = self.peer_manager.upgrade() else {
return Err(anyhow::anyhow!("peer manager is gone"));
};
let guard = peer_manager
.add_managed_packet_process_pipeline(Box::new(self.clone()))
.await;
self.pipeline_guard.lock().await.replace(guard);
self.runtime_tasks
.lock()
.unwrap()
.spawn(reap_joinset_background(
Arc::downgrade(&self.runtime_tasks),
"data plane runtime",
));
self.run_net_update_task().await;
tracing::trace!("data plane peer packet pipeline registered");
Ok(())
}
pub(crate) async fn start_runtime(self: &Arc<Self>) -> anyhow::Result<()> {
let _operation = self.operation.lock().await;
if self.runtime_started.load(Ordering::Acquire) {
return Ok(());
}
if let Err(error) = self.start_runtime_inner().await {
self.stop_runtime_inner().await;
return Err(error);
}
self.runtime_started.store(true, Ordering::Release);
Ok(())
}
async fn shutdown_tasks(tasks: &Arc<std::sync::Mutex<JoinSet<()>>>) {
let mut tasks = {
let mut guard = tasks.lock().unwrap();
std::mem::replace(&mut *guard, JoinSet::new())
};
tasks.shutdown().await;
}
async fn stop_runtime_inner(&self) {
self.runtime_started.store(false, Ordering::Release);
self.runtime_guard
.close(DataPlaneErrorKind::InstanceStopped);
if let Some(guard) = self.pipeline_guard.lock().await.take() {
guard.close();
}
if let Some(net) = self.net.lock().await.take() {
net.close(DataPlaneErrorKind::InstanceStopped);
}
let _ = self.data_plane_net_ready.send_replace(false);
self.entries.clear();
Self::shutdown_tasks(&self.runtime_tasks).await;
}
pub(crate) async fn stop_runtime(&self) {
let _operation = self.operation.lock().await;
self.stop_runtime_inner().await;
}
}
impl<H> DataPlaneRuntime<H>
where
H: VirtualTcpSocketFactory + VirtualTcpListenerFactory + VirtualUdpSocketFactory,
{
async fn tcp_route_input(
&self,
dst_addr: SocketAddr,
options: DataPlaneTcpConnectOptions,
) -> DataPlaneResult<DataPlaneTcpRouteInput> {
let IpAddr::V4(dst_ip) = dst_addr.ip() else {
return Err(DataPlaneError::new(
DataPlaneErrorKind::AddressFamilyUnsupported,
"the EasyTier data plane currently supports IPv4 destinations only",
));
};
self.runtime_guard.ensure_open()?;
let local_virtual_ip = Self::runtime_ipv4(&self.runtime_config).map(|inet| inet.address());
let local_virtual_destination = local_virtual_ip == Some(dst_ip);
let local_endpoint = local_virtual_destination
&& self.entries.contains_key(&FlowKey {
src: dst_addr,
dst: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
kind: TCP_LISTEN_ENTRY,
});
let peer_manager = self.peer_manager.upgrade().ok_or_else(|| {
DataPlaneError::new(
DataPlaneErrorKind::InstanceStopped,
"peer manager is no longer available",
)
})?;
let overlay_destination = if local_virtual_destination {
true
} else if dst_ip.is_loopback() {
false
} else {
let (peers, _) = options
.deadline
.run(async {
Ok::<_, DataPlaneError>(peer_manager.get_msg_dst_peer(&dst_addr.ip()).await)
})
.await?;
!peers.is_empty()
};
let prefer_kcp = options.transport == DataPlaneTransportPreference::PreferKcp;
let transport_proxy = self.transport_proxy.as_ref().and_then(Weak::upgrade);
let kcp_ready = if prefer_kcp && overlay_destination {
match &transport_proxy {
Some(proxy) => {
options
.deadline
.run(async {
Ok::<_, DataPlaneError>(
proxy.source_connect_ready(WrappedTransportKind::Kcp).await,
)
})
.await?
}
None => false,
}
} else {
false
};
let kcp_allowed = if prefer_kcp && kcp_ready {
options
.deadline
.run(async {
Ok::<_, DataPlaneError>(
peer_manager.check_allow_kcp_to_dst(&dst_addr.ip()).await,
)
})
.await?
} else {
false
};
Ok(DataPlaneTcpRouteInput {
policy: options.policy,
transport: options.transport,
local_endpoint,
local_virtual_destination,
overlay_destination,
smoltcp_ready: self.net.lock().await.is_some(),
kcp_ready,
kcp_allowed,
})
}
async fn connect_smoltcp_tcp(
&self,
dst_addr: SocketAddr,
deadline: DataPlaneDeadline,
data_plane_ref: DataPlaneLease,
) -> DataPlaneResult<DataPlaneTcpStream> {
let (ipv4_addr, smoltcp_net, generation) = self.wait_data_plane_net(deadline).await?;
let listen_options = TcpListenOptions::port_lease("0.0.0.0:0".parse().unwrap());
let reservation = generation
.while_open(
deadline.run(
self.host.bind_tcp(
listen_options.clone().with_bind(
listen_options
.bind
.with_context(self.socket_context.clone()),
),
),
),
)
.await?;
let local_port = reservation
.local_addr()
.map_err(DataPlaneError::from)?
.port();
let local_addr = SocketAddr::new(IpAddr::V4(ipv4_addr.address()), local_port);
let (flow, _) = FlowLease::register(
self.entries.clone(),
FlowKey {
src: local_addr,
dst: dst_addr,
kind: TCP_ENTRY,
},
FlowData::Tcp {
_reservation: reservation,
},
);
let stream = generation
.while_open(deadline.run(smoltcp_net.tcp_connect(dst_addr, local_port)))
.await?;
generation.ensure_open()?;
Ok(DataPlaneTcpStream::new(
Box::new(stream),
local_addr,
Some(data_plane_ref),
DataPlaneTcpStreamRoute::Outbound { _flow: flow },
generation,
))
}
async fn connect_host_tcp(
&self,
dst_addr: SocketAddr,
options: DataPlaneTcpConnectOptions,
) -> DataPlaneResult<DataPlaneTcpStream> {
let connect_options = TcpConnectOptions::direct_connect(dst_addr)
.with_purpose(options.purpose)
.with_bind(TcpBindOptions::default().with_context(self.socket_context.clone()));
let socket = self
.runtime_guard
.while_open(options.deadline.run(self.host.connect_tcp(connect_options)))
.await?;
let local_addr = socket.local_addr().map_err(DataPlaneError::from)?;
Ok(DataPlaneTcpStream::new(
Box::new(socket),
local_addr,
None,
DataPlaneTcpStreamRoute::External,
self.runtime_guard.clone(),
))
}
async fn connect_kcp_tcp(
&self,
dst_addr: SocketAddr,
options: DataPlaneTcpConnectOptions,
) -> DataPlaneResult<DataPlaneTcpStream> {
let source_addr = options.source_hint.ok_or_else(|| {
DataPlaneError::new(
DataPlaneErrorKind::PathNotReady,
"KCP gateway route requires a logical source address",
)
})?;
let transport_proxy = self
.transport_proxy
.as_ref()
.and_then(Weak::upgrade)
.ok_or_else(|| {
DataPlaneError::new(
DataPlaneErrorKind::PathNotReady,
"KCP source transport is not ready",
)
})?;
let stream = self
.runtime_guard
.while_open(options.deadline.run(transport_proxy.connect_source(
WrappedTransportKind::Kcp,
source_addr,
dst_addr,
)))
.await?;
Ok(DataPlaneTcpStream::new(
stream,
source_addr,
None,
DataPlaneTcpStreamRoute::External,
self.runtime_guard.clone(),
))
}
pub(crate) async fn connect_tcp(
&self,
dst_addr: SocketAddr,
options: DataPlaneTcpConnectOptions,
) -> DataPlaneResult<DataPlaneTcpStream> {
let mut route_input = self.tcp_route_input(dst_addr, options).await?;
let route = match route_input.select() {
Ok(route) => route,
Err(error) if error.kind() == DataPlaneErrorKind::PathNotReady => {
let data_plane_ref = self.acquire_data_plane_ref()?;
let _ = self.wait_data_plane_net(options.deadline).await?;
route_input.smoltcp_ready = true;
let route = route_input.select()?;
return self
.connect_selected_tcp(dst_addr, options, route, Some(data_plane_ref))
.await;
}
Err(error) => return Err(error),
};
self.connect_selected_tcp(dst_addr, options, route, None)
.await
}
async fn connect_selected_tcp(
&self,
dst_addr: SocketAddr,
options: DataPlaneTcpConnectOptions,
route: DataPlaneTcpRoute,
data_plane_ref: Option<DataPlaneLease>,
) -> DataPlaneResult<DataPlaneTcpStream> {
tracing::debug!(?dst_addr, ?route, "selected data-plane TCP route");
match route {
DataPlaneTcpRoute::LocalEndpoint | DataPlaneTcpRoute::Smoltcp => {
let data_plane_ref = match data_plane_ref {
Some(lease) => lease,
None => self.acquire_data_plane_ref()?,
};
self.connect_smoltcp_tcp(dst_addr, options.deadline, data_plane_ref)
.await
}
DataPlaneTcpRoute::LocalHost => {
self.connect_host_tcp(
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst_addr.port()),
options,
)
.await
}
DataPlaneTcpRoute::Kcp => self.connect_kcp_tcp(dst_addr, options).await,
DataPlaneTcpRoute::Direct => self.connect_host_tcp(dst_addr, options).await,
}
}
fn acquire_data_plane_ref(&self) -> DataPlaneResult<DataPlaneLease> {
if !self.runtime_started.load(Ordering::Acquire) || self.runtime_guard.is_closed() {
return Err(DataPlaneError::new(
DataPlaneErrorKind::InstanceStopped,
"data-plane runtime is not running",
));
}
let lease = DataPlaneLease::acquire(self.data_plane_consumers.clone());
if !self.runtime_started.load(Ordering::Acquire) || self.runtime_guard.is_closed() {
drop(lease);
return Err(DataPlaneError::new(
DataPlaneErrorKind::InstanceStopped,
"data-plane runtime stopped while acquiring a resource",
));
}
Ok(lease)
}
pub(crate) fn acquire_consumer_lease(&self) -> DataPlaneResult<DataPlaneConsumerLease> {
self.acquire_data_plane_ref()
.map(DataPlaneConsumerLease::new)
}
async fn wait_data_plane_net(
&self,
deadline: DataPlaneDeadline,
) -> DataPlaneResult<(cidr::Ipv4Inet, Arc<Net>, DataPlaneIoGuard)> {
let mut ready = self.data_plane_net_ready.subscribe();
loop {
if let Some(net) = self
.net
.lock()
.await
.as_ref()
.map(|plane| (plane.ipv4_addr, plane.net.clone(), plane.lease()))
{
net.2.ensure_open()?;
return Ok(net);
}
tokio::select! {
_ = self.runtime_guard.closed() => {
return Err(DataPlaneError::new(
DataPlaneErrorKind::InstanceStopped,
"data-plane runtime stopped while waiting for the stack",
));
}
result = deadline.run(async {
ready
.wait_for(|ready| *ready)
.await
.map(|_| ())
.map_err(|_| DataPlaneError::new(
DataPlaneErrorKind::InstanceStopped,
"data-plane readiness channel closed",
))
}) => {
result?;
}
}
}
}
pub async fn data_plane_tcp_connect(
&self,
dst_addr: SocketAddr,
timeout: Duration,
) -> DataPlaneResult<DataPlaneTcpStream> {
self.connect_tcp(dst_addr, DataPlaneTcpConnectOptions::public(timeout))
.await
}
pub async fn data_plane_tcp_bind(
&self,
local_port: u16,
timeout: Duration,
) -> DataPlaneResult<DataPlaneTcpListener> {
self.data_plane_tcp_bind_with_deadline(local_port, DataPlaneDeadline::from_timeout(timeout))
.await
}
async fn data_plane_tcp_bind_with_deadline(
&self,
local_port: u16,
deadline: DataPlaneDeadline,
) -> DataPlaneResult<DataPlaneTcpListener> {
let data_plane_ref = self.acquire_data_plane_ref()?;
let (ipv4_addr, smoltcp_net, generation) = self.wait_data_plane_net(deadline).await?;
let bind_addr = SocketAddr::new(IpAddr::V4(ipv4_addr.address()), local_port);
let listener = generation
.while_open(deadline.run(smoltcp_net.tcp_bind(bind_addr)))
.await?;
generation.ensure_open()?;
let local_addr = listener.local_addr().map_err(DataPlaneError::from)?;
let listen_route = FlowLease::try_register(
self.entries.clone(),
FlowKey {
src: local_addr,
dst: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
kind: TCP_LISTEN_ENTRY,
},
FlowData::DataPlaneRoute,
)
.ok_or_else(|| {
DataPlaneError::new(
DataPlaneErrorKind::AddressInUse,
"data-plane TCP listener already exists",
)
})?;
Ok(DataPlaneTcpListener {
listener,
local_addr,
flows: self.entries.clone(),
_listen_flow: listen_route,
data_plane_lease: data_plane_ref,
generation,
})
}
pub async fn data_plane_udp_bind(
&self,
local_port: u16,
timeout: Duration,
) -> DataPlaneResult<DataPlaneUdpSocket> {
self.data_plane_udp_bind_with_deadline(local_port, DataPlaneDeadline::from_timeout(timeout))
.await
}
async fn data_plane_udp_bind_with_deadline(
&self,
local_port: u16,
deadline: DataPlaneDeadline,
) -> DataPlaneResult<DataPlaneUdpSocket> {
let data_plane_ref = self.acquire_data_plane_ref()?;
let (ipv4_addr, smoltcp_net, generation) = self.wait_data_plane_net(deadline).await?;
let reservation_options = UdpBindOptions::port_lease(SocketAddr::new(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
local_port,
))
.with_context(self.socket_context.clone());
let reservation = generation
.while_open(deadline.run(self.host.bind_udp(reservation_options)))
.await?;
let reserved_port = reservation
.local_addr()
.map_err(DataPlaneError::from)?
.port();
let bind_addr = SocketAddr::new(IpAddr::V4(ipv4_addr.address()), reserved_port);
let smol = generation
.while_open(deadline.run(smoltcp_net.udp_bind(bind_addr)))
.await?;
generation.ensure_open()?;
let local_addr = smol.local_addr().map_err(DataPlaneError::from)?;
let socket = Arc::new(DataPlaneUdpIo(smol));
Ok(DataPlaneUdpSocket {
socket,
flows: self.entries.clone(),
routes: std::sync::Mutex::new(std::collections::HashMap::new()),
local_addr,
_reservation: reservation,
_data_plane_lease: data_plane_ref,
generation,
})
}
}