feat(mini): add compact native EasyTier client (#2479)

Add a native EasyTier proof-of-concept binary with TCP and UDP
transports, TUN, UDP hole punching, AES-GCM, and a read-only RPC
portal.

Introduce a release-derived mini profile and musl linker policy so
x86_64, big-endian MIPS, and little-endian MIPS stay below the strict
5,000,000-byte target without UPX.
This commit is contained in:
KKRainbow
2026-08-09 19:43:30 +08:00
committed by GitHub
parent 1e40350c89
commit d375d7e455
47 changed files with 1688 additions and 302 deletions
+145 -27
View File
@@ -6,11 +6,14 @@ use std::{
use arc_swap::ArcSwap;
use async_trait::async_trait;
use easytier_core::config::PeerId;
use easytier_core::connectivity::composite::ConnectorRuntime as _;
use easytier_core::peers::public_ipv6::PublicIpv6Host;
use easytier_core::socket::{NetNamespace, SocketContext};
use easytier_core::tunnel::effective_encryption_uses_xor;
use easytier_core::{
config::{PeerId, peers::PeerRuntimeSnapshot, runtime::CoreInstanceRuntimeConfig},
instance::{CoreInstanceConfig, CoreInstanceHostConfig},
};
use super::{
config::{ConfigLoader, Flags, NetworkIdentity},
@@ -85,11 +88,13 @@ pub struct GlobalCtx {
cached_ipv4: AtomicCell<Option<cidr::Ipv4Inet>>,
cached_ipv6: AtomicCell<Option<cidr::Ipv6Inet>>,
vpn_portal_cidr: AtomicCell<Option<cidr::Ipv4Cidr>>,
hostname: Mutex<String>,
tun_device_name: Mutex<Option<String>>,
flags: ArcSwap<Flags>,
runtime_endpoint_protocols: Option<HashSet<String>>,
}
impl std::fmt::Debug for GlobalCtx {
@@ -113,7 +118,7 @@ impl PublicIpv6Host for GlobalCtx {
prefix: cidr::Ipv6Cidr,
) -> HashSet<Ipv6Addr> {
let context = SocketContext::default()
.with_socket_mark(self.config.get_flags().socket_mark)
.with_socket_mark(self.get_flags().socket_mark)
.with_netns(self.net_ns.name().map(NetNamespace::new));
let ip_list = crate::host_runtime::native_host_runtime()
.collect_ip_addrs(&context)
@@ -139,14 +144,57 @@ impl PublicIpv6Host for GlobalCtx {
impl GlobalCtx {
pub fn new(config_fs: impl ConfigLoader + 'static) -> Self {
Self::new_inner(config_fs, None, None)
}
pub(crate) fn new_with_runtime_config(
config_fs: impl ConfigLoader + 'static,
runtime: &CoreInstanceConfig,
host: &CoreInstanceHostConfig,
) -> Self {
let runtime = CoreInstanceRuntimeConfig {
services: runtime.connectivity.runtime.clone(),
peer: Arc::new(runtime.peer.snapshot.clone()),
};
let protocols = host.ignore_unsupported_config.then(|| {
host.endpoint_protocols
.iter()
.map(|protocol| protocol.to_ascii_lowercase())
.collect()
});
Self::new_inner(config_fs, Some(&runtime), protocols)
}
fn new_inner(
config_fs: impl ConfigLoader + 'static,
runtime: Option<&CoreInstanceRuntimeConfig>,
runtime_endpoint_protocols: Option<HashSet<String>>,
) -> Self {
let id = config_fs.get_id();
let network = config_fs.get_network_identity();
let net_ns = NetNS::new(config_fs.get_netns());
let hostname = match config_fs.get_hostname() {
hostname if !hostname.is_empty() => hostname,
_ => gethostname::gethostname().to_string_lossy().to_string(),
};
let flags = config_fs.get_flags();
let hostname = runtime
.and_then(|runtime| runtime.peer.runtime.core.node.hostname.clone())
.unwrap_or_else(|| match config_fs.get_hostname() {
hostname if !hostname.is_empty() => hostname,
_ => gethostname::gethostname().to_string_lossy().to_string(),
});
let flags = runtime
.map(|runtime| runtime.peer.flags.clone())
.unwrap_or_else(|| config_fs.get_flags());
let ipv4 = runtime
.map(|runtime| Self::runtime_ipv4(&runtime.peer))
.unwrap_or_else(|| config_fs.get_ipv4());
let ipv6 = runtime
.map(|runtime| Self::runtime_ipv6(&runtime.peer))
.unwrap_or_else(|| config_fs.get_ipv6());
let vpn_portal_cidr = runtime
.map(|runtime| runtime.peer.vpn_portal_cidr)
.unwrap_or_else(|| {
config_fs
.get_vpn_portal_config()
.map(|config| config.client_cidr)
});
if flags.enable_encryption && effective_encryption_uses_xor(&flags.encryption_algorithm) {
tracing::warn!("using insecure XOR because no AEAD encryption is configured");
}
@@ -160,16 +208,34 @@ impl GlobalCtx {
network,
event_bus,
cached_ipv4: AtomicCell::new(None),
cached_ipv6: AtomicCell::new(None),
cached_ipv4: AtomicCell::new(ipv4),
cached_ipv6: AtomicCell::new(ipv6),
vpn_portal_cidr: AtomicCell::new(vpn_portal_cidr),
hostname: Mutex::new(hostname),
tun_device_name: Mutex::new(None),
flags: ArcSwap::new(Arc::new(flags)),
runtime_endpoint_protocols,
}
}
pub(crate) fn runtime_ipv4(peer: &PeerRuntimeSnapshot) -> Option<cidr::Ipv4Inet> {
let prefix = peer.runtime.core.routes.ipv4.as_ref()?;
let IpAddr::V4(address) = prefix.address else {
return None;
};
cidr::Ipv4Inet::new(address, prefix.prefix_len).ok()
}
pub(crate) fn runtime_ipv6(peer: &PeerRuntimeSnapshot) -> Option<cidr::Ipv6Inet> {
let prefix = peer.runtime.core.routes.ipv6.as_ref()?;
let IpAddr::V6(address) = prefix.address else {
return None;
};
cidr::Ipv6Inet::new(address, prefix.prefix_len).ok()
}
pub fn subscribe(&self) -> EventBusSubscriber {
self.event_bus.subscribe()
}
@@ -207,31 +273,19 @@ impl GlobalCtx {
}
pub fn get_ipv4(&self) -> Option<cidr::Ipv4Inet> {
if let Some(ret) = self.cached_ipv4.load() {
return Some(ret);
}
let addr = self.config.get_ipv4();
self.cached_ipv4.store(addr);
addr
self.cached_ipv4.load()
}
pub fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>) {
self.config.set_ipv4(addr);
self.cached_ipv4.store(None);
self.cached_ipv4.store(addr);
}
pub fn get_ipv6(&self) -> Option<cidr::Ipv6Inet> {
if let Some(ret) = self.cached_ipv6.load() {
return Some(ret);
}
let addr = self.config.get_ipv6();
self.cached_ipv6.store(addr);
addr
self.cached_ipv6.load()
}
pub fn set_ipv6(&self, addr: Option<cidr::Ipv6Inet>) {
self.config.set_ipv6(addr);
self.cached_ipv6.store(None);
self.cached_ipv6.store(addr);
}
pub fn is_ip_local_ipv6(&self, ip: &std::net::Ipv6Addr) -> bool {
@@ -273,7 +327,7 @@ impl GlobalCtx {
}
pub fn get_vpn_portal_cidr(&self) -> Option<cidr::Ipv4Cidr> {
self.config.get_vpn_portal_config().map(|x| x.client_cidr)
self.vpn_portal_cidr.load()
}
pub fn get_flags(&self) -> Flags {
@@ -281,7 +335,6 @@ impl GlobalCtx {
}
pub fn set_flags(&self, flags: Flags) {
self.config.set_flags(flags.clone());
self.flags.store(Arc::new(flags));
}
@@ -300,6 +353,17 @@ impl GlobalCtx {
pub fn no_tun(&self) -> bool {
self.flags.load().no_tun
}
pub fn runtime_mapped_listeners(&self) -> Vec<url::Url> {
let listeners = self.config.get_mapped_listeners();
let Some(protocols) = &self.runtime_endpoint_protocols else {
return listeners;
};
listeners
.into_iter()
.filter(|listener| protocols.contains(&listener.scheme().to_ascii_lowercase()))
.collect()
}
}
#[cfg(test)]
@@ -377,6 +441,60 @@ pub mod tests {
assert!(!config.dump().contains("hostname"));
}
#[test]
fn active_dhcp_ipv4_survives_declarative_config_replacement() {
let config = TomlConfigLoader::default();
config.set_dhcp(true);
let global_ctx = GlobalCtx::new(config.clone());
let lease = "10.144.144.7/24".parse().unwrap();
global_ctx.set_ipv4(Some(lease));
config.set_ipv4(None);
assert_eq!(global_ctx.get_ipv4(), Some(lease));
}
#[test]
fn runtime_state_does_not_rewrite_toml_config() {
let config = TomlConfigLoader::default();
let global_ctx = GlobalCtx::new(config.clone());
let mut runtime_flags = global_ctx.get_flags();
runtime_flags.enable_exit_node = true;
global_ctx.set_ipv4(Some("10.144.144.7/24".parse().unwrap()));
global_ctx.set_ipv6(Some("fd00::7/64".parse().unwrap()));
global_ctx.set_flags(runtime_flags);
assert_eq!(config.get_ipv4(), None);
assert_eq!(config.get_ipv6(), None);
assert!(!config.get_flags().enable_exit_node);
assert_eq!(
global_ctx.get_ipv4(),
Some("10.144.144.7/24".parse().unwrap())
);
assert_eq!(global_ctx.get_ipv6(), Some("fd00::7/64".parse().unwrap()));
assert!(global_ctx.get_flags().enable_exit_node);
}
#[test]
fn compact_runtime_does_not_advertise_unsupported_mapped_listeners() {
let config = TomlConfigLoader::default();
config.set_mapped_listeners(Some(vec![
"tcp://127.0.0.1:11010".parse().unwrap(),
"quic://127.0.0.1:11011".parse().unwrap(),
]));
let host = crate::instance::config::compact_runtime_core_host_config();
let normalized =
easytier_core::instance::CoreInstanceConfig::from_toml_with_host(&config, &host)
.unwrap();
let global_ctx = GlobalCtx::new_with_runtime_config(config.clone(), &normalized, &host);
assert_eq!(config.get_mapped_listeners().len(), 2);
assert_eq!(global_ctx.runtime_mapped_listeners().len(), 1);
assert_eq!(global_ctx.runtime_mapped_listeners()[0].scheme(), "tcp");
}
pub fn get_mock_global_ctx_with_network(
network_identy: Option<NetworkIdentity>,
) -> ArcGlobalCtx {
+56 -20
View File
@@ -6,15 +6,14 @@ use easytier_core::gateway::proxy::wrapped_transport::WrappedTransportEngines;
use easytier_core::gateway::vpn_portal::VpnPortalHost;
#[cfg(test)]
use easytier_core::host::packet::{HostPacket, PacketSink};
#[cfg(feature = "management")]
#[cfg(feature = "web-client")]
use easytier_core::{
connectivity::manual::ManualTunnelConnector,
host::dns::{DnsRecordResolver, DnsResolver},
instance::CoreInstanceConfig,
};
use easytier_core::{
events::{CoreEvent, CoreEventSink},
instance::{CoreHostAdapters, CoreInstance, PacketEgressHost},
instance::{CoreHostAdapters, CoreInstance, CoreInstanceConfig, PacketEgressHost},
process_runtime::CoreProcessRuntime,
};
@@ -25,7 +24,9 @@ use crate::{
common::global_ctx::ArcGlobalCtx,
common::{config::TomlConfig, global_ctx::GlobalCtx},
host_runtime::native_host_runtime,
instance::config::{runtime_core_host_config, runtime_peer_credential_storage},
instance::config::{
compact_runtime_core_host_config, runtime_core_host_config, runtime_peer_credential_storage,
},
instance::listeners::RuntimeExternalListenerFactory,
instance::runtime_host::NativeInstanceRuntimeHost,
};
@@ -42,18 +43,30 @@ use easytier_core::gateway::proxy::wrapped_transport::WrappedTransportEngine;
pub(crate) type NativeCoreInstance = CoreInstance<NativeInstanceHost>;
pub(crate) fn compose_native_core_instance(
config: TomlConfig,
toml_config: TomlConfig,
process_runtime: Arc<CoreProcessRuntime>,
compact_runtime: bool,
) -> anyhow::Result<Arc<NativeCoreInstance>> {
let global_ctx = Arc::new(GlobalCtx::new(config.clone()));
let host_config = if compact_runtime {
compact_runtime_core_host_config()
} else {
runtime_core_host_config()
};
let normalized = CoreInstanceConfig::from_toml_with_host(&toml_config, &host_config)?;
let global_ctx = Arc::new(GlobalCtx::new_with_runtime_config(
toml_config.clone(),
&normalized,
&host_config,
));
let runtime_host = NativeInstanceRuntimeHost::new(global_ctx.clone());
let mut adapters = runtime_core_host_adapters_with_packet_egress(
let mut adapters = runtime_core_host_adapters_with_packet_egress_and_config(
global_ctx.clone(),
process_runtime,
runtime_host.clone(),
host_config,
);
adapters.instance_runtime = runtime_host;
NativeCoreInstance::from_toml(config, adapters)
NativeCoreInstance::from_toml(toml_config, adapters)
}
impl CoreEventSink for GlobalCtx {
@@ -139,13 +152,19 @@ impl CoreEventSink for GlobalCtx {
}
#[cfg(feature = "wrapped-transport")]
fn runtime_wrapped_transport_engines() -> WrappedTransportEngines {
fn runtime_wrapped_transport_engines(
config: &easytier_core::instance::CoreInstanceHostConfig,
) -> WrappedTransportEngines {
#[cfg(feature = "kcp")]
let kcp = Some(Arc::new(KcpProxyService::new()) as Arc<dyn WrappedTransportEngine>);
let kcp = config
.kcp_enabled
.then(|| Arc::new(KcpProxyService::new()) as Arc<dyn WrappedTransportEngine>);
#[cfg(not(feature = "kcp"))]
let kcp = None;
#[cfg(feature = "quic")]
let quic = Some(Arc::new(QuicProxyService::new()) as Arc<dyn WrappedTransportEngine>);
let quic = config
.quic_enabled
.then(|| Arc::new(QuicProxyService::new()) as Arc<dyn WrappedTransportEngine>);
#[cfg(not(feature = "quic"))]
let quic = None;
@@ -161,9 +180,10 @@ pub(crate) fn runtime_core_host_adapters(
let host = native_instance_host(global_ctx.clone());
let runtime_dns = native_host_runtime();
let adapters = CoreHostAdapters::new(host, runtime_dns, packet_sink, process_runtime);
configure_runtime_core_host_adapters(global_ctx, adapters)
configure_runtime_core_host_adapters(global_ctx, adapters, runtime_core_host_config())
}
#[cfg(test)]
pub(crate) fn runtime_core_host_adapters_with_packet_egress(
global_ctx: ArcGlobalCtx,
process_runtime: Arc<CoreProcessRuntime>,
@@ -173,29 +193,45 @@ pub(crate) fn runtime_core_host_adapters_with_packet_egress(
let runtime_dns = native_host_runtime();
let adapters =
CoreHostAdapters::new_with_packet_egress(host, runtime_dns, packet_egress, process_runtime);
configure_runtime_core_host_adapters(global_ctx, adapters)
configure_runtime_core_host_adapters(global_ctx, adapters, runtime_core_host_config())
}
fn runtime_core_host_adapters_with_packet_egress_and_config(
global_ctx: ArcGlobalCtx,
process_runtime: Arc<CoreProcessRuntime>,
packet_egress: Arc<dyn PacketEgressHost>,
host_config: easytier_core::instance::CoreInstanceHostConfig,
) -> CoreHostAdapters<NativeInstanceHost> {
let host = native_instance_host(global_ctx.clone());
let runtime_dns = native_host_runtime();
let adapters =
CoreHostAdapters::new_with_packet_egress(host, runtime_dns, packet_egress, process_runtime);
configure_runtime_core_host_adapters(global_ctx, adapters, host_config)
}
fn configure_runtime_core_host_adapters(
global_ctx: ArcGlobalCtx,
mut adapters: CoreHostAdapters<NativeInstanceHost>,
host_config: easytier_core::instance::CoreInstanceHostConfig,
) -> CoreHostAdapters<NativeInstanceHost> {
#[cfg(test)]
adapters.replace_stun_provider(Arc::new(crate::common::stun::MockStunInfoCollector {
udp_nat_type: crate::proto::common::NatType::Unknown,
}));
adapters.config = runtime_core_host_config();
adapters.credential_storage = runtime_peer_credential_storage(&global_ctx);
adapters.config = host_config.clone();
adapters.credential_storage = (!host_config.ignore_unsupported_config)
.then(|| runtime_peer_credential_storage(&global_ctx))
.flatten();
adapters.events = global_ctx.clone();
#[cfg(feature = "wrapped-transport")]
{
adapters.wrapped_transports = runtime_wrapped_transport_engines();
adapters.wrapped_transports = runtime_wrapped_transport_engines(&host_config);
}
adapters.protocol = Some(runtime_client_protocol_upgrader(global_ctx.clone()));
adapters.external_listener_factory = Some(Arc::new(RuntimeExternalListenerFactory));
adapters.server_protocol = Some(runtime_server_protocol_upgrader(global_ctx.clone()));
#[cfg(feature = "upnp")]
{
if host_config.upnp_enabled {
adapters.udp_hole_punch_platform = Some(
crate::instance::udp_hole_punch::runtime_udp_hole_punch_platform(
global_ctx.net_ns.clone(),
@@ -211,12 +247,12 @@ fn configure_runtime_core_host_adapters(
adapters.proxy_cidr_monitor_enabled = true;
}
#[cfg(feature = "public-ipv6-provider")]
{
if host_config.public_ipv6_provider_supported {
adapters.public_ipv6_host = Some(global_ctx.clone());
adapters.public_ipv6_provider = Some(runtime_public_ipv6_provider_platform(&global_ctx));
}
#[cfg(feature = "wireguard")]
{
if host_config.vpn_portal_enabled {
use crate::common::config::ConfigLoader as _;
adapters.vpn_portal = Some(crate::vpn_portal::wireguard::WireGuardPortalHost::new(
@@ -230,7 +266,7 @@ fn configure_runtime_core_host_adapters(
adapters
}
#[cfg(feature = "management")]
#[cfg(feature = "web-client")]
pub(crate) fn runtime_one_shot_manual_connector(
global_ctx: ArcGlobalCtx,
config: &TomlConfig,
+36 -2
View File
@@ -44,13 +44,44 @@ pub(crate) fn runtime_core_host_config() -> CoreInstanceHostConfig {
all(target_os = "macos", feature = "macos-ne"),
target_env = "ohos"
))),
public_ipv6_provider_supported: cfg!(target_os = "linux"),
public_ipv6_provider_supported: cfg!(all(
target_os = "linux",
feature = "public-ipv6-provider"
)),
gateway_enabled: cfg!(feature = "socks5"),
proxy_enabled: cfg!(any(feature = "kcp", feature = "quic")),
vpn_portal_enabled: cfg!(feature = "wireguard"),
magic_dns_enabled: cfg!(feature = "magic-dns"),
kcp_enabled: cfg!(feature = "kcp"),
quic_enabled: cfg!(feature = "quic"),
udp_broadcast_enabled: cfg!(all(target_os = "windows", feature = "tun")),
upnp_enabled: cfg!(feature = "upnp"),
tcp_hole_punching_enabled: cfg!(feature = "tcp-hole-punch"),
ignore_unsupported_config: false,
easytier_version: EASYTIER_VERSION.to_owned(),
endpoint_protocols: IpScheme::VARIANTS.iter().map(ToString::to_string).collect(),
}
}
pub(crate) fn compact_runtime_core_host_config() -> CoreInstanceHostConfig {
let mut config = runtime_core_host_config();
config.force_exit_node = false;
config.host_routing.local_exit_node_fallback = false;
config.public_ipv6_provider_supported = false;
config.gateway_enabled = false;
config.proxy_enabled = false;
config.vpn_portal_enabled = false;
config.magic_dns_enabled = false;
config.kcp_enabled = false;
config.quic_enabled = false;
config.udp_broadcast_enabled = false;
config.upnp_enabled = false;
config.tcp_hole_punching_enabled = false;
config.ignore_unsupported_config = true;
config.endpoint_protocols = vec!["tcp".to_owned(), "udp".to_owned()];
config
}
pub(crate) fn runtime_peer_credential_storage(
global_ctx: &ArcGlobalCtx,
) -> Option<Arc<dyn CredentialStorage>> {
@@ -71,6 +102,9 @@ pub(crate) fn test_core_instance_config(
let config = TomlConfig::new_from_str(&global_ctx.config.dump())
.expect("test configuration should round-trip through TOML");
config.set_ipv4(global_ctx.get_ipv4());
config.set_ipv6(global_ctx.get_ipv6());
config.set_flags(global_ctx.get_flags());
let mut host = runtime_core_host_config();
let hostname = global_ctx.get_hostname();
host.hostname_fallback = (!hostname.is_empty()).then_some(hostname);
@@ -113,7 +147,7 @@ mod tests {
assert_eq!(config.smoltcp_available, cfg!(feature = "smoltcp"));
assert_eq!(
config.public_ipv6_provider_supported,
cfg!(target_os = "linux")
cfg!(all(target_os = "linux", feature = "public-ipv6-provider"))
);
assert_eq!(config.easytier_version, EASYTIER_VERSION);
}
@@ -403,7 +403,7 @@ impl MagicDnsServerInstance {
rpc_server.set_hook(data.clone());
// Use configured tld_dns_zone or fall back to DEFAULT_ET_DNS_ZONE if empty
let flags = global_ctx.config.get_flags();
let flags = global_ctx.get_flags();
let tld_dns_zone_clone = flags.tld_dns_zone.clone();
data.update_dns_records(std::iter::empty(), &tld_dns_zone_clone)
+29 -5
View File
@@ -58,6 +58,19 @@ pub fn native_instance_manager_with_runtime(
native_instance_manager_with_optional_runtime(Some(runtime_handle))
}
#[cfg(feature = "management-rpc")]
pub fn native_compact_instance_manager_with_runtime(
runtime_handle: tokio::runtime::Handle,
) -> NativeInstanceManager {
let process_runtime = CoreProcessRuntime::new();
let factory = NativeInstanceFactory::new(process_runtime)
.with_runtime_handle(Some(runtime_handle.clone()))
.with_compact_runtime();
#[cfg(feature = "logging")]
let factory = factory.with_cli_event_logging();
InstanceManager::new(factory, Some(runtime_handle))
}
#[cfg(feature = "management")]
pub fn native_process_management(
instances: Arc<NativeInstanceManager>,
@@ -85,7 +98,8 @@ fn native_instance_manager_with_optional_runtime(
pub struct NativeInstanceFactory {
process_runtime: Arc<CoreProcessRuntime>,
runtime_handle: Option<tokio::runtime::Handle>,
#[cfg(feature = "management")]
compact_runtime: bool,
#[cfg(feature = "logging")]
log_cli_events: bool,
}
@@ -94,12 +108,13 @@ impl NativeInstanceFactory {
Self {
process_runtime,
runtime_handle: None,
#[cfg(feature = "management")]
compact_runtime: false,
#[cfg(feature = "logging")]
log_cli_events: false,
}
}
#[cfg(feature = "management")]
#[cfg(feature = "logging")]
fn with_cli_event_logging(mut self) -> Self {
self.log_cli_events = true;
self
@@ -110,6 +125,11 @@ impl NativeInstanceFactory {
self.runtime_handle = runtime_handle;
self
}
fn with_compact_runtime(mut self) -> Self {
self.compact_runtime = true;
self
}
}
impl InstanceFactory for NativeInstanceFactory {
@@ -126,8 +146,12 @@ impl InstanceFactory for NativeInstanceFactory {
.runtime_handle
.as_ref()
.map(tokio::runtime::Handle::enter);
let instance = compose_native_core_instance(config, self.process_runtime.clone())?;
#[cfg(feature = "management")]
let instance = compose_native_core_instance(
config,
self.process_runtime.clone(),
self.compact_runtime,
)?;
#[cfg(feature = "logging")]
if self.log_cli_events {
let events = subscribe_native_instance_event(&instance)
.ok_or_else(|| anyhow::anyhow!("native instance runtime host is unavailable"))?;
+2 -2
View File
@@ -25,7 +25,7 @@ pub struct NativeInstanceEnvironment {
impl NativeInstanceEnvironment {
fn new(global_ctx: ArcGlobalCtx, runtime: Arc<NativeHostRuntime>) -> Self {
let socket_context = SocketContext::default()
.with_socket_mark(global_ctx.config.get_flags().socket_mark)
.with_socket_mark(global_ctx.get_flags().socket_mark)
.with_netns(global_ctx.net_ns.name().map(NetNamespace::new));
Self {
global_ctx,
@@ -49,7 +49,7 @@ impl ConnectorEnvironment for NativeInstanceEnvironment {
}
fn mapped_listeners(&self) -> Vec<url::Url> {
self.global_ctx.config.get_mapped_listeners()
self.global_ctx.runtime_mapped_listeners()
}
fn is_local_ip(&self, ip: &IpAddr) -> bool {
+1 -1
View File
@@ -1,4 +1,4 @@
#[cfg(feature = "management")]
#[cfg(feature = "logging")]
pub(crate) mod cli_event_logger;
pub(crate) mod composition;
pub(crate) mod config;
+110 -1
View File
@@ -1,7 +1,8 @@
use std::sync::Arc;
use easytier_core::{
gateway::dhcp::DhcpIpv4Host, host::packet::HostPacketReceiver, instance::CorePacketPlane,
config::runtime::CoreInstanceRuntimeConfig, gateway::dhcp::DhcpIpv4Host,
host::packet::HostPacketReceiver, instance::CorePacketPlane,
};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
@@ -77,6 +78,41 @@ impl NativeInstanceRuntimeHost {
self.event_journal.events()
}
#[cfg(feature = "web-client")]
fn synchronize_global_ctx_config(
&self,
patch: &crate::proto::api::config::InstanceConfigPatch,
config: &CoreInstanceRuntimeConfig,
) {
if patch.hostname.is_some() {
self.global_ctx.set_hostname(
config
.peer
.runtime
.core
.node
.hostname
.clone()
.unwrap_or_default(),
);
}
if patch.ipv4.is_some() && !config.services.dhcp_ipv4 {
self.global_ctx
.set_ipv4(crate::common::global_ctx::GlobalCtx::runtime_ipv4(
&config.peer,
));
}
if patch.ipv6.is_some() {
self.global_ctx
.set_ipv6(crate::common::global_ctx::GlobalCtx::runtime_ipv6(
&config.peer,
));
}
if patch.disable_relay_data.is_some() {
self.global_ctx.set_flags(config.peer.flags.clone());
}
}
pub(crate) fn subscribe_event(&self) -> crate::common::global_ctx::EventBusSubscriber {
self.global_ctx.subscribe()
}
@@ -98,6 +134,15 @@ mod tests {
global_ctx::{GlobalCtx, GlobalCtxEvent},
};
#[cfg(feature = "web-client")]
fn runtime_config(config: &TomlConfig) -> CoreInstanceRuntimeConfig {
let normalized = easytier_core::instance::CoreInstanceConfig::from_toml(config).unwrap();
CoreInstanceRuntimeConfig {
services: normalized.connectivity.runtime,
peer: Arc::new(normalized.peer.snapshot),
}
}
#[test]
fn runtime_host_owns_event_subscription_context() {
let global_ctx = Arc::new(GlobalCtx::new(TomlConfig::default()));
@@ -111,4 +156,68 @@ mod tests {
GlobalCtxEvent::CredentialChanged
);
}
#[cfg(feature = "web-client")]
#[test]
fn runtime_host_synchronizes_normalized_config_without_management() {
use easytier_core::{config::toml::ConfigLoader as _, instance::InstanceRuntimeHost as _};
let config = TomlConfig::default();
config.set_hostname(Some("before".to_owned()));
config.set_ipv4(Some("10.20.0.1/24".parse().unwrap()));
config.set_ipv6(Some("fd00::1/64".parse().unwrap()));
let global_ctx = Arc::new(GlobalCtx::new(config.clone()));
let runtime_host = NativeInstanceRuntimeHost::new(global_ctx.clone());
assert_eq!(global_ctx.get_hostname(), "before");
assert_eq!(global_ctx.get_ipv4(), Some("10.20.0.1/24".parse().unwrap()));
assert_eq!(global_ctx.get_ipv6(), Some("fd00::1/64".parse().unwrap()));
assert!(!global_ctx.get_flags().disable_relay_data);
config.set_hostname(Some("after".to_owned()));
config.set_ipv4(Some("10.20.0.2/24".parse().unwrap()));
config.set_ipv6(Some("fd00::2/64".parse().unwrap()));
let mut flags = config.get_flags();
flags.disable_relay_data = true;
config.set_flags(flags);
runtime_host.synchronize_config(
&crate::proto::api::config::InstanceConfigPatch {
hostname: Some("ignored-raw-hostname".to_owned()),
ipv4: Some("10.99.0.1/24".parse::<cidr::Ipv4Inet>().unwrap().into()),
ipv6: Some("fd99::1/64".parse::<cidr::Ipv6Inet>().unwrap().into()),
disable_relay_data: Some(false),
..Default::default()
},
&runtime_config(&config),
);
assert_eq!(global_ctx.get_hostname(), "after");
assert_eq!(global_ctx.get_ipv4(), Some("10.20.0.2/24".parse().unwrap()));
assert_eq!(global_ctx.get_ipv6(), Some("fd00::2/64".parse().unwrap()));
assert!(global_ctx.get_flags().disable_relay_data);
}
#[cfg(feature = "web-client")]
#[test]
fn runtime_host_preserves_dhcp_ipv4_during_config_synchronization() {
use easytier_core::{config::toml::ConfigLoader as _, instance::InstanceRuntimeHost as _};
let config = TomlConfig::default();
config.set_dhcp(true);
let global_ctx = Arc::new(GlobalCtx::new(config.clone()));
let runtime_host = NativeInstanceRuntimeHost::new(global_ctx.clone());
let lease = "10.20.0.7/24".parse().unwrap();
global_ctx.set_ipv4(Some(lease));
config.set_ipv4(None);
runtime_host.synchronize_config(
&crate::proto::api::config::InstanceConfigPatch {
ipv4: Some("10.99.0.1/24".parse::<cidr::Ipv4Inet>().unwrap().into()),
..Default::default()
},
&runtime_config(&config),
);
assert_eq!(global_ctx.get_ipv4(), Some(lease));
}
}
@@ -85,28 +85,6 @@ impl EventJournal {
self.events.read().unwrap().iter().cloned().collect()
}
pub(super) fn synchronize_config(
&self,
patch: &crate::proto::api::config::InstanceConfigPatch,
) {
if let Some(hostname) = &patch.hostname {
self.global_ctx.set_hostname(hostname.clone());
}
if let Some(ipv4) = patch.ipv4.as_ref()
&& !self.global_ctx.config.get_dhcp()
{
self.global_ctx.set_ipv4(Some((*ipv4).into()));
}
if let Some(ipv6) = patch.ipv6.as_ref() {
self.global_ctx.set_ipv6(Some((*ipv6).into()));
}
if let Some(disable_relay_data) = patch.disable_relay_data {
let mut flags = self.global_ctx.get_flags();
flags.disable_relay_data = disable_relay_data;
self.global_ctx.set_flags(flags);
}
}
pub(super) fn publish_config_patch(
&self,
patch: crate::proto::api::config::InstanceConfigPatch,
@@ -29,14 +29,21 @@ impl InstanceRuntimeHost for NativeInstanceRuntimeHost {
self.management_events_snapshot()
}
#[cfg(feature = "management")]
fn synchronize_config(&self, patch: &crate::proto::api::config::InstanceConfigPatch) {
self.event_journal.synchronize_config(patch);
#[cfg(feature = "web-client")]
fn synchronize_config(
&self,
patch: &crate::proto::api::config::InstanceConfigPatch,
config: &easytier_core::config::runtime::CoreInstanceRuntimeConfig,
) {
self.synchronize_global_ctx_config(patch, config);
}
#[cfg(feature = "management")]
#[cfg(feature = "web-client")]
fn publish_config_patch(&self, patch: crate::proto::api::config::InstanceConfigPatch) {
#[cfg(feature = "management")]
self.event_journal.publish_config_patch(patch);
#[cfg(not(feature = "management"))]
let _ = patch;
}
fn attach_tun_fd(&self, fd: i32) -> anyhow::Result<()> {
@@ -5,10 +5,7 @@ use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle};
use crate::common::global_ctx::ArcGlobalCtx;
#[cfg(feature = "magic-dns")]
use crate::{
common::config::ConfigLoader as _,
instance::dns_server::{MAGIC_DNS_FAKE_IP, runner::DnsRunner},
};
use crate::instance::dns_server::{MAGIC_DNS_FAKE_IP, runner::DnsRunner};
#[derive(Default)]
pub(super) struct MagicDnsRuntime {
@@ -30,7 +27,7 @@ impl MagicDnsRuntime {
tun_dev: Option<String>,
tun_ip: Ipv4Inet,
) -> Self {
let active = global_ctx.config.get_flags().accept_dns.then(|| {
let active = global_ctx.get_flags().accept_dns.then(|| {
let mut runner = DnsRunner::new(
packet_plane,
global_ctx,
@@ -17,7 +17,6 @@ use tokio_util::sync::CancellationToken;
use super::{MagicDnsRuntime, tun_common::TunNicState};
use crate::{
common::{
config::ConfigLoader as _,
error::Error,
global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
},
@@ -133,7 +132,7 @@ impl NativeTunRuntime {
pub(super) async fn prepare(&self, packet_plane: Arc<CorePacketPlane>) -> anyhow::Result<()> {
self.nic.drain().await;
if !self.global_ctx.config.get_flags().no_tun {
if !self.global_ctx.get_flags().no_tun {
self.start_static_ip(packet_plane).await?;
}
Ok(())
+1 -1
View File
@@ -663,7 +663,7 @@ impl VirtualNic {
let dev = AsyncDevice::new(dev)?;
let flags = self.global_ctx.config.get_flags();
let flags = self.global_ctx.get_flags();
let mut mtu_in_config = flags.mtu;
if flags.enable_encryption {
mtu_in_config -= 20;
+1 -1
View File
@@ -20,7 +20,7 @@ pub mod service_manager;
pub(crate) mod socket;
pub mod tunnel;
pub mod utils;
#[cfg(feature = "management")]
#[cfg(feature = "web-client")]
pub mod web_client;
#[cfg(test)]
+1 -1
View File
@@ -1,5 +1,5 @@
pub use easytier_proto::api;
#[cfg(feature = "management")]
#[cfg(feature = "web-client")]
pub use easytier_proto::web;
pub use easytier_proto::{
ALL_DESCRIPTOR_BYTES, acl, common, core_config, error, peer_rpc, rpc_types,
+48 -20
View File
@@ -3,6 +3,7 @@ use std::sync::Arc;
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use easytier_core::{
config::toml::ConfigLoader as _,
connectivity::{manual::ManualTunnelConnector, protocol::raw::TunnelDialer},
management::{ConfigServerEndpoint, WebClientConfig},
socket::IpVersion,
@@ -10,18 +11,21 @@ use easytier_core::{
};
use url::Url;
#[cfg(feature = "management")]
use crate::{
common::os_info::collect_device_os_info, instance::config_storage::NativeConfigFileStorage,
rpc_service::logger::NativeLoggerControl,
};
use crate::{
common::{
MachineIdOptions, config::TomlConfigLoader, constants::EASYTIER_VERSION,
global_ctx::GlobalCtx, os_info::collect_device_os_info, resolve_machine_id,
global_ctx::GlobalCtx, resolve_machine_id,
},
instance::{
composition::runtime_one_shot_manual_connector,
config_storage::NativeConfigFileStorage,
factory::{NativeInstanceFactory, NativeInstanceManager},
host::NativeInstanceHost,
},
rpc_service::logger::NativeLoggerControl,
tunnel::TunnelScheme,
};
@@ -46,23 +50,32 @@ impl WebClient {
S: ToString,
H: ToString,
{
Self {
inner: easytier_core::management::WebClient::new(
connector,
WebClientConfig {
token: token.to_string(),
machine_id,
hostname: hostname.to_string(),
device_os: collect_device_os_info(),
easytier_version: EASYTIER_VERSION.to_owned(),
secure_mode,
},
manager,
hooks.unwrap_or_else(|| Arc::new(DefaultHooks)),
Arc::new(NativeConfigFileStorage),
Arc::new(NativeLoggerControl),
),
}
let config = WebClientConfig {
token: token.to_string(),
machine_id,
hostname: hostname.to_string(),
device_os: web_client_device_os_info(),
easytier_version: EASYTIER_VERSION.to_owned(),
secure_mode,
};
#[cfg(feature = "management")]
let inner = easytier_core::management::WebClient::new(
connector,
config,
manager,
hooks.unwrap_or_else(|| Arc::new(DefaultHooks)),
Arc::new(NativeConfigFileStorage),
Arc::new(NativeLoggerControl),
);
#[cfg(not(feature = "management"))]
let inner = easytier_core::management::WebClient::new(
connector,
config,
manager,
hooks.unwrap_or_else(|| Arc::new(DefaultHooks)),
Arc::new(easytier_core::management::UnsupportedConfigFileStorage),
);
Self { inner }
}
pub fn is_connected(&self) -> bool {
@@ -70,6 +83,20 @@ impl WebClient {
}
}
#[cfg(feature = "management")]
fn web_client_device_os_info() -> easytier_proto::web::DeviceOsInfo {
collect_device_os_info()
}
#[cfg(not(feature = "management"))]
fn web_client_device_os_info() -> easytier_proto::web::DeviceOsInfo {
easytier_proto::web::DeviceOsInfo {
os_type: std::env::consts::OS.to_owned(),
version: String::new(),
distribution: String::new(),
}
}
pub struct DefaultHooks;
#[async_trait]
@@ -113,6 +140,7 @@ pub async fn run_web_client(
let global_ctx = Arc::new(GlobalCtx::new(config.clone()));
let mut flags = global_ctx.get_flags();
flags.bind_device = false;
config.set_flags(flags.clone());
global_ctx.set_flags(flags);
let hostname =
hostname.unwrap_or_else(|| gethostname::gethostname().to_string_lossy().to_string());