mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-04 10:05:42 +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:
+17
-255
@@ -1,34 +1,15 @@
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher, hash::Hasher, net::SocketAddr, pin::Pin, sync::Arc,
|
||||
};
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher, net::SocketAddr};
|
||||
|
||||
use crate::{
|
||||
common::{dns::socket_addrs, error::Error},
|
||||
proto::common::TunnelInfo,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
#[cfg(any(feature = "faketcp", feature = "websocket", feature = "wireguard"))]
|
||||
use crate::common::dns::socket_addrs;
|
||||
use crate::common::error::Error;
|
||||
use derive_more::{From, TryInto};
|
||||
use futures::{Sink, Stream};
|
||||
use socket2::Protocol;
|
||||
use std::fmt::Debug;
|
||||
#[cfg(any(feature = "faketcp", feature = "websocket", feature = "wireguard"))]
|
||||
use easytier_core::tunnel::{IpVersion, TunnelError};
|
||||
use strum::{Display, EnumString, IntoStaticStr, VariantArray};
|
||||
use tokio::time::error::Elapsed;
|
||||
|
||||
use self::packet_def::ZCPacket;
|
||||
|
||||
pub mod buf;
|
||||
pub mod common;
|
||||
pub mod filter;
|
||||
pub mod mpsc;
|
||||
pub mod packet_def;
|
||||
pub mod ring;
|
||||
pub mod stats;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
pub(crate) mod udp_src;
|
||||
|
||||
#[cfg(feature = "faketcp")]
|
||||
pub mod fake_tcp;
|
||||
pub(crate) mod protocol;
|
||||
|
||||
#[cfg(feature = "wireguard")]
|
||||
pub mod wireguard;
|
||||
@@ -39,116 +20,6 @@ pub mod quic;
|
||||
#[cfg(feature = "websocket")]
|
||||
pub mod websocket;
|
||||
|
||||
#[cfg(any(feature = "quic", feature = "websocket"))]
|
||||
pub mod insecure_tls;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TunnelError {
|
||||
#[error("io error: {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("invalid packet. msg: {0}")]
|
||||
InvalidPacket(String),
|
||||
#[error("exceed max packet size. max: {0}, input: {1}")]
|
||||
ExceedMaxPacketSize(usize, usize),
|
||||
|
||||
#[error("invalid protocol: {0}")]
|
||||
InvalidProtocol(String),
|
||||
#[error("invalid addr: {0}")]
|
||||
InvalidAddr(String),
|
||||
|
||||
#[error("internal error {0}")]
|
||||
InternalError(String),
|
||||
|
||||
#[error("conn id not match, expect: {0}, actual: {1}")]
|
||||
ConnIdNotMatch(u32, u32),
|
||||
#[error("buffer full")]
|
||||
BufferFull,
|
||||
|
||||
#[error("timeout")]
|
||||
Timeout(#[from] Elapsed),
|
||||
|
||||
#[error("anyhow error: {0}")]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
|
||||
#[error("shutdown")]
|
||||
Shutdown,
|
||||
|
||||
#[error("no dns record found")]
|
||||
NoDnsRecordFound(IpVersion),
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
#[error("websocket error: {0}")]
|
||||
WebSocketError(#[from] tokio_websockets::Error),
|
||||
|
||||
#[error("tunnel error: {0}")]
|
||||
TunError(String),
|
||||
}
|
||||
|
||||
pub type StreamT = packet_def::ZCPacket;
|
||||
pub type StreamItem = Result<StreamT, TunnelError>;
|
||||
pub type SinkItem = packet_def::ZCPacket;
|
||||
pub type SinkError = TunnelError;
|
||||
|
||||
pub trait ZCPacketStream: Stream<Item = StreamItem> + Send {}
|
||||
impl<T> ZCPacketStream for T where T: Stream<Item = StreamItem> + Send {}
|
||||
pub trait ZCPacketSink: Sink<SinkItem, Error = SinkError> + Send {}
|
||||
impl<T> ZCPacketSink for T where T: Sink<SinkItem, Error = SinkError> + Send {}
|
||||
|
||||
pub type SplitTunnel = (Pin<Box<dyn ZCPacketStream>>, Pin<Box<dyn ZCPacketSink>>);
|
||||
|
||||
#[auto_impl::auto_impl(Box, Arc)]
|
||||
pub trait Tunnel: Send {
|
||||
fn split(&self) -> SplitTunnel;
|
||||
fn info(&self) -> Option<TunnelInfo>;
|
||||
}
|
||||
|
||||
#[auto_impl::auto_impl(Arc)]
|
||||
pub trait TunnelConnCounter: 'static + Send + Sync + Debug {
|
||||
fn get(&self) -> Option<u32>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum IpVersion {
|
||||
V4,
|
||||
V6,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[auto_impl::auto_impl(Box)]
|
||||
pub trait TunnelListener: Send {
|
||||
async fn listen(&mut self) -> Result<(), TunnelError>;
|
||||
async fn accept(&mut self) -> Result<Box<dyn Tunnel>, TunnelError>;
|
||||
fn local_url(&self) -> url::Url;
|
||||
fn get_conn_counter(&self) -> Arc<Box<dyn TunnelConnCounter>> {
|
||||
#[derive(Debug)]
|
||||
struct FakeTunnelConnCounter {}
|
||||
impl TunnelConnCounter for FakeTunnelConnCounter {
|
||||
fn get(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
Arc::new(Box::new(FakeTunnelConnCounter {}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[auto_impl::auto_impl(Box, &mut)]
|
||||
pub trait TunnelConnector: Send {
|
||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError>;
|
||||
fn remote_url(&self) -> url::Url;
|
||||
fn set_bind_addrs(&mut self, _addrs: Vec<SocketAddr>) {}
|
||||
fn set_ip_version(&mut self, _ip_version: IpVersion) {}
|
||||
fn set_resolved_addr(&mut self, _addr: SocketAddr) {}
|
||||
/// Linux SO_MARK to apply to outbound sockets. `None` leaves SO_MARK
|
||||
/// untouched; `Some(mark)` applies that exact value (including `Some(0)`).
|
||||
/// Default impl is a no-op; IP-based connectors override.
|
||||
fn set_socket_mark(&mut self, _socket_mark: Option<u32>) {}
|
||||
}
|
||||
|
||||
pub fn build_url_from_socket_addr(addr: &String, scheme: &str) -> url::Url {
|
||||
if let Ok(sock_addr) = addr.parse::<SocketAddr>() {
|
||||
let url_str = format!("{}://0.0.0.0", scheme);
|
||||
@@ -162,31 +33,8 @@ pub fn build_url_from_socket_addr(addr: &String, scheme: &str) -> url::Url {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn Tunnel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Tunnel")
|
||||
.field("info", &self.info())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn TunnelConnector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TunnelConnector")
|
||||
.field("remote_url", &self.remote_url())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn TunnelListener {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TunnelListener")
|
||||
.field("local_url", &self.local_url())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[cfg(any(feature = "faketcp", feature = "websocket", feature = "wireguard"))]
|
||||
pub(crate) trait FromUrl {
|
||||
async fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError>
|
||||
where
|
||||
@@ -194,6 +42,7 @@ pub(crate) trait FromUrl {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[cfg(any(feature = "faketcp", feature = "websocket", feature = "wireguard"))]
|
||||
impl FromUrl for SocketAddr {
|
||||
async fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> {
|
||||
let addrs = socket_addrs(&url, || {
|
||||
@@ -229,15 +78,6 @@ impl FromUrl for SocketAddr {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl FromUrl for uuid::Uuid {
|
||||
async fn from_url(url: url::Url, _ip_version: IpVersion) -> Result<Self, TunnelError> {
|
||||
let o = url.host_str().unwrap();
|
||||
let o = uuid::Uuid::parse_str(o).map_err(|e| TunnelError::InvalidAddr(e.to_string()))?;
|
||||
Ok(o)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TunnelUrl {
|
||||
inner: url::Url,
|
||||
}
|
||||
@@ -284,12 +124,6 @@ pub fn generate_digest_from_str(str1: &str, str2: &str, digest: &mut [u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct IpSchemeAttributes {
|
||||
protocol: Protocol,
|
||||
port_offset: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, IntoStaticStr, VariantArray)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum IpScheme {
|
||||
@@ -308,42 +142,16 @@ pub enum IpScheme {
|
||||
}
|
||||
|
||||
impl IpScheme {
|
||||
const fn attributes(self) -> IpSchemeAttributes {
|
||||
let (protocol, port_offset) = match self {
|
||||
Self::Tcp => (Protocol::TCP, 0),
|
||||
Self::Udp => (Protocol::UDP, 0),
|
||||
#[cfg(feature = "wireguard")]
|
||||
Self::Wg => (Protocol::UDP, 1),
|
||||
#[cfg(feature = "quic")]
|
||||
Self::Quic => (Protocol::UDP, 2),
|
||||
#[cfg(feature = "websocket")]
|
||||
Self::Ws => (Protocol::TCP, 1),
|
||||
#[cfg(feature = "websocket")]
|
||||
Self::Wss => (Protocol::TCP, 2),
|
||||
#[cfg(feature = "faketcp")]
|
||||
Self::FakeTcp => (Protocol::TCP, 3),
|
||||
};
|
||||
IpSchemeAttributes {
|
||||
protocol,
|
||||
port_offset,
|
||||
}
|
||||
}
|
||||
pub const fn protocol(self) -> Protocol {
|
||||
self.attributes().protocol
|
||||
pub fn port_offset(self) -> u16 {
|
||||
let scheme: &'static str = self.into();
|
||||
easytier_core::connectivity::protocol::protocol_port_offset(scheme)
|
||||
.expect("IpScheme must have core protocol metadata")
|
||||
}
|
||||
|
||||
pub const fn port_offset(self) -> u16 {
|
||||
self.attributes().port_offset
|
||||
}
|
||||
|
||||
pub const fn default_port(self) -> u16 {
|
||||
match self {
|
||||
#[cfg(feature = "websocket")]
|
||||
Self::Ws => 80,
|
||||
#[cfg(feature = "websocket")]
|
||||
Self::Wss => 443,
|
||||
_ => 11010 + self.port_offset(),
|
||||
}
|
||||
pub fn default_port(self) -> u16 {
|
||||
let scheme: &'static str = self.into();
|
||||
easytier_core::connectivity::protocol::protocol_default_port(scheme)
|
||||
.expect("IpScheme must have core protocol metadata")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,49 +184,3 @@ impl TryFrom<&url::Url> for TunnelScheme {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_scheme_by_url(l: &url::Url) -> Result<TunnelScheme, Error> {
|
||||
l.try_into()
|
||||
}
|
||||
|
||||
macro_rules! __matches_scheme__ {
|
||||
($url:expr, $( $pattern:pat_param )|+ ) => {
|
||||
matches!($crate::tunnel::get_scheme_by_url(&$url), Ok($( $pattern )|+))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use __matches_scheme__ as matches_scheme;
|
||||
|
||||
pub fn get_protocol_by_url(l: &url::Url) -> Result<Protocol, Error> {
|
||||
let TunnelScheme::Ip(scheme) = l.try_into()? else {
|
||||
return Err(Error::InvalidUrl(l.to_string()));
|
||||
};
|
||||
Ok(scheme.protocol())
|
||||
}
|
||||
|
||||
macro_rules! __matches_protocol__ {
|
||||
($url:expr, $( $pattern:pat_param )|+ ) => {
|
||||
matches!($crate::tunnel::get_protocol_by_url($url), Ok($( $pattern )|+))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use __matches_protocol__ as matches_protocol;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{IpScheme, TunnelScheme, matches_scheme};
|
||||
|
||||
#[test]
|
||||
fn matches_scheme_accepts_owned_url() {
|
||||
let url: url::Url = "udp://[2001:db8::1]:11010".parse().unwrap();
|
||||
|
||||
assert!(matches_scheme!(url, TunnelScheme::Ip(IpScheme::Udp)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_scheme_accepts_borrowed_url() {
|
||||
let url: url::Url = "udp://[2001:db8::1]:11010".parse().unwrap();
|
||||
|
||||
assert!(matches_scheme!(&url, TunnelScheme::Ip(IpScheme::Udp)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user