mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-01 16:59:21 +00:00
refactor(core): separate portable core from native runtime (#2451)
Create easytier-core as the portable owner of configuration, connectivity, tunnels, peer and routing state, gateways, management, the data plane, and instance lifecycle. Keep operating-system integration, native protocol engines, process startup, and presentation in easytier behind explicit Host capability adapters. Create easytier-proto to own schemas, generated RPC types, descriptors, and feature-scoped protocol slices. Remove runtime protobuf reflection from core while preserving unknown route-peer fields across forwarding. Normalize instance construction through CoreInstance, CoreHostAdapters, CoreProcessRuntime, and InstanceManager. Make the runtime config store the only authoritative mutable configuration after startup. Move the portable TCP/UDP data plane into core and extract a generic OperationBroker for completion, cancellation, disposal, and capacity accounting. Expose the session-based FFI v2 completion API and keep the WASI guest ABI, wire schemas, and adapters with core. Migrate CLI, GUI, web, FFI, Android JNI, OHOS, uptime, and mobile consumers to the shared manager and core state. Add explicit user/web config ownership and revision-aware web reconciliation. Preserve configuration, wire, and management behavior while fixing regressions discovered by the full platform and integration matrix: - inherit advertised relay capabilities in foreign networks; - refresh OSPF peer state immediately after runtime config changes; - restore CLI GlobalCtx event output without forcing GUI logging; - retain legacy encryption names and standalone RPC tunnel metadata; - restore ICMP host composition and fragmented UDP handling; - use portable 64-bit atomics on 32-bit MIPS targets; and - retain discarded operations until late cancellation completes. Validate the refactor across 45 GitHub checks, including Linux, macOS, Windows, FreeBSD, web, GUI, Android, OHOS, feature profiles, and three-node and subnet-proxy integration tests. BREAKING CHANGE: internal Rust module paths are not preserved. Legacy native data-plane APIs are replaced by the session-based FFI v2 API. The dedicated Android data-plane wrapper is removed.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//! Core-visible socket primitives.
|
||||
//!
|
||||
//! This Module is below [`crate::tunnel`]. Sockets represent established or
|
||||
//! bindable communication endpoints; tunnels are produced later by runtime
|
||||
//! upgraders and can be handed to peers. Host capability seams (DNS, packet
|
||||
//! egress, environment facts, and the WASI mechanism backend) live in
|
||||
//! [`crate::host`].
|
||||
|
||||
pub mod ring;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub trait ListenerConnectionCounter: Debug + Send + Sync {
|
||||
fn get(&self) -> Option<u32>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EmptyConnectionCounter;
|
||||
|
||||
impl ListenerConnectionCounter for EmptyConnectionCounter {
|
||||
fn get(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[auto_impl::auto_impl(Box)]
|
||||
pub trait SocketListener: Debug + Send {
|
||||
type Accepted: Send + 'static;
|
||||
|
||||
async fn listen(&mut self) -> anyhow::Result<()>;
|
||||
|
||||
async fn accept(&mut self) -> anyhow::Result<Self::Accepted>;
|
||||
|
||||
fn local_url(&self) -> Url;
|
||||
|
||||
fn connection_counter(&self) -> Arc<dyn ListenerConnectionCounter> {
|
||||
Arc::new(EmptyConnectionCounter)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum IpVersion {
|
||||
V4,
|
||||
V6,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NetNamespace(String);
|
||||
|
||||
impl NetNamespace {
|
||||
pub fn new(token: impl Into<String>) -> Self {
|
||||
Self(token.into())
|
||||
}
|
||||
|
||||
pub fn token(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SocketContext {
|
||||
pub ip_version: IpVersion,
|
||||
pub socket_mark: Option<u32>,
|
||||
pub netns: Option<NetNamespace>,
|
||||
}
|
||||
|
||||
impl SocketContext {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_ip_version(mut self, ip_version: IpVersion) -> Self {
|
||||
self.ip_version = ip_version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_socket_mark(mut self, socket_mark: Option<u32>) -> Self {
|
||||
self.socket_mark = socket_mark;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_netns(mut self, netns: Option<NetNamespace>) -> Self {
|
||||
self.netns = netns;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SocketContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ip_version: IpVersion::Both,
|
||||
socket_mark: None,
|
||||
netns: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
task::{Context, Poll, ready},
|
||||
};
|
||||
|
||||
use async_ringbuf::{AsyncHeapCons, AsyncHeapProd, AsyncHeapRb, traits::*};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const RING_SOCKET_CAPACITY: usize = 128;
|
||||
const RING_SOCKET_RESERVED_CAPACITY: usize = 4;
|
||||
|
||||
pub type RingSocketId = Uuid;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RingSocketError {
|
||||
#[error("ring socket already split")]
|
||||
AlreadySplit,
|
||||
#[error("ring socket closed")]
|
||||
Closed,
|
||||
#[error("ring socket full")]
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RingSocketSendError<T> {
|
||||
#[error("ring socket closed")]
|
||||
Closed(T),
|
||||
#[error("ring socket full")]
|
||||
Full(T),
|
||||
}
|
||||
|
||||
pub type RingSocketStreamItem<T> = Result<T, RingSocketError>;
|
||||
|
||||
/// An in-process socket primitive.
|
||||
///
|
||||
/// `RingSocket` is intentionally below `Tunnel`: it contains no tunnel schema,
|
||||
/// no peer metadata, and no `TunnelInfo`. The core ring tunnel Module wraps it
|
||||
/// into a `Tunnel` when a peer connection needs one.
|
||||
pub struct RingSocket<T> {
|
||||
id: RingSocketId,
|
||||
parts: Mutex<Option<RingSocketParts<T>>>,
|
||||
}
|
||||
|
||||
struct RingSocketParts<T> {
|
||||
recv: AsyncHeapCons<T>,
|
||||
send: AsyncHeapProd<T>,
|
||||
}
|
||||
|
||||
impl<T> RingSocket<T> {
|
||||
pub fn pair(capacity: usize) -> (Arc<Self>, Arc<Self>) {
|
||||
Self::pair_with_ids(Uuid::new_v4(), Uuid::new_v4(), capacity)
|
||||
}
|
||||
|
||||
pub fn pair_with_ids(
|
||||
first_id: RingSocketId,
|
||||
second_id: RingSocketId,
|
||||
capacity: usize,
|
||||
) -> (Arc<Self>, Arc<Self>) {
|
||||
let capacity = std::cmp::max(RING_SOCKET_RESERVED_CAPACITY * 2, capacity);
|
||||
let first_to_second = AsyncHeapRb::new(capacity);
|
||||
let second_to_first = AsyncHeapRb::new(capacity);
|
||||
let (first_to_second_send, first_to_second_recv) = first_to_second.split();
|
||||
let (second_to_first_send, second_to_first_recv) = second_to_first.split();
|
||||
|
||||
(
|
||||
Arc::new(Self {
|
||||
id: first_id,
|
||||
parts: Mutex::new(Some(RingSocketParts {
|
||||
recv: second_to_first_recv,
|
||||
send: first_to_second_send,
|
||||
})),
|
||||
}),
|
||||
Arc::new(Self {
|
||||
id: second_id,
|
||||
parts: Mutex::new(Some(RingSocketParts {
|
||||
recv: first_to_second_recv,
|
||||
send: second_to_first_send,
|
||||
})),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> RingSocketId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn split(&self) -> (RingSocketReceiver<T>, RingSocketSender<T>) {
|
||||
self.try_split().expect("RingSocket can only be split once")
|
||||
}
|
||||
|
||||
pub fn try_split(
|
||||
&self,
|
||||
) -> Result<(RingSocketReceiver<T>, RingSocketSender<T>), RingSocketError> {
|
||||
let parts = self
|
||||
.parts
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.ok_or(RingSocketError::AlreadySplit)?;
|
||||
|
||||
Ok((
|
||||
RingSocketReceiver {
|
||||
id: self.id,
|
||||
recv: parts.recv,
|
||||
},
|
||||
RingSocketSender {
|
||||
id: self.id,
|
||||
send: parts.send,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for RingSocket<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RingSocket")
|
||||
.field("id", &self.id)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RingSocketReceiver<T> {
|
||||
id: RingSocketId,
|
||||
recv: AsyncHeapCons<T>,
|
||||
}
|
||||
|
||||
impl<T> Stream for RingSocketReceiver<T> {
|
||||
type Item = RingSocketStreamItem<T>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match ready!(self.get_mut().recv.poll_next_unpin(cx)) {
|
||||
Some(item) => Poll::Ready(Some(Ok(item))),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for RingSocketReceiver<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RingSocketReceiver")
|
||||
.field("id", &self.id)
|
||||
.field("len", &self.recv.base().occupied_len())
|
||||
.field("cap", &self.recv.base().capacity())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RingSocketSender<T> {
|
||||
id: RingSocketId,
|
||||
send: AsyncHeapProd<T>,
|
||||
}
|
||||
|
||||
impl<T> RingSocketSender<T> {
|
||||
pub fn try_send(&mut self, item: T) -> Result<(), RingSocketSendError<T>> {
|
||||
if self.send.is_closed() {
|
||||
return Err(RingSocketSendError::Closed(item));
|
||||
}
|
||||
|
||||
let base = self.send.base();
|
||||
if base.occupied_len() >= base.capacity().get() - RING_SOCKET_RESERVED_CAPACITY {
|
||||
return Err(RingSocketSendError::Full(item));
|
||||
}
|
||||
|
||||
self.send.try_push(item).map_err(|item| {
|
||||
if self.send.is_closed() {
|
||||
RingSocketSendError::Closed(item)
|
||||
} else {
|
||||
RingSocketSendError::Full(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn force_send(&mut self, item: T) -> Result<(), RingSocketSendError<T>> {
|
||||
if self.send.is_closed() {
|
||||
return Err(RingSocketSendError::Closed(item));
|
||||
}
|
||||
|
||||
self.send.try_push(item).map_err(|item| {
|
||||
if self.send.is_closed() {
|
||||
RingSocketSendError::Closed(item)
|
||||
} else {
|
||||
RingSocketSendError::Full(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sink<T> for RingSocketSender<T> {
|
||||
type Error = RingSocketError;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.get_mut().send.poll_ready_unpin(cx)).map_err(|_| RingSocketError::Closed)?;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
|
||||
self.get_mut()
|
||||
.force_send(item)
|
||||
.map_err(|error| match error {
|
||||
RingSocketSendError::Closed(_) => RingSocketError::Closed,
|
||||
RingSocketSendError::Full(_) => RingSocketError::Full,
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.get_mut().send.poll_flush_unpin(cx)).map_err(|_| RingSocketError::Closed)?;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.get_mut().send.poll_close_unpin(cx)).map_err(|_| RingSocketError::Closed)?;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for RingSocketSender<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RingSocketSender")
|
||||
.field("id", &self.id)
|
||||
.field("len", &self.send.base().occupied_len())
|
||||
.field("cap", &self.send.base().capacity())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
||||
use crate::packet::ZCPacket;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn ring_socket_pair_transfers_packets() {
|
||||
let (left, right) = RingSocket::<ZCPacket>::pair(8);
|
||||
let (_left_recv, mut left_send) = left.split();
|
||||
let (mut right_recv, _right_send) = right.split();
|
||||
|
||||
let packet = ZCPacket::new_with_payload(&[1, 2, 3]);
|
||||
left_send.send(packet.clone()).await.unwrap();
|
||||
|
||||
let received = right_recv.next().await.unwrap().unwrap();
|
||||
assert_eq!(received.payload(), packet.payload());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_socket_split_is_single_use() {
|
||||
let (left, _right) = RingSocket::<ZCPacket>::pair(8);
|
||||
|
||||
let _first = left.try_split().unwrap();
|
||||
assert_eq!(left.try_split().unwrap_err(), RingSocketError::AlreadySplit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_socket_try_send_reserves_capacity() {
|
||||
let (left, _right) = RingSocket::<ZCPacket>::pair(8);
|
||||
let (_left_recv, mut left_send) = left.split();
|
||||
|
||||
for _ in 0..4 {
|
||||
left_send
|
||||
.try_send(ZCPacket::new_with_payload(&[1]))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert!(
|
||||
left_send
|
||||
.try_send(ZCPacket::new_with_payload(&[1]))
|
||||
.is_err_and(|error| matches!(error, RingSocketSendError::Full(_)))
|
||||
);
|
||||
assert!(
|
||||
left_send
|
||||
.force_send(ZCPacket::new_with_payload(&[1]))
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_socket_sync_send_reports_closed_receiver() {
|
||||
let (left, right) = RingSocket::<ZCPacket>::pair(8);
|
||||
let (_left_recv, mut left_send) = left.split();
|
||||
let (right_recv, _right_send) = right.split();
|
||||
drop(right_recv);
|
||||
|
||||
assert!(
|
||||
left_send
|
||||
.try_send(ZCPacket::new_with_payload(&[1]))
|
||||
.is_err_and(|error| matches!(error, RingSocketSendError::Closed(_)))
|
||||
);
|
||||
assert!(
|
||||
left_send
|
||||
.force_send(ZCPacket::new_with_payload(&[1]))
|
||||
.is_err_and(|error| matches!(error, RingSocketSendError::Closed(_)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,683 @@
|
||||
use std::{fmt, io, net::SocketAddr, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::socket::{IpVersion, SocketContext, SocketListener};
|
||||
|
||||
/// A core-visible TCP stream endpoint.
|
||||
///
|
||||
/// Implementations are runtime adapters over concrete TCP stream types. This
|
||||
/// trait deliberately stays below tunnel framing: it only exposes stream I/O and
|
||||
/// socket addresses.
|
||||
pub trait VirtualTcpSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static {
|
||||
fn local_addr(&self) -> io::Result<SocketAddr>;
|
||||
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr>;
|
||||
|
||||
/// Optional host transport label retained in tunnel management metadata.
|
||||
fn transport_label(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TcpSocketPurpose {
|
||||
DirectConnect,
|
||||
FakeTcp,
|
||||
HolePunch,
|
||||
ManualConnect,
|
||||
ProxyNat,
|
||||
StunProbe,
|
||||
Socks5,
|
||||
PortForward,
|
||||
DataPlane,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TcpBindOptions {
|
||||
#[serde(default)]
|
||||
pub context: SocketContext,
|
||||
pub local_addr: Option<SocketAddr>,
|
||||
pub bind_device: Option<String>,
|
||||
/// `None` delegates the platform default to the host socket adapter.
|
||||
pub reuse_addr: Option<bool>,
|
||||
pub reuse_port: bool,
|
||||
pub only_v6: bool,
|
||||
}
|
||||
|
||||
impl TcpBindOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
context: SocketContext::default(),
|
||||
local_addr: None,
|
||||
bind_device: None,
|
||||
reuse_addr: None,
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_local_addr(mut self, local_addr: Option<SocketAddr>) -> Self {
|
||||
self.local_addr = local_addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_socket_mark(mut self, socket_mark: Option<u32>) -> Self {
|
||||
self.context.socket_mark = socket_mark;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_context(mut self, context: SocketContext) -> Self {
|
||||
self.context = context;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ip_version(mut self, ip_version: IpVersion) -> Self {
|
||||
self.context.ip_version = ip_version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bind_device(mut self, bind_device: Option<String>) -> Self {
|
||||
self.bind_device = bind_device;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reuse_addr(mut self, reuse_addr: bool) -> Self {
|
||||
self.reuse_addr = Some(reuse_addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reuse_port(mut self, reuse_port: bool) -> Self {
|
||||
self.reuse_port = reuse_port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_only_v6(mut self, only_v6: bool) -> Self {
|
||||
self.only_v6 = only_v6;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TcpBindOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TcpConnectOptions {
|
||||
pub remote_addr: SocketAddr,
|
||||
pub bind: TcpBindOptions,
|
||||
pub purpose: TcpSocketPurpose,
|
||||
}
|
||||
|
||||
impl TcpConnectOptions {
|
||||
pub fn direct_connect(remote_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default(),
|
||||
purpose: TcpSocketPurpose::DirectConnect,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_purpose(mut self, purpose: TcpSocketPurpose) -> Self {
|
||||
self.purpose = purpose;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hole_punch(remote_addr: SocketAddr, local_addr: Option<SocketAddr>) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(local_addr),
|
||||
purpose: TcpSocketPurpose::HolePunch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn manual_connect(remote_addr: SocketAddr, local_addr: Option<SocketAddr>) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(local_addr),
|
||||
purpose: TcpSocketPurpose::ManualConnect,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proxy_nat(remote_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default(),
|
||||
purpose: TcpSocketPurpose::ProxyNat,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stun_probe(remote_addr: SocketAddr, local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpSocketPurpose::StunProbe,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socks5(remote_addr: SocketAddr) -> Self {
|
||||
Self::direct_connect(remote_addr).with_purpose(TcpSocketPurpose::Socks5)
|
||||
}
|
||||
|
||||
pub fn port_forward(remote_addr: SocketAddr) -> Self {
|
||||
Self::direct_connect(remote_addr).with_purpose(TcpSocketPurpose::PortForward)
|
||||
}
|
||||
|
||||
pub fn data_plane(remote_addr: SocketAddr) -> Self {
|
||||
Self::direct_connect(remote_addr).with_purpose(TcpSocketPurpose::DataPlane)
|
||||
}
|
||||
|
||||
pub fn with_bind(mut self, bind: TcpBindOptions) -> Self {
|
||||
self.bind = bind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait VirtualTcpSocketFactory: Send + Sync + 'static {
|
||||
type Socket: VirtualTcpSocket;
|
||||
|
||||
async fn connect_tcp(&self, options: TcpConnectOptions) -> anyhow::Result<Self::Socket>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait VirtualTcpListener: Send + Sync + 'static {
|
||||
type Socket: VirtualTcpSocket;
|
||||
|
||||
fn local_addr(&self) -> io::Result<SocketAddr>;
|
||||
|
||||
async fn accept(&self) -> io::Result<(Self::Socket, SocketAddr)>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum TcpListenPurpose {
|
||||
DirectConnect,
|
||||
HolePunch,
|
||||
ManualConnect,
|
||||
ProxyNat,
|
||||
Socks5,
|
||||
PortForward,
|
||||
PortLease,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TcpListenOptions {
|
||||
pub bind: TcpBindOptions,
|
||||
pub purpose: TcpListenPurpose,
|
||||
}
|
||||
|
||||
impl TcpListenOptions {
|
||||
pub fn direct_connect(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::DirectConnect,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hole_punch(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::HolePunch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn manual_connect(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ManualConnect,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proxy_nat(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ProxyNat,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socks5(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::Socks5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port_forward(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::PortForward,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port_lease(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::PortLease,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_bind(mut self, bind: TcpBindOptions) -> Self {
|
||||
self.bind = bind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait VirtualTcpListenerFactory: Send + Sync + 'static {
|
||||
type Listener: VirtualTcpListener;
|
||||
|
||||
async fn bind_tcp(&self, options: TcpListenOptions) -> anyhow::Result<Arc<Self::Listener>>;
|
||||
}
|
||||
|
||||
type AcceptedTcpSocket<F> =
|
||||
<<F as VirtualTcpListenerFactory>::Listener as VirtualTcpListener>::Socket;
|
||||
|
||||
pub struct TcpSocketListener<F>
|
||||
where
|
||||
F: VirtualTcpListenerFactory,
|
||||
{
|
||||
url: url::Url,
|
||||
options: TcpListenOptions,
|
||||
factory: Arc<F>,
|
||||
listener: Option<Arc<F::Listener>>,
|
||||
}
|
||||
|
||||
impl<F> TcpSocketListener<F>
|
||||
where
|
||||
F: VirtualTcpListenerFactory,
|
||||
{
|
||||
pub fn new_with_options(url: url::Url, options: TcpListenOptions, factory: Arc<F>) -> Self {
|
||||
Self {
|
||||
url,
|
||||
options,
|
||||
factory,
|
||||
listener: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn listener(&self) -> anyhow::Result<Arc<F::Listener>> {
|
||||
self.listener
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("tcp socket listener is not started"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> fmt::Debug for TcpSocketListener<F>
|
||||
where
|
||||
F: VirtualTcpListenerFactory,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TcpSocketListener")
|
||||
.field("url", &self.url)
|
||||
.field("options", &self.options)
|
||||
.field("listening", &self.listener.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F> SocketListener for TcpSocketListener<F>
|
||||
where
|
||||
F: VirtualTcpListenerFactory,
|
||||
{
|
||||
type Accepted = AcceptedTcpSocket<F>;
|
||||
|
||||
async fn listen(&mut self) -> anyhow::Result<()> {
|
||||
if self.listener.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let listener = self.factory.bind_tcp(self.options.clone()).await?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
self.url
|
||||
.set_port(Some(local_addr.port()))
|
||||
.map_err(|_| anyhow::anyhow!("failed to update tcp listener port for {}", self.url))?;
|
||||
self.listener = Some(listener);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn accept(&mut self) -> anyhow::Result<Self::Accepted> {
|
||||
loop {
|
||||
let listener = self.listener()?;
|
||||
match listener.accept().await {
|
||||
Ok((socket, _)) => return Ok(socket),
|
||||
Err(error) if is_retryable_tcp_accept_error(&error) => {
|
||||
tracing::warn!(?error, "tcp accept failed with retryable error");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(?error, "tcp accept failed");
|
||||
return Err(error.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_url(&self) -> url::Url {
|
||||
self.url.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_retryable_tcp_accept_error(error: &io::Error) -> bool {
|
||||
use io::ErrorKind::*;
|
||||
matches!(
|
||||
error.kind(),
|
||||
NotConnected | ConnectionAborted | ConnectionRefused | ConnectionReset
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
pin::Pin,
|
||||
sync::Mutex,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use tokio::io::{DuplexStream, ReadBuf};
|
||||
|
||||
use super::*;
|
||||
|
||||
struct MockTcpSocket {
|
||||
stream: DuplexStream,
|
||||
local_addr: SocketAddr,
|
||||
peer_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl MockTcpSocket {
|
||||
fn new(local_addr: SocketAddr, peer_addr: SocketAddr) -> Self {
|
||||
let (stream, _) = tokio::io::duplex(64);
|
||||
Self {
|
||||
stream,
|
||||
local_addr,
|
||||
peer_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for MockTcpSocket {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.stream).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for MockTcpSocket {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.stream).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.stream).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.stream).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualTcpSocket for MockTcpSocket {
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(self.local_addr)
|
||||
}
|
||||
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(self.peer_addr)
|
||||
}
|
||||
}
|
||||
|
||||
struct MockTcpListener {
|
||||
local_addr: SocketAddr,
|
||||
accepts: Mutex<VecDeque<io::Result<MockTcpSocket>>>,
|
||||
}
|
||||
|
||||
impl MockTcpListener {
|
||||
fn new(local_addr: SocketAddr, accepts: Vec<io::Result<MockTcpSocket>>) -> Self {
|
||||
Self {
|
||||
local_addr,
|
||||
accepts: Mutex::new(accepts.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VirtualTcpListener for MockTcpListener {
|
||||
type Socket = MockTcpSocket;
|
||||
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(self.local_addr)
|
||||
}
|
||||
|
||||
async fn accept(&self) -> io::Result<(Self::Socket, SocketAddr)> {
|
||||
let result = self.accepts.lock().unwrap().pop_front();
|
||||
match result {
|
||||
Some(Ok(socket)) => {
|
||||
let peer_addr = socket.peer_addr()?;
|
||||
Ok((socket, peer_addr))
|
||||
}
|
||||
Some(Err(error)) => Err(error),
|
||||
None => std::future::pending().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MockTcpListenerFactory {
|
||||
listener: Arc<MockTcpListener>,
|
||||
binds: Mutex<Vec<TcpListenOptions>>,
|
||||
}
|
||||
|
||||
impl MockTcpListenerFactory {
|
||||
fn new(listener: Arc<MockTcpListener>) -> Self {
|
||||
Self {
|
||||
listener,
|
||||
binds: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VirtualTcpListenerFactory for MockTcpListenerFactory {
|
||||
type Listener = MockTcpListener;
|
||||
|
||||
async fn bind_tcp(&self, options: TcpListenOptions) -> anyhow::Result<Arc<Self::Listener>> {
|
||||
self.binds.lock().unwrap().push(options);
|
||||
Ok(self.listener.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_connect_options_preserve_socket_purpose() {
|
||||
let remote_addr = SocketAddr::from(([127, 0, 0, 1], 11010));
|
||||
let local_addr = SocketAddr::from(([0, 0, 0, 0], 0));
|
||||
|
||||
assert_eq!(
|
||||
TcpConnectOptions::direct_connect(remote_addr),
|
||||
TcpConnectOptions {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default(),
|
||||
purpose: TcpSocketPurpose::DirectConnect,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::hole_punch(remote_addr, Some(local_addr)),
|
||||
TcpConnectOptions {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpSocketPurpose::HolePunch,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::manual_connect(remote_addr, Some(local_addr)),
|
||||
TcpConnectOptions {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpSocketPurpose::ManualConnect,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::proxy_nat(remote_addr),
|
||||
TcpConnectOptions {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default(),
|
||||
purpose: TcpSocketPurpose::ProxyNat,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::stun_probe(remote_addr, local_addr),
|
||||
TcpConnectOptions {
|
||||
remote_addr,
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpSocketPurpose::StunProbe,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::socks5(remote_addr).purpose,
|
||||
TcpSocketPurpose::Socks5
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::port_forward(remote_addr).purpose,
|
||||
TcpSocketPurpose::PortForward
|
||||
);
|
||||
assert_eq!(
|
||||
TcpConnectOptions::data_plane(remote_addr).purpose,
|
||||
TcpSocketPurpose::DataPlane
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_listen_options_preserve_socket_purpose() {
|
||||
let local_addr = SocketAddr::from(([0, 0, 0, 0], 11010));
|
||||
|
||||
assert_eq!(
|
||||
TcpListenOptions::socks5(local_addr).purpose,
|
||||
TcpListenPurpose::Socks5
|
||||
);
|
||||
assert_eq!(
|
||||
TcpListenOptions::port_forward(local_addr).purpose,
|
||||
TcpListenPurpose::PortForward
|
||||
);
|
||||
assert_eq!(
|
||||
TcpListenOptions::port_lease(local_addr).purpose,
|
||||
TcpListenPurpose::PortLease
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TcpListenOptions::direct_connect(local_addr),
|
||||
TcpListenOptions {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::DirectConnect,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpListenOptions::hole_punch(local_addr),
|
||||
TcpListenOptions {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::HolePunch,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpListenOptions::manual_connect(local_addr),
|
||||
TcpListenOptions {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ManualConnect,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
TcpListenOptions::proxy_nat(local_addr),
|
||||
TcpListenOptions {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ProxyNat,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_bind_options_preserve_socket_configuration() {
|
||||
let local_addr = SocketAddr::from(([0, 0, 0, 0], 0));
|
||||
let options = TcpBindOptions::default()
|
||||
.with_local_addr(Some(local_addr))
|
||||
.with_socket_mark(Some(7))
|
||||
.with_bind_device(Some("eth0".to_owned()))
|
||||
.with_reuse_addr(true)
|
||||
.with_reuse_port(true)
|
||||
.with_only_v6(true);
|
||||
|
||||
assert_eq!(
|
||||
options,
|
||||
TcpBindOptions {
|
||||
context: SocketContext::default().with_socket_mark(Some(7)),
|
||||
local_addr: Some(local_addr),
|
||||
bind_device: Some("eth0".to_owned()),
|
||||
reuse_addr: Some(true),
|
||||
reuse_port: true,
|
||||
only_v6: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_bind_default_delegates_reuse_addr_policy_to_host() {
|
||||
assert_eq!(TcpBindOptions::default().reuse_addr, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tcp_socket_listener_binds_and_accepts_socket() {
|
||||
let requested_addr = SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let bound_addr = SocketAddr::from(([127, 0, 0, 1], 12000));
|
||||
let peer_addr = SocketAddr::from(([127, 0, 0, 1], 12001));
|
||||
let options = TcpListenOptions::direct_connect(requested_addr);
|
||||
let listener = Arc::new(MockTcpListener::new(
|
||||
bound_addr,
|
||||
vec![Ok(MockTcpSocket::new(bound_addr, peer_addr))],
|
||||
));
|
||||
let factory = Arc::new(MockTcpListenerFactory::new(listener));
|
||||
let mut socket_listener = TcpSocketListener::new_with_options(
|
||||
"tcp://127.0.0.1:0".parse().unwrap(),
|
||||
options.clone(),
|
||||
factory.clone(),
|
||||
);
|
||||
|
||||
socket_listener.listen().await.unwrap();
|
||||
let accepted = socket_listener.accept().await.unwrap();
|
||||
|
||||
assert_eq!(socket_listener.local_url().port(), Some(bound_addr.port()));
|
||||
assert_eq!(accepted.peer_addr().unwrap(), peer_addr);
|
||||
assert_eq!(factory.binds.lock().unwrap().as_slice(), &[options]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tcp_socket_listener_retries_retryable_accept_error() {
|
||||
let requested_addr = SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let bound_addr = SocketAddr::from(([127, 0, 0, 1], 12010));
|
||||
let peer_addr = SocketAddr::from(([127, 0, 0, 1], 12011));
|
||||
let listener = Arc::new(MockTcpListener::new(
|
||||
bound_addr,
|
||||
vec![
|
||||
Err(io::Error::new(io::ErrorKind::ConnectionReset, "reset")),
|
||||
Ok(MockTcpSocket::new(bound_addr, peer_addr)),
|
||||
],
|
||||
));
|
||||
let factory = Arc::new(MockTcpListenerFactory::new(listener));
|
||||
let mut socket_listener = TcpSocketListener::new_with_options(
|
||||
"tcp://127.0.0.1:0".parse().unwrap(),
|
||||
TcpListenOptions::direct_connect(requested_addr),
|
||||
factory,
|
||||
);
|
||||
|
||||
socket_listener.listen().await.unwrap();
|
||||
let accepted = socket_listener.accept().await.unwrap();
|
||||
|
||||
assert_eq!(accepted.peer_addr().unwrap(), peer_addr);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
||||
use std::{
|
||||
fmt, io,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex as StdMutex, Weak},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::socket::{ListenerConnectionCounter, SocketListener};
|
||||
|
||||
use super::{
|
||||
UdpBindOptions, UdpSession, UdpSessionLayer, UdpSessionListenRequest, UdpSessionProtocol,
|
||||
UdpSessionStunResponder, VirtualUdpSocket, VirtualUdpSocketFactory,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum UdpSessionAcceptKind {
|
||||
EasyTierMux,
|
||||
Classified(UdpSessionProtocol),
|
||||
}
|
||||
|
||||
pub async fn accept_udp_session<S, R>(
|
||||
layer: &Arc<UdpSessionLayer<S, R>>,
|
||||
accept_kind: UdpSessionAcceptKind,
|
||||
) -> io::Result<UdpSession>
|
||||
where
|
||||
S: VirtualUdpSocket,
|
||||
R: UdpSessionStunResponder<S>,
|
||||
{
|
||||
match accept_kind {
|
||||
UdpSessionAcceptKind::EasyTierMux => layer.accept().await,
|
||||
UdpSessionAcceptKind::Classified(protocol) => {
|
||||
layer.accept_classified_session(protocol).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Layer<F> = UdpSessionLayer<<F as VirtualUdpSocketFactory>::Socket, F>;
|
||||
|
||||
pub struct UdpSessionSocketListener<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
url: Url,
|
||||
request: UdpSessionListenRequest,
|
||||
accept_kind: UdpSessionAcceptKind,
|
||||
factory: Arc<F>,
|
||||
socket: Option<Arc<F::Socket>>,
|
||||
layer: Option<Arc<Layer<F>>>,
|
||||
layer_ref: Arc<StdMutex<Option<Weak<Layer<F>>>>>,
|
||||
}
|
||||
|
||||
impl<F> UdpSessionSocketListener<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
pub fn new(url: Url, local_addr: SocketAddr, factory: Arc<F>) -> Self {
|
||||
let request = UdpSessionListenRequest::new(
|
||||
UdpBindOptions::port_bound_listener(local_addr).with_only_v6(true),
|
||||
);
|
||||
Self::new_with_request(url, request, UdpSessionAcceptKind::EasyTierMux, factory)
|
||||
}
|
||||
|
||||
pub fn new_with_request(
|
||||
url: Url,
|
||||
request: UdpSessionListenRequest,
|
||||
accept_kind: UdpSessionAcceptKind,
|
||||
factory: Arc<F>,
|
||||
) -> Self {
|
||||
Self {
|
||||
url,
|
||||
request,
|
||||
accept_kind,
|
||||
factory,
|
||||
socket: None,
|
||||
layer: None,
|
||||
layer_ref: Arc::new(StdMutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn layer(&self) -> anyhow::Result<Arc<Layer<F>>> {
|
||||
self.layer
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("udp session listener is not started"))
|
||||
}
|
||||
|
||||
pub async fn accept_session(&self) -> anyhow::Result<UdpSession> {
|
||||
let layer = self.layer()?;
|
||||
let mut session = accept_udp_session(&layer, self.accept_kind).await?;
|
||||
session.keep_layer_alive(layer);
|
||||
Ok(session)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> fmt::Debug for UdpSessionSocketListener<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("UdpSessionSocketListener")
|
||||
.field("url", &self.url)
|
||||
.field("request", &self.request)
|
||||
.field("accept_kind", &self.accept_kind)
|
||||
.field("listening", &self.socket.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F> SocketListener for UdpSessionSocketListener<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
type Accepted = UdpSession;
|
||||
|
||||
async fn listen(&mut self) -> anyhow::Result<()> {
|
||||
if self.layer.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let socket = self.factory.bind_udp(self.request.bind.clone()).await?;
|
||||
let local_addr = socket.local_addr()?;
|
||||
self.url
|
||||
.set_port(Some(local_addr.port()))
|
||||
.map_err(|_| anyhow::anyhow!("failed to update udp listener port for {}", self.url))?;
|
||||
|
||||
let layer = Arc::new(UdpSessionLayer::new_with_stun_responder(
|
||||
socket.clone(),
|
||||
self.factory.clone(),
|
||||
));
|
||||
if let UdpSessionAcceptKind::Classified(protocol) = self.accept_kind {
|
||||
layer.enable_classified_accept(protocol)?;
|
||||
}
|
||||
|
||||
*self.layer_ref.lock().unwrap() = Some(Arc::downgrade(&layer));
|
||||
self.socket = Some(socket);
|
||||
self.layer = Some(layer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn accept(&mut self) -> anyhow::Result<Self::Accepted> {
|
||||
self.accept_session().await
|
||||
}
|
||||
|
||||
fn local_url(&self) -> Url {
|
||||
self.url.clone()
|
||||
}
|
||||
|
||||
fn connection_counter(&self) -> Arc<dyn ListenerConnectionCounter> {
|
||||
Arc::new(UdpSessionConnectionCounter {
|
||||
layer: self.layer_ref.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct UdpSessionConnectionCounter<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
layer: Arc<StdMutex<Option<Weak<Layer<F>>>>>,
|
||||
}
|
||||
|
||||
impl<F> fmt::Debug for UdpSessionConnectionCounter<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("UdpSessionConnectionCounter")
|
||||
.field("active", &self.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ListenerConnectionCounter for UdpSessionConnectionCounter<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
fn get(&self) -> Option<u32> {
|
||||
let layer = self.layer.lock().unwrap();
|
||||
let Some(layer) = layer.as_ref().and_then(Weak::upgrade) else {
|
||||
return Some(0);
|
||||
};
|
||||
let active = layer.active_session_count() + layer.active_classified_session_count();
|
||||
Some(active as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
mod test_utils {
|
||||
use super::*;
|
||||
|
||||
impl<F> UdpSessionSocketListener<F>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
#[doc(hidden)]
|
||||
pub fn bound_socket(&self) -> anyhow::Result<Arc<F::Socket>> {
|
||||
self.socket
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("udp session listener is not started"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
mod layer;
|
||||
mod listener;
|
||||
mod packet;
|
||||
mod session;
|
||||
mod virtual_socket;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const UDP_SESSION_RESEND_INTERVAL: std::time::Duration = std::time::Duration::from_millis(200);
|
||||
const UDP_SESSION_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
const UDP_SESSION_QUEUE_CAPACITY: usize = 128;
|
||||
|
||||
pub use layer::{UdpSessionDialer, UdpSessionLayer};
|
||||
pub use listener::{UdpSessionAcceptKind, UdpSessionSocketListener, accept_udp_session};
|
||||
pub use packet::{
|
||||
UdpSessionPacketError, extract_dst_addr_from_v4_hole_punch_packet,
|
||||
extract_v6_hole_punch_packet, is_stun_packet, new_sack_packet, new_syn_packet,
|
||||
new_v4_hole_punch_packet, new_v6_hole_punch_packet, parse_quic_initial_dcid,
|
||||
parse_udp_session_datagram,
|
||||
};
|
||||
pub use session::{
|
||||
UdpSession, UdpSessionConnectError, UdpSessionConnectRequest, UdpSessionConnector,
|
||||
UdpSessionKind, UdpSessionLayerControl, UdpSessionListenRequest, UdpSessionListener,
|
||||
UdpSessionProtocol, UdpSessionRecvMeta, UdpSessionSocket,
|
||||
};
|
||||
pub(crate) use session::{
|
||||
UdpSessionCleanup, UdpSessionCodec, UdpSessionDatagram, UdpSessionOutbound,
|
||||
UdpSessionTunnelParts,
|
||||
};
|
||||
pub use virtual_socket::{
|
||||
NoopUdpSessionStunResponder, PreferredIpv6Source, UdpBindOptions, UdpSessionStunResponder,
|
||||
UdpSocketPurpose, UdpSocketRecvMeta, UdpSocketSendMeta, VirtualUdpSocket,
|
||||
VirtualUdpSocketFactory, send_v4_hole_punch_control_packet, send_v6_hole_punch_control_packet,
|
||||
};
|
||||
@@ -0,0 +1,407 @@
|
||||
use std::{
|
||||
io,
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
use crate::packet::{
|
||||
UDP_TUNNEL_HEADER_SIZE, UDPTunnelHeader, UdpPacketType, V4HolePunchPacket, V6HolePunchPacket,
|
||||
ZCPacket, ZCPacketType,
|
||||
};
|
||||
|
||||
use super::{session::UdpSessionProtocol, virtual_socket::PreferredIpv6Source};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UdpSessionPacketError {
|
||||
#[error("udp packet size too small: {datagram_size:?}, packet: {packet:?}")]
|
||||
TooSmall {
|
||||
datagram_size: usize,
|
||||
packet: BytesMut,
|
||||
},
|
||||
#[error(
|
||||
"udp packet payload len not match: header len: {header_len:?}, real len: {datagram_size:?}"
|
||||
)]
|
||||
PayloadLenMismatch {
|
||||
header_len: usize,
|
||||
datagram_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) fn new_udp_packet<F>(f: F, udp_body: &[u8]) -> ZCPacket
|
||||
where
|
||||
F: FnOnce(&mut UDPTunnelHeader),
|
||||
{
|
||||
let mut buf = BytesMut::new();
|
||||
buf.resize(UDP_TUNNEL_HEADER_SIZE + udp_body.len(), 0);
|
||||
buf[UDP_TUNNEL_HEADER_SIZE..].copy_from_slice(udp_body);
|
||||
|
||||
let mut ret = ZCPacket::new_from_buf(buf, ZCPacketType::UDP);
|
||||
let header = ret.mut_udp_tunnel_header().unwrap();
|
||||
f(header);
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn new_syn_packet(conn_id: u32, magic: u64) -> ZCPacket {
|
||||
new_udp_packet(
|
||||
|header| {
|
||||
header.msg_type = UdpPacketType::Syn as u8;
|
||||
header.conn_id.set(conn_id);
|
||||
header.len.set(8);
|
||||
},
|
||||
&magic.to_le_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_sack_packet(conn_id: u32, magic: u64) -> ZCPacket {
|
||||
new_udp_packet(
|
||||
|header| {
|
||||
header.msg_type = UdpPacketType::Sack as u8;
|
||||
header.conn_id.set(conn_id);
|
||||
header.len.set(8);
|
||||
},
|
||||
&magic.to_le_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn new_data_packet(conn_id: u32, payload: &[u8]) -> io::Result<ZCPacket> {
|
||||
let len = udp_session_payload_len(payload)?;
|
||||
|
||||
Ok(new_udp_packet(
|
||||
|header| {
|
||||
header.msg_type = UdpPacketType::Data as u8;
|
||||
header.conn_id.set(conn_id);
|
||||
header.len.set(len);
|
||||
},
|
||||
payload,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn udp_session_payload_len(payload: &[u8]) -> io::Result<u16> {
|
||||
u16::try_from(payload.len()).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("udp session payload too large: {}", payload.len()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_v6_hole_punch_packet(
|
||||
dst: &SocketAddrV6,
|
||||
preferred_src: Option<PreferredIpv6Source>,
|
||||
) -> ZCPacket {
|
||||
let mut body = V6HolePunchPacket::default();
|
||||
body.dst_ipv6.copy_from_slice(&dst.ip().octets());
|
||||
body.dst_port.set(dst.port());
|
||||
if let Some(src) = preferred_src {
|
||||
body.preferred_src_ipv6.copy_from_slice(&src.ip.octets());
|
||||
body.preferred_src_ifindex.set(src.ifindex);
|
||||
}
|
||||
new_udp_packet(
|
||||
|header| {
|
||||
header.msg_type = UdpPacketType::V6HolePunch as u8;
|
||||
header.conn_id.set(dst.port() as u32);
|
||||
header
|
||||
.len
|
||||
.set(std::mem::size_of::<V6HolePunchPacket>() as u16);
|
||||
},
|
||||
body.as_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_v4_hole_punch_packet(dst: &SocketAddrV4) -> ZCPacket {
|
||||
let mut body = V4HolePunchPacket::default();
|
||||
body.dst_ipv4.copy_from_slice(&dst.ip().octets());
|
||||
body.dst_port.set(dst.port());
|
||||
new_udp_packet(
|
||||
|header| {
|
||||
header.msg_type = UdpPacketType::V4HolePunch as u8;
|
||||
header.conn_id.set(dst.port() as u32);
|
||||
header
|
||||
.len
|
||||
.set(std::mem::size_of::<V4HolePunchPacket>() as u16);
|
||||
},
|
||||
body.as_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_dst_addr_from_v4_hole_punch_packet(buf: &[u8]) -> Option<SocketAddrV4> {
|
||||
let body = V4HolePunchPacket::ref_from_prefix(buf)?;
|
||||
let ip = Ipv4Addr::from(body.dst_ipv4);
|
||||
Some(SocketAddrV4::new(ip, body.dst_port.get()))
|
||||
}
|
||||
|
||||
pub fn extract_v6_hole_punch_packet(
|
||||
buf: &[u8],
|
||||
) -> Option<(SocketAddrV6, Option<PreferredIpv6Source>)> {
|
||||
let body = V6HolePunchPacket::ref_from_prefix(buf)?;
|
||||
let ip = Ipv6Addr::from(body.dst_ipv6);
|
||||
let preferred_src_ipv6 = Ipv6Addr::from(body.preferred_src_ipv6);
|
||||
let preferred_src = (!preferred_src_ipv6.is_unspecified()).then_some(PreferredIpv6Source {
|
||||
ip: preferred_src_ipv6,
|
||||
ifindex: body.preferred_src_ifindex.get(),
|
||||
});
|
||||
Some((
|
||||
SocketAddrV6::new(ip, body.dst_port.get(), 0, 0),
|
||||
preferred_src,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn is_stun_packet(data: &[u8]) -> bool {
|
||||
data.len() >= UDP_TUNNEL_HEADER_SIZE
|
||||
&& data[4..8] == [0x21, 0x12, 0xA4, 0x42]
|
||||
&& data[0] & 0xC0 == 0
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum UdpDatagramClassification {
|
||||
Stun(BytesMut),
|
||||
EasyTier {
|
||||
kind: EasyTierUdpPacketKind,
|
||||
conn_id: u32,
|
||||
packet: ZCPacket,
|
||||
fallback: UdpSessionPacketKind,
|
||||
},
|
||||
SessionPacket {
|
||||
kind: UdpSessionPacketKind,
|
||||
datagram: BytesMut,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum UdpSessionPacketKind {
|
||||
Classified(UdpSessionProtocol),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum EasyTierUdpPacketKind {
|
||||
Data,
|
||||
Syn,
|
||||
Sack,
|
||||
HolePunch,
|
||||
V4HolePunch,
|
||||
V6HolePunch,
|
||||
}
|
||||
|
||||
impl EasyTierUdpPacketKind {
|
||||
fn from_msg_type(msg_type: u8) -> Option<Self> {
|
||||
match msg_type {
|
||||
msg_type if msg_type == UdpPacketType::Data as u8 => Some(Self::Data),
|
||||
msg_type if msg_type == UdpPacketType::Syn as u8 => Some(Self::Syn),
|
||||
msg_type if msg_type == UdpPacketType::Sack as u8 => Some(Self::Sack),
|
||||
msg_type if msg_type == UdpPacketType::HolePunch as u8 => Some(Self::HolePunch),
|
||||
msg_type if msg_type == UdpPacketType::V4HolePunch as u8 => Some(Self::V4HolePunch),
|
||||
msg_type if msg_type == UdpPacketType::V6HolePunch as u8 => Some(Self::V6HolePunch),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) struct EasyTierUdpDatagramInfo {
|
||||
pub(super) kind: EasyTierUdpPacketKind,
|
||||
pub(super) conn_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum EasyTierUdpDatagramInspectError {
|
||||
TooSmall {
|
||||
datagram_size: usize,
|
||||
},
|
||||
PayloadLenMismatch {
|
||||
header_len: usize,
|
||||
datagram_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
fn classify_session_udp_datagram(data: &[u8]) -> UdpSessionPacketKind {
|
||||
if is_wireguard_packet(data) {
|
||||
UdpSessionPacketKind::Classified(UdpSessionProtocol::WireGuard)
|
||||
} else if is_quic_packet(data) {
|
||||
UdpSessionPacketKind::Classified(UdpSessionProtocol::Quic)
|
||||
} else {
|
||||
UdpSessionPacketKind::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wireguard_packet(data: &[u8]) -> bool {
|
||||
if data.len() < 4 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let msg_type = u32::from_le_bytes(data[..4].try_into().unwrap());
|
||||
match msg_type {
|
||||
1 => data.len() == 148,
|
||||
2 => data.len() == 92,
|
||||
3 => data.len() == 64,
|
||||
4 => data.len() >= 32,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_quic_varint(data: &[u8]) -> Option<(u64, usize)> {
|
||||
let first = *data.first()?;
|
||||
let len = 1usize << (first >> 6);
|
||||
if data.len() < len {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut value = u64::from(first & 0x3f);
|
||||
for byte in &data[1..len] {
|
||||
value = (value << 8) | u64::from(*byte);
|
||||
}
|
||||
Some((value, len))
|
||||
}
|
||||
|
||||
pub fn parse_quic_initial_dcid(data: &[u8]) -> Option<Vec<u8>> {
|
||||
const QUIC_INITIAL_HEADER_FORM_AND_FIXED_BIT: u8 = 0xC0;
|
||||
const QUIC_LONG_PACKET_TYPE_MASK: u8 = 0x30;
|
||||
const QUIC_MIN_INITIAL_DATAGRAM_LEN: usize = 1200;
|
||||
const QUIC_MAX_CID_LEN: usize = 20;
|
||||
|
||||
let first = *data.first()?;
|
||||
if (first & QUIC_INITIAL_HEADER_FORM_AND_FIXED_BIT) != QUIC_INITIAL_HEADER_FORM_AND_FIXED_BIT
|
||||
|| (first & QUIC_LONG_PACKET_TYPE_MASK) != 0
|
||||
|| data.len() < QUIC_MIN_INITIAL_DATAGRAM_LEN
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let version = data.get(1..5)?;
|
||||
if version == [0, 0, 0, 0] {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dcid_len = usize::from(*data.get(5)?);
|
||||
if dcid_len == 0 || dcid_len > QUIC_MAX_CID_LEN {
|
||||
return None;
|
||||
}
|
||||
let dcid_start = 6;
|
||||
let dcid_end = dcid_start + dcid_len;
|
||||
let dcid = data.get(dcid_start..dcid_end)?;
|
||||
|
||||
let scid_len = usize::from(*data.get(dcid_end)?);
|
||||
if scid_len > QUIC_MAX_CID_LEN {
|
||||
return None;
|
||||
}
|
||||
let token_len_offset = dcid_end + 1 + scid_len;
|
||||
let (token_len, token_len_size) = parse_quic_varint(data.get(token_len_offset..)?)?;
|
||||
let packet_len_offset = token_len_offset + token_len_size + usize::try_from(token_len).ok()?;
|
||||
let (packet_len, packet_len_size) = parse_quic_varint(data.get(packet_len_offset..)?)?;
|
||||
let packet_offset = packet_len_offset + packet_len_size;
|
||||
if packet_len == 0
|
||||
|| data.len().saturating_sub(packet_offset) < usize::try_from(packet_len).ok()?
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(dcid.to_vec())
|
||||
}
|
||||
|
||||
fn is_quic_packet(data: &[u8]) -> bool {
|
||||
parse_quic_initial_dcid(data).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn inspect_easytier_udp_datagram(
|
||||
data: &[u8],
|
||||
) -> Result<Option<EasyTierUdpDatagramInfo>, EasyTierUdpDatagramInspectError> {
|
||||
let datagram_size = data.len();
|
||||
if datagram_size < UDP_TUNNEL_HEADER_SIZE {
|
||||
return Err(EasyTierUdpDatagramInspectError::TooSmall { datagram_size });
|
||||
}
|
||||
|
||||
let header = UDPTunnelHeader::ref_from_prefix(data).unwrap();
|
||||
let header_len = header.len.get() as usize;
|
||||
let real_len = datagram_size - UDP_TUNNEL_HEADER_SIZE;
|
||||
if header_len != real_len {
|
||||
return Err(EasyTierUdpDatagramInspectError::PayloadLenMismatch {
|
||||
header_len,
|
||||
datagram_size,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(
|
||||
EasyTierUdpPacketKind::from_msg_type(header.msg_type).map(|kind| EasyTierUdpDatagramInfo {
|
||||
kind,
|
||||
conn_id: header.conn_id.get(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn classify_udp_datagram(datagram: BytesMut) -> UdpDatagramClassification {
|
||||
if is_stun_packet(&datagram) {
|
||||
return UdpDatagramClassification::Stun(datagram);
|
||||
}
|
||||
|
||||
let fallback = classify_session_udp_datagram(&datagram);
|
||||
let easytier = match inspect_easytier_udp_datagram(&datagram) {
|
||||
Ok(Some(easytier)) => easytier,
|
||||
Ok(None) => {
|
||||
return UdpDatagramClassification::SessionPacket {
|
||||
kind: fallback,
|
||||
datagram,
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
EasyTierUdpDatagramInspectError::TooSmall { datagram_size } => {
|
||||
tracing::debug!(datagram_size, "udp session packet too small");
|
||||
}
|
||||
EasyTierUdpDatagramInspectError::PayloadLenMismatch {
|
||||
header_len,
|
||||
datagram_size,
|
||||
} => {
|
||||
tracing::debug!(
|
||||
header_len,
|
||||
datagram_size,
|
||||
"udp session packet payload len mismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
return UdpDatagramClassification::SessionPacket {
|
||||
kind: fallback,
|
||||
datagram,
|
||||
};
|
||||
}
|
||||
};
|
||||
let packet = ZCPacket::new_from_buf(datagram, ZCPacketType::UDP);
|
||||
|
||||
UdpDatagramClassification::EasyTier {
|
||||
kind: easytier.kind,
|
||||
conn_id: easytier.conn_id,
|
||||
packet,
|
||||
fallback,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_udp_session_datagram(
|
||||
buf: BytesMut,
|
||||
allow_stun: bool,
|
||||
) -> Result<ZCPacket, UdpSessionPacketError> {
|
||||
let datagram_size = buf.len();
|
||||
if datagram_size < UDP_TUNNEL_HEADER_SIZE {
|
||||
return Err(UdpSessionPacketError::TooSmall {
|
||||
datagram_size,
|
||||
packet: buf,
|
||||
});
|
||||
}
|
||||
|
||||
if allow_stun && is_stun_packet(&buf[..UDP_TUNNEL_HEADER_SIZE]) {
|
||||
return Ok(ZCPacket::new_from_buf(buf, ZCPacketType::UDP));
|
||||
}
|
||||
|
||||
let zc_packet = ZCPacket::new_from_buf(buf, ZCPacketType::UDP);
|
||||
let header = zc_packet.udp_tunnel_header().unwrap();
|
||||
let header_len = header.len.get() as usize;
|
||||
let real_len = datagram_size - UDP_TUNNEL_HEADER_SIZE;
|
||||
if header_len != real_len {
|
||||
return Err(UdpSessionPacketError::PayloadLenMismatch {
|
||||
header_len,
|
||||
datagram_size,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(zc_packet)
|
||||
}
|
||||
@@ -0,0 +1,785 @@
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
sync::{Arc, Mutex as StdMutex},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::BytesMut;
|
||||
use dashmap::DashMap;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
sync::{Mutex as TokioMutex, mpsc, oneshot, watch},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::socket::ring::{RingSocket, RingSocketReceiver, RingSocketSendError, RingSocketSender};
|
||||
|
||||
use super::{
|
||||
UDP_SESSION_QUEUE_CAPACITY,
|
||||
packet::{new_data_packet, udp_session_payload_len},
|
||||
virtual_socket::{PreferredIpv6Source, UdpBindOptions, UdpSocketRecvMeta, VirtualUdpSocket},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct UdpSessionDatagram {
|
||||
pub(crate) payload: BytesMut,
|
||||
pub(crate) dst_ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl UdpSessionDatagram {
|
||||
pub(crate) fn new(payload: BytesMut, meta: UdpSocketRecvMeta) -> Self {
|
||||
Self {
|
||||
payload,
|
||||
dst_ip: meta.dst_ip,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for UdpSessionDatagram {
|
||||
fn from(payload: BytesMut) -> Self {
|
||||
Self {
|
||||
payload,
|
||||
dst_ip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UdpSessionRecvMeta {
|
||||
pub dst_ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UdpSessionKind {
|
||||
EasyTierMux,
|
||||
WireGuard,
|
||||
Quic,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UdpSessionConnectError {
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("timeout")]
|
||||
Timeout,
|
||||
#[error("invalid packet: {0}")]
|
||||
InvalidPacket(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UdpSessionSocket: Send + Sync + 'static {
|
||||
fn kind(&self) -> UdpSessionKind;
|
||||
|
||||
fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
||||
|
||||
fn peer_addr(&self) -> std::io::Result<SocketAddr>;
|
||||
|
||||
async fn send(&self, data: &[u8]) -> std::io::Result<usize>;
|
||||
|
||||
async fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize>;
|
||||
|
||||
async fn recv_with_meta(&self, buf: &mut [u8]) -> std::io::Result<(usize, UdpSessionRecvMeta)> {
|
||||
let len = self.recv(buf).await?;
|
||||
Ok((len, UdpSessionRecvMeta::default()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum UdpSessionProtocol {
|
||||
WireGuard,
|
||||
Quic,
|
||||
}
|
||||
|
||||
impl UdpSessionProtocol {
|
||||
pub(super) fn session_kind(self) -> UdpSessionKind {
|
||||
match self {
|
||||
Self::WireGuard => UdpSessionKind::WireGuard,
|
||||
Self::Quic => UdpSessionKind::Quic,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UdpSessionConnectRequest {
|
||||
pub remote_addr: SocketAddr,
|
||||
pub bind: UdpBindOptions,
|
||||
pub protocol: UdpSessionProtocol,
|
||||
}
|
||||
|
||||
impl UdpSessionConnectRequest {
|
||||
pub fn wireguard(remote_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
remote_addr,
|
||||
bind: UdpBindOptions::direct_connect(),
|
||||
protocol: UdpSessionProtocol::WireGuard,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_bind(mut self, bind: UdpBindOptions) -> Self {
|
||||
self.bind = bind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UdpSessionListenRequest {
|
||||
pub bind: UdpBindOptions,
|
||||
}
|
||||
|
||||
impl UdpSessionListenRequest {
|
||||
pub fn new(bind: UdpBindOptions) -> Self {
|
||||
Self { bind }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UdpSessionConnector: Send {
|
||||
type Session: UdpSessionSocket;
|
||||
|
||||
async fn connect(&mut self, request: UdpSessionConnectRequest)
|
||||
-> anyhow::Result<Self::Session>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UdpSessionListener: Send {
|
||||
type Session: UdpSessionSocket;
|
||||
|
||||
async fn listen(&mut self, request: UdpSessionListenRequest) -> anyhow::Result<()>;
|
||||
|
||||
fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
||||
|
||||
async fn accept(&mut self) -> anyhow::Result<Self::Session>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(super) struct UdpSessionKey {
|
||||
pub(super) peer_addr: SocketAddr,
|
||||
pub(super) conn_id: u32,
|
||||
}
|
||||
|
||||
impl UdpSessionKey {
|
||||
pub(super) fn new(peer_addr: SocketAddr, conn_id: u32) -> Self {
|
||||
Self { peer_addr, conn_id }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) type UdpSessionRegistry = DashMap<UdpSessionKey, UdpSessionRegistryEntry>;
|
||||
pub(super) type ClassifiedUdpSessionRegistry =
|
||||
DashMap<ClassifiedUdpSessionKey, UdpSessionRegistryEntry>;
|
||||
pub(super) type ClassifiedUdpSessionAccepts =
|
||||
DashMap<UdpSessionProtocol, Arc<ClassifiedUdpSessionAccept>>;
|
||||
pub(super) type PendingUdpSessionConnects = DashMap<u32, PendingUdpSessionConnect>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(super) struct ClassifiedUdpSessionKey {
|
||||
pub(super) protocol: UdpSessionProtocol,
|
||||
pub(super) peer_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl ClassifiedUdpSessionKey {
|
||||
pub(super) fn new(protocol: UdpSessionProtocol, peer_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
protocol,
|
||||
peer_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ClassifiedUdpSessionAccept {
|
||||
pub(super) accepted: mpsc::Sender<UdpSession>,
|
||||
pub(super) accepted_rx: TokioMutex<mpsc::Receiver<UdpSession>>,
|
||||
pub(super) accept_enabled: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct UdpSessionRegistryEntry {
|
||||
pub(super) incoming: Arc<StdMutex<RingSocketSender<UdpSessionDatagram>>>,
|
||||
pub(super) close: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct PendingUdpSessionConnect {
|
||||
pub(super) expected_addr: SocketAddr,
|
||||
pub(super) magic: u64,
|
||||
pub(super) session_key: Arc<StdMutex<Option<UdpSessionKey>>>,
|
||||
pub(super) entry: UdpSessionRegistryEntry,
|
||||
pub(super) control: mpsc::Sender<UdpConnectControl>,
|
||||
pub(super) sack: watch::Sender<Option<SocketAddr>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum UdpConnectControl {
|
||||
HolePunch { recv_addr: SocketAddr },
|
||||
InvalidPacket(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UdpSessionLayerControl {
|
||||
Stun {
|
||||
remote_addr: SocketAddr,
|
||||
datagram: BytesMut,
|
||||
},
|
||||
V4HolePunch {
|
||||
remote_addr: SocketAddr,
|
||||
dst_addr: SocketAddrV4,
|
||||
},
|
||||
V6HolePunch {
|
||||
remote_addr: SocketAddr,
|
||||
dst_addr: SocketAddrV6,
|
||||
preferred_src: Option<PreferredIpv6Source>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdpSession {
|
||||
local_addr: SocketAddr,
|
||||
peer_addr: SocketAddr,
|
||||
kind: UdpSessionKind,
|
||||
codec: UdpSessionCodec,
|
||||
incoming: TokioMutex<RingSocketReceiver<UdpSessionDatagram>>,
|
||||
outgoing: TokioMutex<RingSocketSender<UdpSessionOutbound>>,
|
||||
closed: watch::Receiver<bool>,
|
||||
pub(super) _cleanup: UdpSessionCleanup,
|
||||
}
|
||||
|
||||
pub(crate) struct UdpSessionOutbound {
|
||||
pub(crate) payload: BytesMut,
|
||||
pub(crate) completion: oneshot::Sender<io::Result<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum UdpSessionCodec {
|
||||
EasyTierData { conn_id: u32 },
|
||||
Identity,
|
||||
}
|
||||
|
||||
impl UdpSessionCodec {
|
||||
pub(crate) fn validate_payload(&self, payload: &[u8]) -> io::Result<()> {
|
||||
if matches!(self, Self::EasyTierData { .. }) {
|
||||
udp_session_payload_len(payload)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(&self, payload: &[u8]) -> io::Result<BytesMut> {
|
||||
match self {
|
||||
Self::EasyTierData { conn_id } => {
|
||||
Ok(new_data_packet(*conn_id, payload)?.into_bytes().into())
|
||||
}
|
||||
Self::Identity => Ok(BytesMut::from(payload)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum UdpSessionCloseTarget {
|
||||
#[cfg(test)]
|
||||
SignalOnly,
|
||||
EasyTier {
|
||||
key: UdpSessionKey,
|
||||
sessions: Arc<UdpSessionRegistry>,
|
||||
},
|
||||
Classified {
|
||||
key: ClassifiedUdpSessionKey,
|
||||
sessions: Arc<ClassifiedUdpSessionRegistry>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct UdpSessionClose {
|
||||
close: watch::Sender<bool>,
|
||||
target: UdpSessionCloseTarget,
|
||||
}
|
||||
|
||||
impl UdpSessionClose {
|
||||
pub(super) fn easy_tier(
|
||||
key: UdpSessionKey,
|
||||
close: watch::Sender<bool>,
|
||||
sessions: Arc<UdpSessionRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
close,
|
||||
target: UdpSessionCloseTarget::EasyTier { key, sessions },
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn classified(
|
||||
key: ClassifiedUdpSessionKey,
|
||||
close: watch::Sender<bool>,
|
||||
sessions: Arc<ClassifiedUdpSessionRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
close,
|
||||
target: UdpSessionCloseTarget::Classified { key, sessions },
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
match &self.target {
|
||||
#[cfg(test)]
|
||||
UdpSessionCloseTarget::SignalOnly => {}
|
||||
UdpSessionCloseTarget::EasyTier { key, sessions } => {
|
||||
close_udp_session(sessions, *key);
|
||||
}
|
||||
UdpSessionCloseTarget::Classified { key, sessions } => {
|
||||
close_classified_udp_session(sessions, *key);
|
||||
}
|
||||
}
|
||||
let _ = self.close.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UdpSessionCleanup {
|
||||
session_close: Option<UdpSessionClose>,
|
||||
shutdown: Option<watch::Sender<bool>>,
|
||||
tasks: Vec<JoinHandle<()>>,
|
||||
pub(super) layer_guard: Option<Box<dyn Send + Sync>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for UdpSessionCleanup {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UdpSessionCleanup")
|
||||
.field("has_session_close", &self.session_close.is_some())
|
||||
.field("has_shutdown", &self.shutdown.is_some())
|
||||
.field("tasks", &self.tasks.len())
|
||||
.field("has_layer_guard", &self.layer_guard.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UdpSessionCleanup {
|
||||
fn drop(&mut self) {
|
||||
if let Some(shutdown) = &self.shutdown {
|
||||
let _ = shutdown.send(true);
|
||||
}
|
||||
if let Some(close) = &self.session_close {
|
||||
close.close();
|
||||
}
|
||||
for task in &self.tasks {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UdpSession {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn new<S>(
|
||||
socket: Arc<S>,
|
||||
local_addr: SocketAddr,
|
||||
peer_addr: SocketAddr,
|
||||
kind: UdpSessionKind,
|
||||
codec: UdpSessionCodec,
|
||||
rings: UdpSessionRingParts,
|
||||
close: UdpSessionClose,
|
||||
shutdown: watch::Receiver<bool>,
|
||||
) -> Self
|
||||
where
|
||||
S: VirtualUdpSocket,
|
||||
{
|
||||
if *shutdown.borrow() {
|
||||
let _ = rings.close_tx.send(true);
|
||||
}
|
||||
let send_task = tokio::spawn(forward_udp_session_to_socket(
|
||||
socket,
|
||||
peer_addr,
|
||||
codec,
|
||||
rings.session_send_rx,
|
||||
shutdown,
|
||||
close.clone(),
|
||||
));
|
||||
|
||||
Self {
|
||||
local_addr,
|
||||
peer_addr,
|
||||
kind,
|
||||
codec,
|
||||
incoming: TokioMutex::new(rings.session_recv_rx),
|
||||
outgoing: TokioMutex::new(rings.session_send_tx),
|
||||
closed: rings.close_rx,
|
||||
_cleanup: UdpSessionCleanup {
|
||||
session_close: Some(close),
|
||||
shutdown: None,
|
||||
tasks: vec![send_task],
|
||||
layer_guard: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn keep_layer_alive<T>(&mut self, layer_guard: T)
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self._cleanup.layer_guard = Some(Box::new(layer_guard));
|
||||
}
|
||||
|
||||
pub(crate) fn into_tunnel_parts(self) -> UdpSessionTunnelParts {
|
||||
let Self {
|
||||
local_addr,
|
||||
peer_addr,
|
||||
kind,
|
||||
codec,
|
||||
incoming,
|
||||
outgoing,
|
||||
closed,
|
||||
_cleanup,
|
||||
} = self;
|
||||
UdpSessionTunnelParts {
|
||||
local_addr,
|
||||
peer_addr,
|
||||
kind,
|
||||
codec,
|
||||
session_recv_rx: incoming.into_inner(),
|
||||
session_send_tx: outgoing.into_inner(),
|
||||
closed,
|
||||
cleanup: _cleanup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UdpSessionTunnelParts {
|
||||
pub(crate) local_addr: SocketAddr,
|
||||
pub(crate) peer_addr: SocketAddr,
|
||||
pub(crate) kind: UdpSessionKind,
|
||||
pub(crate) codec: UdpSessionCodec,
|
||||
pub(crate) session_recv_rx: RingSocketReceiver<UdpSessionDatagram>,
|
||||
pub(crate) session_send_tx: RingSocketSender<UdpSessionOutbound>,
|
||||
pub(crate) closed: watch::Receiver<bool>,
|
||||
pub(crate) cleanup: UdpSessionCleanup,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UdpSessionSocket for UdpSession {
|
||||
fn kind(&self) -> UdpSessionKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||
Ok(self.local_addr)
|
||||
}
|
||||
|
||||
fn peer_addr(&self) -> std::io::Result<SocketAddr> {
|
||||
Ok(self.peer_addr)
|
||||
}
|
||||
|
||||
async fn send(&self, data: &[u8]) -> std::io::Result<usize> {
|
||||
self.codec.validate_payload(data)?;
|
||||
let mut closed = self.closed.clone();
|
||||
if *closed.borrow() {
|
||||
return Err(udp_session_closed_error());
|
||||
}
|
||||
let (completion, sent) = oneshot::channel();
|
||||
let outbound = UdpSessionOutbound {
|
||||
payload: BytesMut::from(data),
|
||||
completion,
|
||||
};
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = closed.changed() => return Err(udp_session_closed_error()),
|
||||
ret = async {
|
||||
let mut outgoing = self.outgoing.lock().await;
|
||||
outgoing.send(outbound).await
|
||||
} => ret.map_err(ring_socket_error_to_io)?,
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
ret = sent => ret.map_err(|_| udp_session_closed_error())?,
|
||||
_ = closed.changed() => Err(udp_session_closed_error()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.recv_with_meta(buf).await.map(|(len, _meta)| len)
|
||||
}
|
||||
|
||||
async fn recv_with_meta(&self, buf: &mut [u8]) -> std::io::Result<(usize, UdpSessionRecvMeta)> {
|
||||
let mut closed = self.closed.clone();
|
||||
if *closed.borrow() {
|
||||
return Err(udp_session_closed_error());
|
||||
}
|
||||
let mut incoming = self.incoming.lock().await;
|
||||
let payload = tokio::select! {
|
||||
biased;
|
||||
_ = closed.changed() => return Err(udp_session_closed_error()),
|
||||
payload = incoming.next() => payload
|
||||
.ok_or_else(udp_session_closed_error)?
|
||||
.map_err(ring_socket_error_to_io)?,
|
||||
};
|
||||
let len = payload.payload.len().min(buf.len());
|
||||
buf[..len].copy_from_slice(&payload.payload[..len]);
|
||||
Ok((
|
||||
len,
|
||||
UdpSessionRecvMeta {
|
||||
dst_ip: payload.dst_ip,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct UdpSessionRingParts {
|
||||
pub(super) session_recv_rx: RingSocketReceiver<UdpSessionDatagram>,
|
||||
pub(super) session_recv_tx: Arc<StdMutex<RingSocketSender<UdpSessionDatagram>>>,
|
||||
pub(super) session_send_tx: RingSocketSender<UdpSessionOutbound>,
|
||||
pub(super) session_send_rx: RingSocketReceiver<UdpSessionOutbound>,
|
||||
pub(super) close_tx: watch::Sender<bool>,
|
||||
pub(super) close_rx: watch::Receiver<bool>,
|
||||
}
|
||||
|
||||
pub(super) fn create_udp_session_rings() -> UdpSessionRingParts {
|
||||
let (session_recv_rx_socket, session_recv_tx_socket) =
|
||||
RingSocket::pair(UDP_SESSION_QUEUE_CAPACITY);
|
||||
let (session_send_rx_socket, session_send_tx_socket) =
|
||||
RingSocket::pair(UDP_SESSION_QUEUE_CAPACITY);
|
||||
let (session_recv_rx, _unused_session_recv_tx) = session_recv_rx_socket.split();
|
||||
let (_unused_session_recv_peer_rx, session_recv_tx) = session_recv_tx_socket.split();
|
||||
let (session_send_rx, _unused_session_send_peer_tx) = session_send_rx_socket.split();
|
||||
let (_unused_session_send_rx, session_send_tx) = session_send_tx_socket.split();
|
||||
let (close_tx, close_rx) = watch::channel(false);
|
||||
UdpSessionRingParts {
|
||||
session_recv_rx,
|
||||
session_recv_tx: Arc::new(StdMutex::new(session_recv_tx)),
|
||||
session_send_tx,
|
||||
session_send_rx,
|
||||
close_tx,
|
||||
close_rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn udp_session_registry_entry(rings: &UdpSessionRingParts) -> UdpSessionRegistryEntry {
|
||||
UdpSessionRegistryEntry {
|
||||
incoming: rings.session_recv_tx.clone(),
|
||||
close: rings.close_tx.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn close_udp_session(sessions: &UdpSessionRegistry, key: UdpSessionKey) {
|
||||
if let Some((_, entry)) = sessions.remove(&key) {
|
||||
let _ = entry.close.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn close_classified_udp_session(
|
||||
classified_sessions: &ClassifiedUdpSessionRegistry,
|
||||
key: ClassifiedUdpSessionKey,
|
||||
) {
|
||||
if let Some((_, entry)) = classified_sessions.remove(&key) {
|
||||
let _ = entry.close.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn close_all_udp_sessions(sessions: &UdpSessionRegistry) {
|
||||
let close_senders = sessions
|
||||
.iter()
|
||||
.map(|entry| entry.value().close.clone())
|
||||
.collect::<Vec<_>>();
|
||||
for close in close_senders {
|
||||
let _ = close.send(true);
|
||||
}
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
pub(super) fn close_all_classified_udp_sessions(
|
||||
classified_sessions: &ClassifiedUdpSessionRegistry,
|
||||
) {
|
||||
let close_senders = classified_sessions
|
||||
.iter()
|
||||
.map(|entry| entry.value().close.clone())
|
||||
.collect::<Vec<_>>();
|
||||
for close in close_senders {
|
||||
let _ = close.send(true);
|
||||
}
|
||||
classified_sessions.clear();
|
||||
}
|
||||
|
||||
fn ring_socket_error_to_io(error: crate::socket::ring::RingSocketError) -> io::Error {
|
||||
let kind = match error {
|
||||
crate::socket::ring::RingSocketError::Closed => io::ErrorKind::UnexpectedEof,
|
||||
crate::socket::ring::RingSocketError::Full => io::ErrorKind::WouldBlock,
|
||||
crate::socket::ring::RingSocketError::AlreadySplit => io::ErrorKind::Other,
|
||||
};
|
||||
io::Error::new(kind, error.to_string())
|
||||
}
|
||||
|
||||
fn udp_session_closed_error() -> io::Error {
|
||||
io::Error::new(io::ErrorKind::UnexpectedEof, "udp session closed")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) enum UdpSessionEnqueuePolicy {
|
||||
Lossy,
|
||||
Reliable,
|
||||
}
|
||||
|
||||
async fn forward_udp_session_to_socket<S>(
|
||||
socket: Arc<S>,
|
||||
peer_addr: SocketAddr,
|
||||
codec: UdpSessionCodec,
|
||||
mut outgoing: RingSocketReceiver<UdpSessionOutbound>,
|
||||
mut shutdown: watch::Receiver<bool>,
|
||||
close: UdpSessionClose,
|
||||
) where
|
||||
S: VirtualUdpSocket,
|
||||
{
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.changed() => {
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
outbound = outgoing.next() => {
|
||||
let Some(outbound) = outbound else {
|
||||
break;
|
||||
};
|
||||
let outbound = match outbound {
|
||||
Ok(outbound) => outbound,
|
||||
Err(err) => {
|
||||
tracing::debug!(?err, ?peer_addr, "udp session outgoing ring closed");
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
let payload_len = outbound.payload.len();
|
||||
let datagram = match codec.encode(&outbound.payload) {
|
||||
Ok(datagram) => datagram,
|
||||
Err(err) => {
|
||||
tracing::debug!(?err, ?peer_addr, ?codec, "udp session datagram encode error");
|
||||
let _ = outbound.completion.send(Err(err));
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
match socket.send_to(&datagram, peer_addr).await {
|
||||
Ok(_) => {
|
||||
let _ = outbound.completion.send(Ok(payload_len));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!(?err, ?peer_addr, "udp session send error");
|
||||
let _ = outbound.completion.send(Err(err));
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_payload_to_session(
|
||||
incoming: &Arc<StdMutex<RingSocketSender<UdpSessionDatagram>>>,
|
||||
payload: impl Into<UdpSessionDatagram>,
|
||||
policy: UdpSessionEnqueuePolicy,
|
||||
) -> bool {
|
||||
let payload = payload.into();
|
||||
let result = {
|
||||
let mut incoming = incoming.lock().unwrap();
|
||||
match policy {
|
||||
UdpSessionEnqueuePolicy::Lossy => incoming.try_send(payload),
|
||||
UdpSessionEnqueuePolicy::Reliable => incoming.force_send(payload),
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Ok(()) => true,
|
||||
Err(RingSocketSendError::Full(_)) => {
|
||||
tracing::trace!(?policy, "udp session data queue full");
|
||||
true
|
||||
}
|
||||
Err(RingSocketSendError::Closed(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl UdpSessionClose {
|
||||
fn signal_only(close: watch::Sender<bool>) -> Self {
|
||||
Self {
|
||||
close,
|
||||
target: UdpSessionCloseTarget::SignalOnly,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UdpSession {
|
||||
pub(crate) fn identity_standalone<S>(
|
||||
socket: Arc<S>,
|
||||
peer_addr: SocketAddr,
|
||||
kind: UdpSessionKind,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
S: VirtualUdpSocket,
|
||||
{
|
||||
let local_addr = socket.local_addr()?;
|
||||
let rings = create_udp_session_rings();
|
||||
let (shutdown_tx, _) = watch::channel(false);
|
||||
let close = UdpSessionClose::signal_only(rings.close_tx.clone());
|
||||
let recv_socket = socket.clone();
|
||||
let recv_task = tokio::spawn(forward_identity_socket_to_udp_session(
|
||||
recv_socket,
|
||||
peer_addr,
|
||||
rings.session_recv_tx.clone(),
|
||||
shutdown_tx.subscribe(),
|
||||
close.clone(),
|
||||
));
|
||||
let mut session = Self::new(
|
||||
socket.clone(),
|
||||
local_addr,
|
||||
peer_addr,
|
||||
kind,
|
||||
UdpSessionCodec::Identity,
|
||||
rings,
|
||||
close,
|
||||
shutdown_tx.subscribe(),
|
||||
);
|
||||
session._cleanup.shutdown = Some(shutdown_tx);
|
||||
session._cleanup.tasks.push(recv_task);
|
||||
Ok(session)
|
||||
}
|
||||
}
|
||||
|
||||
async fn forward_identity_socket_to_udp_session<S>(
|
||||
socket: Arc<S>,
|
||||
peer_addr: SocketAddr,
|
||||
incoming: Arc<StdMutex<RingSocketSender<UdpSessionDatagram>>>,
|
||||
mut shutdown: watch::Receiver<bool>,
|
||||
close: UdpSessionClose,
|
||||
) where
|
||||
S: VirtualUdpSocket,
|
||||
{
|
||||
let mut buf = [0u8; 65535];
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.changed() => {
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
ret = socket.recv_from(&mut buf) => {
|
||||
let (len, remote_addr) = match ret {
|
||||
Ok(ret) => ret,
|
||||
Err(err) => {
|
||||
tracing::debug!(?err, ?peer_addr, "identity udp session recv error");
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
if remote_addr != peer_addr {
|
||||
continue;
|
||||
}
|
||||
if !dispatch_payload_to_session(
|
||||
&incoming,
|
||||
BytesMut::from(&buf[..len]),
|
||||
UdpSessionEnqueuePolicy::Reliable,
|
||||
) {
|
||||
close.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,265 @@
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::socket::{IpVersion, SocketContext};
|
||||
|
||||
use super::packet::{new_v4_hole_punch_packet, new_v6_hole_punch_packet};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UdpSocketRecvMeta {
|
||||
pub dst_ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UdpSocketSendMeta {
|
||||
pub src_ip: Option<IpAddr>,
|
||||
pub src_ifindex: Option<u32>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait VirtualUdpSocket: Send + Sync + 'static {
|
||||
fn local_addr(&self) -> std::io::Result<SocketAddr>;
|
||||
|
||||
fn socket_context(&self) -> SocketContext {
|
||||
SocketContext::default()
|
||||
}
|
||||
|
||||
async fn send_to(&self, data: &[u8], addr: SocketAddr) -> std::io::Result<usize>;
|
||||
|
||||
async fn recv_from(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)>;
|
||||
|
||||
async fn send_to_with_meta(
|
||||
&self,
|
||||
data: &[u8],
|
||||
addr: SocketAddr,
|
||||
meta: UdpSocketSendMeta,
|
||||
) -> std::io::Result<usize> {
|
||||
let _ = meta;
|
||||
self.send_to(data, addr).await
|
||||
}
|
||||
|
||||
async fn recv_from_with_meta(
|
||||
&self,
|
||||
buf: &mut [u8],
|
||||
) -> std::io::Result<(usize, SocketAddr, UdpSocketRecvMeta)> {
|
||||
let (len, addr) = self.recv_from(buf).await?;
|
||||
Ok((len, addr, UdpSocketRecvMeta::default()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UdpSessionStunResponder<S>: Send + Sync + 'static
|
||||
where
|
||||
S: VirtualUdpSocket,
|
||||
{
|
||||
async fn respond_stun(
|
||||
&self,
|
||||
_socket: Arc<S>,
|
||||
_datagram: &[u8],
|
||||
_remote_addr: SocketAddr,
|
||||
) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NoopUdpSessionStunResponder;
|
||||
|
||||
#[async_trait]
|
||||
impl<S> UdpSessionStunResponder<S> for NoopUdpSessionStunResponder where S: VirtualUdpSocket {}
|
||||
|
||||
pub async fn send_v4_hole_punch_control_packet<F>(
|
||||
factory: &F,
|
||||
context: SocketContext,
|
||||
listener_port: u16,
|
||||
dst_addr: SocketAddrV4,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
let socket = factory
|
||||
.bind_udp(
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_context(context.with_ip_version(IpVersion::V4))
|
||||
.with_local_addr(Some(SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
0,
|
||||
)))),
|
||||
)
|
||||
.await?;
|
||||
let packet = new_v4_hole_punch_packet(&dst_addr).into_bytes();
|
||||
let listener_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, listener_port));
|
||||
socket.send_to(&packet, listener_addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_v6_hole_punch_control_packet<F>(
|
||||
factory: &F,
|
||||
context: SocketContext,
|
||||
listener_port: u16,
|
||||
dst_addr: SocketAddrV6,
|
||||
preferred_src: Option<PreferredIpv6Source>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
F: VirtualUdpSocketFactory,
|
||||
{
|
||||
let socket = factory
|
||||
.bind_udp(
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_context(context.with_ip_version(IpVersion::V6))
|
||||
.with_local_addr(Some(SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::LOCALHOST,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)))),
|
||||
)
|
||||
.await?;
|
||||
let packet = new_v6_hole_punch_packet(&dst_addr, preferred_src).into_bytes();
|
||||
let listener_addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, listener_port, 0, 0));
|
||||
socket.send_to(&packet, listener_addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum UdpSocketPurpose {
|
||||
HolePunchControl,
|
||||
HolePunchCandidate,
|
||||
DirectConnect,
|
||||
PortBoundListener,
|
||||
ProxyNat,
|
||||
StunProbe,
|
||||
Socks5,
|
||||
PortForward,
|
||||
PortLease,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UdpBindOptions {
|
||||
#[serde(default)]
|
||||
pub context: SocketContext,
|
||||
pub local_addr: Option<SocketAddr>,
|
||||
pub bind_device: Option<String>,
|
||||
pub reuse_addr: bool,
|
||||
pub reuse_port: bool,
|
||||
pub only_v6: bool,
|
||||
pub purpose: UdpSocketPurpose,
|
||||
}
|
||||
|
||||
impl UdpBindOptions {
|
||||
fn for_purpose(purpose: UdpSocketPurpose) -> Self {
|
||||
Self {
|
||||
context: SocketContext::default(),
|
||||
local_addr: None,
|
||||
bind_device: None,
|
||||
reuse_addr: false,
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hole_punch_control() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::HolePunchControl)
|
||||
}
|
||||
|
||||
pub fn hole_punch_candidate() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::HolePunchCandidate)
|
||||
}
|
||||
|
||||
pub fn direct_connect() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::DirectConnect)
|
||||
}
|
||||
|
||||
pub fn port_bound_listener(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
local_addr: Some(local_addr),
|
||||
..Self::for_purpose(UdpSocketPurpose::PortBoundListener)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proxy_nat() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::ProxyNat)
|
||||
}
|
||||
|
||||
pub fn stun_probe() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::StunProbe)
|
||||
}
|
||||
|
||||
pub fn socks5() -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::Socks5)
|
||||
}
|
||||
|
||||
pub fn port_forward(local_addr: SocketAddr) -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::PortForward).with_local_addr(Some(local_addr))
|
||||
}
|
||||
|
||||
pub fn port_lease(local_addr: SocketAddr) -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::PortLease).with_local_addr(Some(local_addr))
|
||||
}
|
||||
|
||||
pub fn with_local_addr(mut self, local_addr: Option<SocketAddr>) -> Self {
|
||||
self.local_addr = local_addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_socket_mark(mut self, socket_mark: Option<u32>) -> Self {
|
||||
self.context.socket_mark = socket_mark;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_context(mut self, context: SocketContext) -> Self {
|
||||
self.context = context;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ip_version(mut self, ip_version: IpVersion) -> Self {
|
||||
self.context.ip_version = ip_version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bind_device(mut self, bind_device: Option<String>) -> Self {
|
||||
self.bind_device = bind_device;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reuse_addr(mut self, reuse_addr: bool) -> Self {
|
||||
self.reuse_addr = reuse_addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reuse_port(mut self, reuse_port: bool) -> Self {
|
||||
self.reuse_port = reuse_port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_only_v6(mut self, only_v6: bool) -> Self {
|
||||
self.only_v6 = only_v6;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UdpBindOptions {
|
||||
fn default() -> Self {
|
||||
Self::hole_punch_control()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait VirtualUdpSocketFactory: Send + Sync + 'static {
|
||||
type Socket: VirtualUdpSocket;
|
||||
|
||||
async fn bind_udp(&self, options: UdpBindOptions) -> anyhow::Result<Arc<Self::Socket>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PreferredIpv6Source {
|
||||
pub ip: Ipv6Addr,
|
||||
pub ifindex: u32,
|
||||
}
|
||||
Reference in New Issue
Block a user