mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-13 07:46:15 -08:00
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.
979 lines
35 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|