From 841d52591339b5ddce071f397ca07407dbf1e529 Mon Sep 17 00:00:00 2001 From: Mg Pig Date: Thu, 2 Oct 2025 20:30:39 +0800 Subject: [PATCH] refactor(rpc): Centralize RPC service and unify API (#1427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces a major refactoring of the RPC service layer to improve modularity, unify the API, and simplify the overall architecture. Key changes: - Replaced per-network-instance RPC services with a single global RPC server, reducing resource usage and simplifying management. - All clients (CLI, Web UI, etc.) now interact with EasyTier core through a unified RPC entrypoint, enabling consistent authentication and control. - RPC implementation logic has been moved to `easytier/src/rpc_service/` and organized by functionality (e.g., `instance_manage.rs`, `peer_manage.rs`, `config.rs`) for better maintainability. - Standardized Protobuf API definitions under `easytier/src/proto/` with an `api_` prefix (e.g., `cli.proto` → `api_instance.proto`) to provide a consistent interface. - CLI commands now require explicit `--instance-id` or `--instance-name` when multiple network instances are running; the parameter is optional when only one instance exists. BREAKING CHANGE: RPC portal configuration (`rpc_portal` and `rpc_portal_whitelist`) has been removed from per-instance configs and the Web UI. The RPC listen address must now be specified globally via the `--rpc-portal` command-line flag or the `ET_RPC_PORTAL` environment variable, as there is only one RPC service for the entire application. --- .../easytier-android-jni/src/lib.rs | 2 +- .../frontend-lib/src/components/Config.vue | 17 - .../frontend-lib/src/types/network.ts | 5 - easytier-web/src/client_manager/mod.rs | 10 +- easytier-web/src/client_manager/session.rs | 10 +- easytier-web/src/restful/network.rs | 2 +- easytier/build.rs | 10 +- easytier/src/common/config.rs | 26 -- easytier/src/common/global_ctx.rs | 4 +- easytier/src/common/stats_manager.rs | 15 +- easytier/src/connector/direct.rs | 2 +- easytier/src/connector/manual.rs | 43 +- easytier/src/easytier-cli.rs | 164 +++++-- easytier/src/easytier-core.rs | 77 ++-- easytier/src/gateway/kcp_proxy.rs | 2 +- easytier/src/gateway/quic_proxy.rs | 2 +- easytier/src/gateway/tcp_proxy.rs | 2 +- .../instance/dns_server/client_instance.rs | 2 +- .../instance/dns_server/server_instance.rs | 2 +- easytier/src/instance/dns_server/tests.rs | 2 +- easytier/src/instance/instance.rs | 430 ++++++++++-------- easytier/src/instance/mod.rs | 2 - easytier/src/instance_manager.rs | 25 +- easytier/src/launcher.rs | 111 ++--- easytier/src/lib.rs | 1 + easytier/src/peer_center/instance.rs | 6 +- easytier/src/peer_center/mod.rs | 2 +- easytier/src/peers/foreign_network_manager.rs | 2 +- easytier/src/peers/peer.rs | 2 +- easytier/src/peers/peer_conn.rs | 2 +- easytier/src/peers/peer_manager.rs | 8 +- easytier/src/peers/peer_map.rs | 4 +- easytier/src/peers/peer_ospf_route.rs | 8 +- easytier/src/peers/route_trait.rs | 4 +- easytier/src/peers/rpc_service.rs | 51 ++- easytier/src/proto/api.rs | 262 +++++++++++ .../proto/{config.proto => api_config.proto} | 15 +- .../proto/{cli.proto => api_instance.proto} | 140 +++--- easytier/src/proto/api_logger.proto | 24 + easytier/src/proto/api_manage.proto | 157 +++++++ easytier/src/proto/cli.rs | 177 ------- easytier/src/proto/config.rs | 75 --- easytier/src/proto/magic_dns.proto | 4 +- easytier/src/proto/mod.rs | 3 +- easytier/src/proto/rpc_types/error.rs | 2 +- easytier/src/proto/web.proto | 185 +------- easytier/src/rpc_service/acl_manage.rs | 50 ++ easytier/src/rpc_service/api.rs | 164 +++++++ easytier/src/rpc_service/config.rs | 36 ++ easytier/src/rpc_service/connector_manage.rs | 36 ++ easytier/src/rpc_service/instance_manage.rs | 135 ++++++ .../logger.rs} | 8 +- .../src/rpc_service/mapped_listener_manage.rs | 38 ++ easytier/src/rpc_service/mod.rs | 108 +++++ easytier/src/rpc_service/peer_manage.rs | 91 ++++ .../src/rpc_service/port_forward_manage.rs | 36 ++ easytier/src/rpc_service/proxy.rs | 41 ++ easytier/src/rpc_service/stats.rs | 50 ++ easytier/src/rpc_service/vpn_portal.rs | 36 ++ easytier/src/tests/mod.rs | 6 +- easytier/src/tests/three_node.rs | 6 +- easytier/src/utils.rs | 9 +- easytier/src/web_client/controller.rs | 134 +----- easytier/src/web_client/mod.rs | 7 +- easytier/src/web_client/session.rs | 14 +- 65 files changed, 1953 insertions(+), 1153 deletions(-) create mode 100644 easytier/src/proto/api.rs rename easytier/src/proto/{config.proto => api_config.proto} (78%) rename easytier/src/proto/{cli.proto => api_instance.proto} (69%) create mode 100644 easytier/src/proto/api_logger.proto create mode 100644 easytier/src/proto/api_manage.proto delete mode 100644 easytier/src/proto/cli.rs delete mode 100644 easytier/src/proto/config.rs create mode 100644 easytier/src/rpc_service/acl_manage.rs create mode 100644 easytier/src/rpc_service/api.rs create mode 100644 easytier/src/rpc_service/config.rs create mode 100644 easytier/src/rpc_service/connector_manage.rs create mode 100644 easytier/src/rpc_service/instance_manage.rs rename easytier/src/{instance/logger_rpc_service.rs => rpc_service/logger.rs} (97%) create mode 100644 easytier/src/rpc_service/mapped_listener_manage.rs create mode 100644 easytier/src/rpc_service/mod.rs create mode 100644 easytier/src/rpc_service/peer_manage.rs create mode 100644 easytier/src/rpc_service/port_forward_manage.rs create mode 100644 easytier/src/rpc_service/proxy.rs create mode 100644 easytier/src/rpc_service/stats.rs create mode 100644 easytier/src/rpc_service/vpn_portal.rs diff --git a/easytier-contrib/easytier-android-jni/src/lib.rs b/easytier-contrib/easytier-android-jni/src/lib.rs index 4f32a96..f6ada29 100644 --- a/easytier-contrib/easytier-android-jni/src/lib.rs +++ b/easytier-contrib/easytier-android-jni/src/lib.rs @@ -1,4 +1,4 @@ -use easytier::proto::web::{NetworkInstanceRunningInfo, NetworkInstanceRunningInfoMap}; +use easytier::proto::api::manage::{NetworkInstanceRunningInfo, NetworkInstanceRunningInfoMap}; use jni::objects::{JClass, JObjectArray, JString}; use jni::sys::{jint, jstring}; use jni::JNIEnv; diff --git a/easytier-web/frontend-lib/src/components/Config.vue b/easytier-web/frontend-lib/src/components/Config.vue index 1692883..dd33177 100644 --- a/easytier-web/frontend-lib/src/components/Config.vue +++ b/easytier-web/frontend-lib/src/components/Config.vue @@ -307,23 +307,6 @@ const portForwardProtocolOptions = ref(["tcp", "udp"]); -
-
- - -
-
- -
-
- - -
-
-
diff --git a/easytier-web/frontend-lib/src/types/network.ts b/easytier-web/frontend-lib/src/types/network.ts index fdccc7d..3100734 100644 --- a/easytier-web/frontend-lib/src/types/network.ts +++ b/easytier-web/frontend-lib/src/types/network.ts @@ -31,7 +31,6 @@ export interface NetworkConfig { advanced_settings: boolean listener_urls: string[] - rpc_port: number latency_first: boolean dev_name: string @@ -70,8 +69,6 @@ export interface NetworkConfig { enable_magic_dns?: boolean enable_private_mode?: boolean - rpc_portal_whitelists: string[] - port_forwards: PortForwardConfig[] } @@ -104,7 +101,6 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { 'udp://0.0.0.0:11010', 'wg://0.0.0.0:11011', ], - rpc_port: 0, latency_first: false, dev_name: '', @@ -135,7 +131,6 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { mapped_listeners: [], enable_magic_dns: false, enable_private_mode: false, - rpc_portal_whitelists: [], port_forwards: [], } } diff --git a/easytier-web/src/client_manager/mod.rs b/easytier-web/src/client_manager/mod.rs index f9e0802..8e4010d 100644 --- a/easytier-web/src/client_manager/mod.rs +++ b/easytier-web/src/client_manager/mod.rs @@ -247,9 +247,10 @@ impl ClientManager { #[cfg(test)] mod tests { - use std::time::Duration; + use std::{sync::Arc, time::Duration}; use easytier::{ + instance_manager::NetworkInstanceManager, tunnel::{ common::tests::wait_for_condition, udp::{UdpTunnelConnector, UdpTunnelListener}, @@ -273,7 +274,12 @@ mod tests { .unwrap(); let connector = UdpTunnelConnector::new("udp://127.0.0.1:54333".parse().unwrap()); - let _c = WebClient::new(connector, "test", "test"); + let _c = WebClient::new( + connector, + "test", + "test", + Arc::new(NetworkInstanceManager::new()), + ); wait_for_condition( || async { mgr.client_sessions.len() == 1 }, diff --git a/easytier-web/src/client_manager/session.rs b/easytier-web/src/client_manager/session.rs index 97a2259..8287787 100644 --- a/easytier-web/src/client_manager/session.rs +++ b/easytier-web/src/client_manager/session.rs @@ -4,13 +4,13 @@ use anyhow::Context; use easytier::{ common::scoped_task::ScopedTask, proto::{ + api::manage::{ + NetworkConfig, RunNetworkInstanceRequest, WebClientService, + WebClientServiceClientFactory, + }, rpc_impl::bidirect::BidirectRpcManager, rpc_types::{self, controller::BaseController}, - web::{ - HeartbeatRequest, HeartbeatResponse, NetworkConfig, RunNetworkInstanceRequest, - WebClientService, WebClientServiceClientFactory, WebServerService, - WebServerServiceServer, - }, + web::{HeartbeatRequest, HeartbeatResponse, WebServerService, WebServerServiceServer}, }, tunnel::Tunnel, }; diff --git a/easytier-web/src/restful/network.rs b/easytier-web/src/restful/network.rs index 9a103cc..90f5c5a 100644 --- a/easytier-web/src/restful/network.rs +++ b/easytier-web/src/restful/network.rs @@ -8,7 +8,7 @@ use axum_login::AuthUser; use easytier::launcher::NetworkConfig; use easytier::proto::common::Void; use easytier::proto::rpc_types::controller::BaseController; -use easytier::proto::{self, web::*}; +use easytier::proto::{self, api::manage::*, web::*}; use crate::client_manager::session::{Location, Session}; use crate::client_manager::ClientManager; diff --git a/easytier/build.rs b/easytier/build.rs index f12911f..adef2ef 100644 --- a/easytier/build.rs +++ b/easytier/build.rs @@ -144,11 +144,13 @@ fn main() -> Result<(), Box> { let proto_files = [ "src/proto/error.proto", "src/proto/tests.proto", - "src/proto/cli.proto", + "src/proto/api_instance.proto", + "src/proto/api_logger.proto", + "src/proto/api_config.proto", + "src/proto/api_manage.proto", "src/proto/web.proto", "src/proto/magic_dns.proto", "src/proto/acl.proto", - "src/proto/config.proto", ]; for proto_file in proto_files.iter().chain(proto_files_reflect.iter()) { @@ -161,7 +163,7 @@ fn main() -> Result<(), Box> { .type_attribute(".acl", "#[derive(serde::Serialize, serde::Deserialize)]") .type_attribute(".common", "#[derive(serde::Serialize, serde::Deserialize)]") .type_attribute(".error", "#[derive(serde::Serialize, serde::Deserialize)]") - .type_attribute(".cli", "#[derive(serde::Serialize, serde::Deserialize)]") + .type_attribute(".api", "#[derive(serde::Serialize, serde::Deserialize)]") .type_attribute(".web", "#[derive(serde::Serialize, serde::Deserialize)]") .type_attribute(".config", "#[derive(serde::Serialize, serde::Deserialize)]") .type_attribute( @@ -180,7 +182,7 @@ fn main() -> Result<(), Box> { "#[derive(Hash, Eq, serde::Serialize, serde::Deserialize)]", ) .type_attribute("common.RpcDescriptor", "#[derive(Hash, Eq)]") - .field_attribute(".web.NetworkConfig", "#[serde(default)]") + .field_attribute(".api.manage.NetworkConfig", "#[serde(default)]") .service_generator(Box::new(rpc_build::ServiceGenerator::new())) .btree_map(["."]) .skip_debug([".common.Ipv4Addr", ".common.Ipv6Addr", ".common.UUID"]); diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index a4b3e18..b27115f 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -6,7 +6,6 @@ use std::{ }; use anyhow::Context; -use cidr::IpCidr; use serde::{Deserialize, Serialize}; use crate::{ @@ -169,12 +168,6 @@ pub trait ConfigLoader: Send + Sync { fn get_mapped_listeners(&self) -> Vec; fn set_mapped_listeners(&self, listeners: Option>); - fn get_rpc_portal(&self) -> Option; - fn set_rpc_portal(&self, addr: SocketAddr); - - fn get_rpc_portal_whitelist(&self) -> Option>; - fn set_rpc_portal_whitelist(&self, whitelist: Option>); - fn get_vpn_portal_config(&self) -> Option; fn set_vpn_portal_config(&self, config: VpnPortalConfig); @@ -398,9 +391,6 @@ struct Config { peer: Option>, proxy_network: Option>, - rpc_portal: Option, - rpc_portal_whitelist: Option>, - vpn_portal_config: Option, routes: Option>, @@ -692,22 +682,6 @@ impl ConfigLoader for TomlConfigLoader { self.config.lock().unwrap().mapped_listeners = listeners; } - fn get_rpc_portal(&self) -> Option { - self.config.lock().unwrap().rpc_portal - } - - fn set_rpc_portal(&self, addr: SocketAddr) { - self.config.lock().unwrap().rpc_portal = Some(addr); - } - - fn get_rpc_portal_whitelist(&self) -> Option> { - self.config.lock().unwrap().rpc_portal_whitelist.clone() - } - - fn set_rpc_portal_whitelist(&self, whitelist: Option>) { - self.config.lock().unwrap().rpc_portal_whitelist = whitelist; - } - fn get_vpn_portal_config(&self) -> Option { self.config.lock().unwrap().vpn_portal_config.clone() } diff --git a/easytier/src/common/global_ctx.rs b/easytier/src/common/global_ctx.rs index 5633450..670553b 100644 --- a/easytier/src/common/global_ctx.rs +++ b/easytier/src/common/global_ctx.rs @@ -10,9 +10,9 @@ use crate::common::stats_manager::StatsManager; use crate::common::token_bucket::TokenBucketManager; use crate::peers::acl_filter::AclFilter; use crate::proto::acl::GroupIdentity; -use crate::proto::cli::PeerConnInfo; +use crate::proto::api::config::InstanceConfigPatch; +use crate::proto::api::instance::PeerConnInfo; use crate::proto::common::{PeerFeatureFlag, PortForwardConfigPb}; -use crate::proto::config::InstanceConfigPatch; use crate::proto::peer_rpc::PeerGroupInfo; use crossbeam::atomic::AtomicCell; diff --git a/easytier/src/common/stats_manager.rs b/easytier/src/common/stats_manager.rs index b7d8077..39cc673 100644 --- a/easytier/src/common/stats_manager.rs +++ b/easytier/src/common/stats_manager.rs @@ -664,7 +664,7 @@ impl Default for StatsManager { mod tests { use super::*; use crate::common::stats_manager::{LabelSet, LabelType, MetricName, StatsManager}; - use crate::proto::cli::{ + use crate::proto::api::instance::{ GetPrometheusStatsRequest, GetPrometheusStatsResponse, GetStatsRequest, GetStatsResponse, }; use std::collections::BTreeMap; @@ -818,16 +818,19 @@ mod tests { #[tokio::test] async fn test_stats_rpc_data_structures() { // Test GetStatsRequest - let request = GetStatsRequest {}; - assert_eq!(request, GetStatsRequest {}); + let request = GetStatsRequest { instance: None }; + assert_eq!(request, GetStatsRequest { instance: None }); // Test GetStatsResponse let response = GetStatsResponse { metrics: vec![] }; assert!(response.metrics.is_empty()); // Test GetPrometheusStatsRequest - let prometheus_request = GetPrometheusStatsRequest {}; - assert_eq!(prometheus_request, GetPrometheusStatsRequest {}); + let prometheus_request = GetPrometheusStatsRequest { instance: None }; + assert_eq!( + prometheus_request, + GetPrometheusStatsRequest { instance: None } + ); // Test GetPrometheusStatsResponse let prometheus_response = GetPrometheusStatsResponse { @@ -867,7 +870,7 @@ mod tests { } // This simulates what the RPC service would do - let _metric_snapshot = crate::proto::cli::MetricSnapshot { + let _metric_snapshot = crate::proto::api::instance::MetricSnapshot { name: metric.name.to_string(), value: metric.value, labels, diff --git a/easytier/src/connector/direct.rs b/easytier/src/connector/direct.rs index 2a6ffb5..d91e4d5 100644 --- a/easytier/src/connector/direct.rs +++ b/easytier/src/connector/direct.rs @@ -35,7 +35,7 @@ use crate::{ use_global_var, }; -use crate::proto::cli::PeerConnInfo; +use crate::proto::api::instance::PeerConnInfo; use anyhow::Context; use rand::Rng; use tokio::{net::UdpSocket, task::JoinSet, time::timeout}; diff --git a/easytier/src/connector/manual.rs b/easytier/src/connector/manual.rs index 81bb7b9..6d2696d 100644 --- a/easytier/src/connector/manual.rs +++ b/easytier/src/connector/manual.rs @@ -18,12 +18,14 @@ use crate::{ common::{dns::socket_addrs, join_joinset_background, PeerId}, peers::peer_conn::PeerConnId, proto::{ - cli::{ - ConnectorManageAction, ListConnectorResponse, ManageConnectorResponse, PeerConnInfo, + api::instance::{ + Connector, ConnectorManageRpc, ConnectorStatus, ListConnectorRequest, + ListConnectorResponse, PeerConnInfo, }, rpc_types::{self, controller::BaseController}, }, tunnel::{IpVersion, TunnelConnector}, + utils::weak_upgrade, }; use crate::{ @@ -33,10 +35,6 @@ use crate::{ netns::NetNS, }, peers::peer_manager::PeerManager, - proto::cli::{ - Connector, ConnectorManageRpc, ConnectorStatus, ListConnectorRequest, - ManageConnectorRequest, - }, use_global_var, }; @@ -126,6 +124,14 @@ impl ManualConnectorManager { Ok(()) } + pub async fn clear_connectors(&self) { + self.list_connectors().await.iter().for_each(|x| { + if let Some(url) = &x.url { + self.data.removed_conn_urls.insert(url.to_string()); + } + }); + } + pub async fn list_connectors(&self) -> Vec { let conn_urls: BTreeSet = self .data @@ -421,7 +427,7 @@ impl ManualConnectorManager { } #[derive(Clone)] -pub struct ConnectorManagerRpcService(pub Arc); +pub struct ConnectorManagerRpcService(pub Weak); #[async_trait::async_trait] impl ConnectorManageRpc for ConnectorManagerRpcService { @@ -433,31 +439,10 @@ impl ConnectorManageRpc for ConnectorManagerRpcService { _request: ListConnectorRequest, ) -> Result { let mut ret = ListConnectorResponse::default(); - let connectors = self.0.list_connectors().await; + let connectors = weak_upgrade(&self.0)?.list_connectors().await; ret.connectors = connectors; Ok(ret) } - - async fn manage_connector( - &self, - _: BaseController, - req: ManageConnectorRequest, - ) -> Result { - let url: url::Url = req.url.ok_or(anyhow::anyhow!("url is empty"))?.into(); - if req.action == ConnectorManageAction::Remove as i32 { - self.0 - .remove_connector(url.clone()) - .await - .with_context(|| format!("remove connector failed: {:?}", url))?; - return Ok(ManageConnectorResponse::default()); - } else { - self.0 - .add_connector_by_url(url.as_str()) - .await - .with_context(|| format!("add connector failed: {:?}", url))?; - } - Ok(ManageConnectorResponse::default()) - } } #[cfg(test)] diff --git a/easytier/src/easytier-cli.rs b/easytier/src/easytier-cli.rs index 5a3ce6d..a66d2c9 100644 --- a/easytier/src/easytier-cli.rs +++ b/easytier/src/easytier-cli.rs @@ -26,25 +26,32 @@ use easytier::{ }, peers, proto::{ - cli::{ - list_peer_route_pair, AclManageRpc, AclManageRpcClientFactory, ConnectorManageRpc, - ConnectorManageRpcClientFactory, DumpRouteRequest, GetAclStatsRequest, - GetLoggerConfigRequest, GetPrometheusStatsRequest, GetStatsRequest, - GetVpnPortalInfoRequest, GetWhitelistRequest, ListConnectorRequest, - ListForeignNetworkRequest, ListGlobalForeignNetworkRequest, ListMappedListenerRequest, - ListPeerRequest, ListPeerResponse, ListPortForwardRequest, ListRouteRequest, - ListRouteResponse, LogLevel, LoggerRpc, LoggerRpcClientFactory, - MappedListenerManageRpc, MappedListenerManageRpcClientFactory, NodeInfo, PeerManageRpc, - PeerManageRpcClientFactory, PortForwardManageRpc, PortForwardManageRpcClientFactory, - SetLoggerConfigRequest, ShowNodeInfoRequest, StatsRpc, StatsRpcClientFactory, - TcpProxyEntryState, TcpProxyEntryTransportType, TcpProxyRpc, TcpProxyRpcClientFactory, - VpnPortalRpc, VpnPortalRpcClientFactory, + api::{ + config::{ + AclPatch, ConfigPatchAction, ConfigRpc, ConfigRpcClientFactory, + InstanceConfigPatch, PatchConfigRequest, PortForwardPatch, StringPatch, UrlPatch, + }, + instance::{ + instance_identifier::{InstanceSelector, Selector}, + list_peer_route_pair, AclManageRpc, AclManageRpcClientFactory, ConnectorManageRpc, + ConnectorManageRpcClientFactory, DumpRouteRequest, GetAclStatsRequest, + GetPrometheusStatsRequest, GetStatsRequest, GetVpnPortalInfoRequest, + GetWhitelistRequest, InstanceIdentifier, ListConnectorRequest, + ListForeignNetworkRequest, ListGlobalForeignNetworkRequest, + ListMappedListenerRequest, ListPeerRequest, ListPeerResponse, + ListPortForwardRequest, ListRouteRequest, ListRouteResponse, + MappedListenerManageRpc, MappedListenerManageRpcClientFactory, NodeInfo, + PeerManageRpc, PeerManageRpcClientFactory, PortForwardManageRpc, + PortForwardManageRpcClientFactory, ShowNodeInfoRequest, StatsRpc, + StatsRpcClientFactory, TcpProxyEntryState, TcpProxyEntryTransportType, TcpProxyRpc, + TcpProxyRpcClientFactory, VpnPortalRpc, VpnPortalRpcClientFactory, + }, + logger::{ + GetLoggerConfigRequest, LogLevel, LoggerRpc, LoggerRpcClientFactory, + SetLoggerConfigRequest, + }, }, common::{NatType, PortForwardConfigPb, SocketType}, - config::{ - AclPatch, ConfigPatchAction, ConfigRpc, ConfigRpcClientFactory, InstanceConfigPatch, - PatchConfigRequest, PortForwardPatch, StringPatch, UrlPatch, - }, peer_rpc::{GetGlobalPeerMapRequest, PeerCenterRpc, PeerCenterRpcClientFactory}, rpc_impl::standalone::StandAloneClient, rpc_types::controller::BaseController, @@ -58,8 +65,12 @@ rust_i18n::i18n!("locales", fallback = "en"); #[derive(Parser, Debug)] #[command(name = "easytier-cli", author, version = EASYTIER_VERSION, about, long_about = None)] struct Cli { - /// the instance name - #[arg(short = 'p', long, default_value = "127.0.0.1:15888")] + #[arg( + short = 'p', + long, + default_value = "127.0.0.1:15888", + help = "easytier-core rpc portal address" + )] rpc_portal: SocketAddr, #[arg(short, long, default_value = "false", help = "verbose output")] @@ -74,6 +85,9 @@ struct Cli { )] output_format: OutputFormat, + #[command(flatten)] + instance_select: InstanceSelectArgs, + #[command(subcommand)] sub_command: SubCommand, } @@ -120,6 +134,28 @@ enum OutputFormat { Json, } +#[derive(Parser, Debug)] +struct InstanceSelectArgs { + #[arg(short = 'i', long = "instance-id", help = "the instance id")] + id: Option, + + #[arg(short = 'n', long = "instance-name", help = "the instance name")] + name: Option, +} + +impl From<&InstanceSelectArgs> for InstanceIdentifier { + fn from(args: &InstanceSelectArgs) -> Self { + InstanceIdentifier { + selector: match args.id { + Some(id) => Some(Selector::Id(id.into())), + None => Some(Selector::InstanceSelector(InstanceSelector { + name: args.name.clone(), + })), + }, + } + } +} + #[derive(Args, Debug)] struct PeerArgs { #[command(subcommand)] @@ -351,6 +387,7 @@ struct CommandHandler<'a> { client: tokio::sync::Mutex, verbose: bool, output_format: &'a OutputFormat, + instance_selector: InstanceIdentifier, } type RpcClient = StandAloneClient; @@ -491,14 +528,18 @@ impl CommandHandler<'_> { async fn list_peers(&self) -> Result { let client = self.get_peer_manager_client().await?; - let request = ListPeerRequest::default(); + let request = ListPeerRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client.list_peer(BaseController::default(), request).await?; Ok(response) } async fn list_routes(&self) -> Result { let client = self.get_peer_manager_client().await?; - let request = ListRouteRequest::default(); + let request = ListRouteRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_route(BaseController::default(), request) .await?; @@ -618,7 +659,12 @@ impl CommandHandler<'_> { let client = self.get_peer_manager_client().await?; let node_info = client - .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .show_node_info( + BaseController::default(), + ShowNodeInfoRequest { + instance: Some(self.instance_selector.clone()), + }, + ) .await? .node_info .ok_or(anyhow::anyhow!("node info not found"))?; @@ -671,7 +717,9 @@ impl CommandHandler<'_> { async fn handle_route_dump(&self) -> Result<(), Error> { let client = self.get_peer_manager_client().await?; - let request = DumpRouteRequest::default(); + let request = DumpRouteRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .dump_route(BaseController::default(), request) .await?; @@ -681,7 +729,9 @@ impl CommandHandler<'_> { async fn handle_foreign_network_list(&self) -> Result<(), Error> { let client = self.get_peer_manager_client().await?; - let request = ListForeignNetworkRequest::default(); + let request = ListForeignNetworkRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_foreign_network(BaseController::default(), request) .await?; @@ -724,7 +774,9 @@ impl CommandHandler<'_> { async fn handle_global_foreign_network_list(&self) -> Result<(), Error> { let client = self.get_peer_manager_client().await?; - let request = ListGlobalForeignNetworkRequest::default(); + let request = ListGlobalForeignNetworkRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_global_foreign_network(BaseController::default(), request) .await?; @@ -773,7 +825,12 @@ impl CommandHandler<'_> { let mut items: Vec = vec![]; let client = self.get_peer_manager_client().await?; let node_info = client - .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .show_node_info( + BaseController::default(), + ShowNodeInfoRequest { + instance: Some(self.instance_selector.clone()), + }, + ) .await? .node_info .ok_or(anyhow::anyhow!("node info not found"))?; @@ -895,7 +952,9 @@ impl CommandHandler<'_> { async fn handle_connector_list(&self) -> Result<(), Error> { let client = self.get_connector_manager_client().await?; - let request = ListConnectorRequest::default(); + let request = ListConnectorRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_connector(BaseController::default(), request) .await?; @@ -909,7 +968,9 @@ impl CommandHandler<'_> { async fn handle_acl_stats(&self) -> Result<(), Error> { let client = self.get_acl_manager_client().await?; - let request = GetAclStatsRequest::default(); + let request = GetAclStatsRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .get_acl_stats(BaseController::default(), request) .await?; @@ -929,7 +990,9 @@ impl CommandHandler<'_> { async fn handle_mapped_listener_list(&self) -> Result<(), Error> { let client = self.get_mapped_listener_manager_client().await?; - let request = ListMappedListenerRequest::default(); + let request = ListMappedListenerRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_mapped_listener(BaseController::default(), request) .await?; @@ -952,6 +1015,7 @@ impl CommandHandler<'_> { let url = Self::mapped_listener_validate_url(url)?; let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { mapped_listeners: vec![UrlPatch { action: action.into(), @@ -997,6 +1061,7 @@ impl CommandHandler<'_> { let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { port_forwards: vec![PortForwardPatch { action: action.into(), @@ -1024,7 +1089,9 @@ impl CommandHandler<'_> { async fn handle_port_forward_list(&self) -> Result<(), Error> { let client = self.get_port_forward_manager_client().await?; - let request = ListPortForwardRequest::default(); + let request = ListPortForwardRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .list_port_forward(BaseController::default(), request) .await?; @@ -1082,6 +1149,7 @@ impl CommandHandler<'_> { let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { acl: Some(AclPatch { tcp_whitelist: whitelist, @@ -1116,6 +1184,7 @@ impl CommandHandler<'_> { let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { acl: Some(AclPatch { udp_whitelist: whitelist, @@ -1136,6 +1205,7 @@ impl CommandHandler<'_> { let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { acl: Some(AclPatch { tcp_whitelist: vec![StringPatch { @@ -1159,6 +1229,7 @@ impl CommandHandler<'_> { let client = self.get_config_client().await?; let request = PatchConfigRequest { + instance: Some(self.instance_selector.clone()), patch: Some(InstanceConfigPatch { acl: Some(AclPatch { udp_whitelist: vec![StringPatch { @@ -1180,7 +1251,9 @@ impl CommandHandler<'_> { async fn handle_whitelist_show(&self) -> Result<(), Error> { let client = self.get_acl_manager_client().await?; - let request = GetWhitelistRequest::default(); + let request = GetWhitelistRequest { + instance: Some(self.instance_selector.clone()), + }; let response = client .get_whitelist(BaseController::default(), request) .await?; @@ -1213,7 +1286,7 @@ impl CommandHandler<'_> { async fn handle_logger_get(&self) -> Result<(), Error> { let client = self.get_logger_client().await?; - let request = GetLoggerConfigRequest {}; + let request = GetLoggerConfigRequest::default(); let response = client .get_logger_config(BaseController::default(), request) .await?; @@ -1601,6 +1674,7 @@ async fn main() -> Result<(), Error> { client: tokio::sync::Mutex::new(client), verbose: cli.verbose, output_format: &cli.output_format, + instance_selector: (&cli.instance_select).into(), }; match cli.sub_command { @@ -1700,7 +1774,12 @@ async fn main() -> Result<(), Error> { let node_info = handler .get_peer_manager_client() .await? - .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .show_node_info( + BaseController::default(), + ShowNodeInfoRequest { + instance: Some((&cli.instance_select).into()), + }, + ) .await? .node_info .ok_or(anyhow::anyhow!("node info not found"))?; @@ -1804,7 +1883,9 @@ async fn main() -> Result<(), Error> { let resp = vpn_portal_client .get_vpn_portal_info( BaseController::default(), - GetVpnPortalInfoRequest::default(), + GetVpnPortalInfoRequest { + instance: Some((&cli.instance_select).into()), + }, ) .await? .vpn_portal_info @@ -1823,7 +1904,12 @@ async fn main() -> Result<(), Error> { SubCommand::Node(sub_cmd) => { let client = handler.get_peer_manager_client().await?; let node_info = client - .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .show_node_info( + BaseController::default(), + ShowNodeInfoRequest { + instance: Some((&cli.instance_select).into()), + }, + ) .await? .node_info .ok_or(anyhow::anyhow!("node info not found"))?; @@ -2058,7 +2144,9 @@ async fn main() -> Result<(), Error> { SubCommand::Stats(stats_args) => match &stats_args.sub_command { Some(StatsSubCommand::Show) | None => { let client = handler.get_stats_client().await?; - let request = GetStatsRequest {}; + let request = GetStatsRequest { + instance: Some((&cli.instance_select).into()), + }; let response = client.get_stats(BaseController::default(), request).await?; if cli.output_format == OutputFormat::Json { @@ -2110,7 +2198,9 @@ async fn main() -> Result<(), Error> { } Some(StatsSubCommand::Prometheus) => { let client = handler.get_stats_client().await?; - let request = GetPrometheusStatsRequest {}; + let request = GetPrometheusStatsRequest { + instance: Some((&cli.instance_select).into()), + }; let response = client .get_prometheus_stats(BaseController::default(), request) .await?; diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 527c902..102b55e 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -30,6 +30,7 @@ use easytier::{ instance_manager::NetworkInstanceManager, launcher::{add_proxy_network_to_config, ConfigSource}, proto::common::{CompressionAlgoPb, NatType}, + rpc_service::ApiRpcServer, tunnel::{IpVersion, PROTO_PORT_OFFSET}, utils::{init_logger, setup_panic_handler}, web_client, @@ -130,6 +131,9 @@ struct Cli { #[command(flatten)] logging_options: LoggingOptions, + #[command(flatten)] + rpc_portal_options: RpcPortalOptions, + #[clap(long, help = t!("core_clap.generate_completions").to_string())] gen_autocomplete: Option, @@ -205,22 +209,6 @@ struct NetworkOptions { )] proxy_networks: Vec, - #[arg( - short, - long, - env = "ET_RPC_PORTAL", - help = t!("core_clap.rpc_portal").to_string(), - )] - rpc_portal: Option, - - #[arg( - long, - env = "ET_RPC_PORTAL_WHITELIST", - value_delimiter = ',', - help = t!("core_clap.rpc_portal_whitelist").to_string(), - )] - rpc_portal_whitelist: Option>, - #[arg( short, long, @@ -624,6 +612,25 @@ struct LoggingOptions { file_log_count: Option, } +#[derive(Parser, Debug)] +struct RpcPortalOptions { + #[arg( + short, + long, + env = "ET_RPC_PORTAL", + help = t!("core_clap.rpc_portal").to_string(), + )] + rpc_portal: Option, + + #[arg( + long, + env = "ET_RPC_PORTAL_WHITELIST", + value_delimiter = ',', + help = t!("core_clap.rpc_portal_whitelist").to_string(), + )] + rpc_portal_whitelist: Option>, +} + rust_i18n::i18n!("locales", fallback = "en"); impl Cli { @@ -671,14 +678,6 @@ impl Cli { Ok(listeners) } - - fn parse_rpc_portal(rpc_portal: String) -> anyhow::Result { - if let Ok(port) = rpc_portal.parse::() { - return Ok(format!("0.0.0.0:{}", port).parse().unwrap()); - } - - Ok(rpc_portal.parse()?) - } } impl NetworkOptions { @@ -786,24 +785,6 @@ impl NetworkOptions { add_proxy_network_to_config(n, cfg)?; } - let rpc_portal = if let Some(r) = &self.rpc_portal { - Cli::parse_rpc_portal(r.clone()) - .with_context(|| format!("failed to parse rpc portal: {}", r))? - } else if let Some(r) = cfg.get_rpc_portal() { - r - } else { - Cli::parse_rpc_portal("0".into())? - }; - cfg.set_rpc_portal(rpc_portal); - - if let Some(rpc_portal_whitelist) = &self.rpc_portal_whitelist { - let mut whitelist = cfg.get_rpc_portal_whitelist().unwrap_or_default(); - for cidr in rpc_portal_whitelist { - whitelist.push(*cidr); - } - cfg.set_rpc_portal_whitelist(Some(whitelist)); - } - if let Some(external_nodes) = self.external_node.as_ref() { let mut old_peers = cfg.get_peers(); old_peers.push(PeerConfig { @@ -1127,6 +1108,16 @@ fn win_service_main(arg: Vec) { async fn run_main(cli: Cli) -> anyhow::Result<()> { init_logger(&cli.logging_options, true)?; + let manager = Arc::new(NetworkInstanceManager::new()); + + let _rpc_server = ApiRpcServer::new( + cli.rpc_portal_options.rpc_portal, + cli.rpc_portal_options.rpc_portal_whitelist, + manager.clone(), + )? + .serve() + .await?; + if cli.config_server.is_some() { set_default_machine_id(cli.machine_id); let config_server_url_s = cli.config_server.clone().unwrap(); @@ -1175,11 +1166,11 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> { create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?, token.to_string(), hostname, + manager, ); tokio::signal::ctrl_c().await.unwrap(); return Ok(()); } - let manager = NetworkInstanceManager::new(); let mut crate_cli_network = cli.config_file.is_none() || cli.network_options.network_name.is_some(); if let Some(config_files) = cli.config_file { diff --git a/easytier/src/gateway/kcp_proxy.rs b/easytier/src/gateway/kcp_proxy.rs index 0d085b3..adaa7f6 100644 --- a/easytier/src/gateway/kcp_proxy.rs +++ b/easytier/src/gateway/kcp_proxy.rs @@ -40,7 +40,7 @@ use crate::{ peers::{acl_filter::AclFilter, peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter}, proto::{ acl::{Action, ChainType, Protocol}, - cli::{ + api::instance::{ ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState, TcpProxyEntryTransportType, TcpProxyRpc, }, diff --git a/easytier/src/gateway/quic_proxy.rs b/easytier/src/gateway/quic_proxy.rs index 8cd6c60..7ccca2a 100644 --- a/easytier/src/gateway/quic_proxy.rs +++ b/easytier/src/gateway/quic_proxy.rs @@ -22,7 +22,7 @@ use crate::gateway::tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy}; use crate::gateway::CidrSet; use crate::peers::peer_manager::PeerManager; use crate::proto::acl::{ChainType, Protocol}; -use crate::proto::cli::{ +use crate::proto::api::instance::{ ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState, TcpProxyEntryTransportType, TcpProxyRpc, }; diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index 1ead0cf..c609963 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -27,7 +27,7 @@ use crate::common::join_joinset_background; use crate::common::stats_manager::{LabelSet, LabelType, MetricName}; use crate::peers::peer_manager::PeerManager; use crate::peers::{NicPacketFilter, PeerPacketFilter}; -use crate::proto::cli::{ +use crate::proto::api::instance::{ ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState, TcpProxyEntryTransportType, TcpProxyRpc, }; diff --git a/easytier/src/instance/dns_server/client_instance.rs b/easytier/src/instance/dns_server/client_instance.rs index b27d6a8..eb172f8 100644 --- a/easytier/src/instance/dns_server/client_instance.rs +++ b/easytier/src/instance/dns_server/client_instance.rs @@ -5,7 +5,7 @@ use tokio::task::JoinSet; use crate::{ peers::peer_manager::PeerManager, proto::{ - cli::Route, + api::instance::Route, common::Void, magic_dns::{ HandshakeRequest, MagicDnsServerRpc, MagicDnsServerRpcClientFactory, diff --git a/easytier/src/instance/dns_server/server_instance.rs b/easytier/src/instance/dns_server/server_instance.rs index 68aa41e..9f450c0 100644 --- a/easytier/src/instance/dns_server/server_instance.rs +++ b/easytier/src/instance/dns_server/server_instance.rs @@ -24,7 +24,7 @@ use crate::{ }, peers::{peer_manager::PeerManager, NicPacketFilter}, proto::{ - cli::Route, + api::instance::Route, common::{TunnelInfo, Void}, magic_dns::{ dns_record::{self}, diff --git a/easytier/src/instance/dns_server/tests.rs b/easytier/src/instance/dns_server/tests.rs index e750323..a5ace89 100644 --- a/easytier/src/instance/dns_server/tests.rs +++ b/easytier/src/instance/dns_server/tests.rs @@ -21,7 +21,7 @@ use crate::instance::virtual_nic::NicCtx; use crate::peers::peer_manager::{PeerManager, RouteAlgoType}; use crate::peers::create_packet_recv_chan; -use crate::proto::cli::Route; +use crate::proto::api::instance::Route; use crate::proto::common::NatType; pub async fn prepare_env(dns_name: &str, tun_ip: Ipv4Inet) -> (Arc, NicCtx) { diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 41bea23..5fab30b 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -32,23 +32,22 @@ use crate::peers::peer_conn::PeerConnId; use crate::peers::peer_manager::{PeerManager, RouteAlgoType}; use crate::peers::rpc_service::PeerManagerRpcService; use crate::peers::{create_packet_recv_chan, recv_packet_from_chan, PacketRecvChanReceiver}; -use crate::proto::cli::VpnPortalRpc; -use crate::proto::cli::{ +use crate::proto::api::config::{ + ConfigPatchAction, ConfigRpc, PatchConfigRequest, PatchConfigResponse, PortForwardPatch, +}; +use crate::proto::api::instance::{ GetPrometheusStatsRequest, GetPrometheusStatsResponse, GetStatsRequest, GetStatsResponse, - ListMappedListenerRequest, ListMappedListenerResponse, ListPortForwardRequest, - ListPortForwardResponse, MappedListener, MappedListenerManageRpc, MetricSnapshot, - PortForwardManageRpc, StatsRpc, + GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, ListMappedListenerRequest, + ListMappedListenerResponse, ListPortForwardRequest, ListPortForwardResponse, MappedListener, + MappedListenerManageRpc, MetricSnapshot, PortForwardManageRpc, StatsRpc, VpnPortalInfo, + VpnPortalRpc, }; -use crate::proto::cli::{GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, VpnPortalInfo}; -use crate::proto::common::{PortForwardConfigPb, TunnelInfo, Void}; -use crate::proto::config::{ - ConfigPatchAction, ConfigRpc, ConfigRpcServer, PatchConfigRequest, PortForwardPatch, -}; -use crate::proto::peer_rpc::PeerCenterRpcServer; -use crate::proto::rpc_impl::standalone::{RpcServerHook, StandAloneServer}; +use crate::proto::common::{PortForwardConfigPb, TunnelInfo}; +use crate::proto::rpc_impl::standalone::RpcServerHook; use crate::proto::rpc_types; use crate::proto::rpc_types::controller::BaseController; -use crate::tunnel::tcp::TcpTunnelListener; +use crate::rpc_service::InstanceRpcService; +use crate::utils::weak_upgrade; use crate::vpn_portal::{self, VpnPortal}; use super::dns_server::runner::DnsRunner; @@ -229,15 +228,16 @@ impl RpcServerHook for InstanceRpcServerHook { #[derive(Clone)] pub struct InstanceConfigPatcher { - global_ctx: ArcGlobalCtx, + global_ctx: Weak, socks5_server: Weak, - peer_manager: Arc, + peer_manager: Weak, + conn_manager: Weak, } impl InstanceConfigPatcher { pub async fn apply_patch( &self, - patch: crate::proto::config::InstanceConfigPatch, + patch: crate::proto::api::config::InstanceConfigPatch, ) -> Result<(), anyhow::Error> { let patch_for_event = patch.clone(); @@ -247,27 +247,32 @@ impl InstanceConfigPatcher { self.patch_routes(patch.routes).await?; self.patch_exit_nodes(patch.exit_nodes).await?; self.patch_mapped_listeners(patch.mapped_listeners).await?; + self.patch_connector(patch.connectors).await?; + + let global_ctx = weak_upgrade(&self.global_ctx)?; if let Some(hostname) = patch.hostname { - self.global_ctx.set_hostname(hostname.clone()); - self.global_ctx.config.set_hostname(Some(hostname)); + global_ctx.set_hostname(hostname.clone()); + global_ctx.config.set_hostname(Some(hostname)); } if let Some(ipv4) = patch.ipv4 { - if !self.global_ctx.config.get_dhcp() { - self.global_ctx.set_ipv4(Some(ipv4.into())); - self.global_ctx.config.set_ipv4(Some(ipv4.into())); + if !global_ctx.config.get_dhcp() { + global_ctx.set_ipv4(Some(ipv4.into())); + global_ctx.config.set_ipv4(Some(ipv4.into())); } } if let Some(ipv6) = patch.ipv6 { - self.global_ctx.set_ipv6(Some(ipv6.into())); - self.global_ctx.config.set_ipv6(Some(ipv6.into())); + global_ctx.set_ipv6(Some(ipv6.into())); + global_ctx.config.set_ipv6(Some(ipv6.into())); } - self.global_ctx - .issue_event(GlobalCtxEvent::ConfigPatched(patch_for_event)); + + global_ctx.issue_event(GlobalCtxEvent::ConfigPatched(patch_for_event)); Ok(()) } - fn trace_patchables(patches: &Vec>) { + fn trace_patchables( + patches: &Vec>, + ) { for patch in patches { match patch.action { Some(ConfigPatchAction::Add) | Some(ConfigPatchAction::Remove) => { @@ -304,13 +309,14 @@ impl InstanceConfigPatcher { let Some(socks5_server) = self.socks5_server.upgrade() else { return Err(anyhow::anyhow!("socks5 server not available")); }; + let global_ctx = weak_upgrade(&self.global_ctx)?; - let mut current_forwards = self.global_ctx.config.get_port_forwards(); + let mut current_forwards = global_ctx.config.get_port_forwards(); let patches = port_forwards.into_iter().map(Into::into).collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_forwards, patches); + crate::proto::api::config::patch_vec(&mut current_forwards, patches); - self.global_ctx + global_ctx .config .set_port_forwards(current_forwards.clone()); socks5_server @@ -323,49 +329,51 @@ impl InstanceConfigPatcher { async fn patch_acl( &self, - acl_patch: Option, + acl_patch: Option, ) -> Result<(), anyhow::Error> { let Some(acl_patch) = acl_patch else { return Ok(()); }; + let global_ctx = weak_upgrade(&self.global_ctx)?; if let Some(acl) = acl_patch.acl { - self.global_ctx.config.set_acl(Some(acl)); + global_ctx.config.set_acl(Some(acl)); } if !acl_patch.tcp_whitelist.is_empty() { - let mut current_whitelist = self.global_ctx.config.get_tcp_whitelist(); + let mut current_whitelist = global_ctx.config.get_tcp_whitelist(); let patches = acl_patch .tcp_whitelist .into_iter() .map(Into::into) .collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_whitelist, patches); - self.global_ctx.config.set_tcp_whitelist(current_whitelist); + crate::proto::api::config::patch_vec(&mut current_whitelist, patches); + global_ctx.config.set_tcp_whitelist(current_whitelist); } if !acl_patch.udp_whitelist.is_empty() { - let mut current_whitelist = self.global_ctx.config.get_udp_whitelist(); + let mut current_whitelist = global_ctx.config.get_udp_whitelist(); let patches = acl_patch .udp_whitelist .into_iter() .map(Into::into) .collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_whitelist, patches); - self.global_ctx.config.set_udp_whitelist(current_whitelist); + crate::proto::api::config::patch_vec(&mut current_whitelist, patches); + global_ctx.config.set_udp_whitelist(current_whitelist); } - self.global_ctx + global_ctx .get_acl_filter() - .reload_rules(AclRuleBuilder::build(&self.global_ctx)?.as_ref()); + .reload_rules(AclRuleBuilder::build(&global_ctx)?.as_ref()); Ok(()) } async fn patch_proxy_networks( &self, - proxy_networks: Vec, + proxy_networks: Vec, ) -> Result<(), anyhow::Error> { if proxy_networks.is_empty() { return Ok(()); } + let global_ctx = weak_upgrade(&self.global_ctx)?; for proxy_network_patch in proxy_networks { let Some(cidr) = proxy_network_patch.cidr.map(|c| c.into()) else { tracing::warn!("Proxy network cidr is None, skipping."); @@ -376,15 +384,15 @@ impl InstanceConfigPatcher { match ConfigPatchAction::try_from(proxy_network_patch.action) { Ok(ConfigPatchAction::Add) => { tracing::info!("Proxy network added: {}", cidr); - self.global_ctx.config.add_proxy_cidr(cidr, mapped_cidr)?; + global_ctx.config.add_proxy_cidr(cidr, mapped_cidr)?; } Ok(ConfigPatchAction::Remove) => { tracing::info!("Proxy network removed: {}", cidr); - self.global_ctx.config.remove_proxy_cidr(cidr); + global_ctx.config.remove_proxy_cidr(cidr); } Ok(ConfigPatchAction::Clear) => { tracing::info!("Proxy networks cleared."); - self.global_ctx.config.clear_proxy_cidrs(); + global_ctx.config.clear_proxy_cidrs(); } Err(_) => { tracing::warn!( @@ -399,60 +407,98 @@ impl InstanceConfigPatcher { async fn patch_routes( &self, - routes: Vec, + routes: Vec, ) -> Result<(), anyhow::Error> { if routes.is_empty() { return Ok(()); } - let mut current_routes = self.global_ctx.config.get_routes().unwrap_or_default(); + let global_ctx = weak_upgrade(&self.global_ctx)?; + let mut current_routes = global_ctx.config.get_routes().unwrap_or_default(); let patches = routes.into_iter().map(Into::into).collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_routes, patches); + crate::proto::api::config::patch_vec(&mut current_routes, patches); if current_routes.is_empty() { - self.global_ctx.config.set_routes(None); + global_ctx.config.set_routes(None); } else { - self.global_ctx.config.set_routes(Some(current_routes)); + global_ctx.config.set_routes(Some(current_routes)); } Ok(()) } async fn patch_exit_nodes( &self, - exit_nodes: Vec, + exit_nodes: Vec, ) -> Result<(), anyhow::Error> { if exit_nodes.is_empty() { return Ok(()); } - let mut current_exit_nodes = self.global_ctx.config.get_exit_nodes(); + let global_ctx = weak_upgrade(&self.global_ctx)?; + let peer_manager = weak_upgrade(&self.peer_manager)?; + let mut current_exit_nodes = global_ctx.config.get_exit_nodes(); let patches = exit_nodes.into_iter().map(Into::into).collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_exit_nodes, patches); - self.global_ctx.config.set_exit_nodes(current_exit_nodes); - self.peer_manager.update_exit_nodes().await; + crate::proto::api::config::patch_vec(&mut current_exit_nodes, patches); + global_ctx.config.set_exit_nodes(current_exit_nodes); + peer_manager.update_exit_nodes().await; Ok(()) } async fn patch_mapped_listeners( &self, - mapped_listeners: Vec, + mapped_listeners: Vec, ) -> Result<(), anyhow::Error> { if mapped_listeners.is_empty() { return Ok(()); } - let mut current_mapped_listeners = self.global_ctx.config.get_mapped_listeners(); + let global_ctx = weak_upgrade(&self.global_ctx)?; + let mut current_mapped_listeners = global_ctx.config.get_mapped_listeners(); let patches = mapped_listeners.into_iter().map(Into::into).collect(); InstanceConfigPatcher::trace_patchables(&patches); - crate::proto::config::patch_vec(&mut current_mapped_listeners, patches); + crate::proto::api::config::patch_vec(&mut current_mapped_listeners, patches); if current_mapped_listeners.is_empty() { - self.global_ctx.config.set_mapped_listeners(None); + global_ctx.config.set_mapped_listeners(None); } else { - self.global_ctx + global_ctx .config .set_mapped_listeners(Some(current_mapped_listeners)); } Ok(()) } + + async fn patch_connector( + &self, + connectors: Vec, + ) -> Result<(), anyhow::Error> { + if connectors.is_empty() { + return Ok(()); + } + let conn_manager = weak_upgrade(&self.conn_manager)?; + for connector in connectors { + let Some(url) = connector.url.map(Into::::into) else { + tracing::warn!("Connector url is None, skipping."); + return Ok(()); + }; + match ConfigPatchAction::try_from(connector.action) { + Ok(ConfigPatchAction::Add) => { + tracing::info!("Connector added: {}", url); + conn_manager.add_connector_by_url(url.as_str()).await?; + } + Ok(ConfigPatchAction::Remove) => { + tracing::info!("Connector removed: {}", url); + conn_manager.remove_connector(url).await?; + } + Ok(ConfigPatchAction::Clear) => { + tracing::info!("Connectors cleared."); + conn_manager.clear_connectors().await; + } + Err(_) => { + tracing::warn!("Invalid connector action: {}", connector.action); + } + } + } + Ok(()) + } } pub struct Instance { @@ -484,8 +530,6 @@ pub struct Instance { #[cfg(feature = "socks5")] socks5_server: Arc, - rpc_server: Option>, - global_ctx: ArcGlobalCtx, } @@ -536,12 +580,6 @@ impl Instance { #[cfg(feature = "socks5")] let socks5_server = Socks5Server::new(global_ctx.clone(), peer_manager.clone(), None); - let rpc_server = global_ctx.config.get_rpc_portal().map(|s| { - StandAloneServer::new(TcpTunnelListener::new( - format!("tcp://{}", s).parse().unwrap(), - )) - }); - Instance { inst_name: global_ctx.inst_name.clone(), id, @@ -569,8 +607,6 @@ impl Instance { #[cfg(feature = "socks5")] socks5_server, - rpc_server, - global_ctx, } } @@ -934,8 +970,6 @@ impl Instance { ) .await?; - self.run_rpc_server().await?; - Ok(()) } @@ -1034,7 +1068,7 @@ impl Instance { &self, ) -> impl MappedListenerManageRpc + Clone { #[derive(Clone)] - pub struct MappedListenerManagerRpcService(Arc); + pub struct MappedListenerManagerRpcService(Weak); #[async_trait::async_trait] impl MappedListenerManageRpc for MappedListenerManagerRpcService { @@ -1046,7 +1080,7 @@ impl Instance { _request: ListMappedListenerRequest, ) -> Result { let mut ret = ListMappedListenerResponse::default(); - let urls = self.0.config.get_mapped_listeners(); + let urls = weak_upgrade(&self.0)?.config.get_mapped_listeners(); let mapped_listeners: Vec = urls .into_iter() .map(|u| MappedListener { @@ -1058,7 +1092,7 @@ impl Instance { } } - MappedListenerManagerRpcService(self.global_ctx.clone()) + MappedListenerManagerRpcService(Arc::downgrade(&self.global_ctx)) } fn get_port_forward_manager_rpc_service( @@ -1066,7 +1100,7 @@ impl Instance { ) -> impl PortForwardManageRpc + Clone { #[derive(Clone)] pub struct PortForwardManagerRpcService { - global_ctx: ArcGlobalCtx, + global_ctx: Weak, socks5_server: Weak, } @@ -1079,14 +1113,14 @@ impl Instance { _: BaseController, _request: ListPortForwardRequest, ) -> Result { - let forwards = self.global_ctx.config.get_port_forwards(); + let forwards = weak_upgrade(&self.global_ctx)?.config.get_port_forwards(); let cfgs: Vec = forwards.into_iter().map(Into::into).collect(); Ok(ListPortForwardResponse { cfgs }) } } PortForwardManagerRpcService { - global_ctx: self.global_ctx.clone(), + global_ctx: Arc::downgrade(&self.global_ctx), socks5_server: Arc::downgrade(&self.socks5_server), } } @@ -1094,7 +1128,7 @@ impl Instance { fn get_stats_rpc_service(&self) -> impl StatsRpc + Clone { #[derive(Clone)] pub struct StatsRpcService { - global_ctx: ArcGlobalCtx, + global_ctx: Weak, } #[async_trait::async_trait] @@ -1106,8 +1140,9 @@ impl Instance { _: BaseController, _request: GetStatsRequest, ) -> Result { - let stats_manager = self.global_ctx.stats_manager(); - let snapshots = stats_manager.get_all_metrics(); + let snapshots = weak_upgrade(&self.global_ctx)? + .stats_manager() + .get_all_metrics(); let metrics = snapshots .into_iter() @@ -1133,23 +1168,25 @@ impl Instance { _: BaseController, _request: GetPrometheusStatsRequest, ) -> Result { - let stats_manager = self.global_ctx.stats_manager(); - let prometheus_text = stats_manager.export_prometheus(); + let prometheus_text = weak_upgrade(&self.global_ctx)? + .stats_manager() + .export_prometheus(); Ok(GetPrometheusStatsResponse { prometheus_text }) } } StatsRpcService { - global_ctx: self.global_ctx.clone(), + global_ctx: Arc::downgrade(&self.global_ctx), } } pub fn get_config_patcher(&self) -> InstanceConfigPatcher { InstanceConfigPatcher { - global_ctx: self.global_ctx.clone(), + global_ctx: Arc::downgrade(&self.global_ctx), socks5_server: Arc::downgrade(&self.socks5_server), - peer_manager: self.peer_manager.clone(), + peer_manager: Arc::downgrade(&self.peer_manager), + conn_manager: Arc::downgrade(&self.conn_manager), } } @@ -1167,13 +1204,13 @@ impl Instance { &self, _: Self::Controller, request: PatchConfigRequest, - ) -> crate::proto::rpc_types::error::Result { + ) -> crate::proto::rpc_types::error::Result { let Some(patch) = request.patch else { - return Ok(Void::default()); + return Ok(PatchConfigResponse::default()); }; self.patcher.apply_patch(patch).await?; - Ok(Void::default()) + Ok(PatchConfigResponse::default()) } } @@ -1182,97 +1219,140 @@ impl Instance { } } - async fn run_rpc_server(&mut self) -> Result<(), Error> { - let Some(_) = self.global_ctx.config.get_rpc_portal() else { - tracing::info!("rpc server not enabled, because rpc_portal is not set."); - return Ok(()); - }; + pub fn get_api_rpc_service(&self) -> impl InstanceRpcService { + use crate::proto::api::instance::*; - use crate::instance::logger_rpc_service::LoggerRpcService; - use crate::proto::cli::*; - - let peer_mgr = self.peer_manager.clone(); - let conn_manager = self.conn_manager.clone(); - let peer_center = self.peer_center.clone(); - let vpn_portal_rpc = self.get_vpn_portal_rpc_service(); - let mapped_listener_manager_rpc = self.get_mapped_listener_manager_rpc_service(); - let port_forward_manager_rpc = self.get_port_forward_manager_rpc_service(); - let stats_rpc_service = self.get_stats_rpc_service(); - let logger_rpc_service = LoggerRpcService::new(); - let config_rpc_service = self.get_config_service(); - - let s = self.rpc_server.as_mut().unwrap(); - let peer_mgr_rpc_service = PeerManagerRpcService::new(peer_mgr.clone()); - s.registry() - .register(PeerManageRpcServer::new(peer_mgr_rpc_service.clone()), ""); - s.registry() - .register(AclManageRpcServer::new(peer_mgr_rpc_service), ""); - s.registry().register( - ConnectorManageRpcServer::new(ConnectorManagerRpcService(conn_manager)), - "", - ); - - s.registry() - .register(PeerCenterRpcServer::new(peer_center.get_rpc_service()), ""); - s.registry() - .register(VpnPortalRpcServer::new(vpn_portal_rpc), ""); - s.registry().register( - MappedListenerManageRpcServer::new(mapped_listener_manager_rpc), - "", - ); - s.registry().register( - PortForwardManageRpcServer::new(port_forward_manager_rpc), - "", - ); - s.registry().register( - crate::proto::cli::StatsRpcServer::new(stats_rpc_service), - "", - ); - s.registry() - .register(LoggerRpcServer::new(logger_rpc_service), ""); - s.registry() - .register(ConfigRpcServer::new(config_rpc_service), ""); - - if let Some(ip_proxy) = self.ip_proxy.as_ref() { - s.registry().register( - TcpProxyRpcServer::new(TcpProxyRpcService::new(ip_proxy.tcp_proxy.clone())), - "tcp", - ); - } - if let Some(kcp_proxy) = self.kcp_proxy_src.as_ref() { - s.registry().register( - TcpProxyRpcServer::new(TcpProxyRpcService::new(kcp_proxy.get_tcp_proxy())), - "kcp_src", - ); + #[derive(Clone)] + struct ApiRpcServiceImpl { + peer_mgr_rpc_service: A, + connector_mgr_rpc_service: B, + mapped_listener_mgr_rpc_service: C, + vpn_portal_rpc_service: D, + tcp_proxy_rpc_services: dashmap::DashMap< + String, + Arc + Send + Sync>, + >, + acl_manage_rpc_service: E, + port_forward_manage_rpc_service: F, + stats_rpc_service: G, + config_rpc_service: H, } - if let Some(kcp_proxy) = self.kcp_proxy_dst.as_ref() { - s.registry().register( - TcpProxyRpcServer::new(KcpProxyDstRpcService::new(kcp_proxy)), - "kcp_dst", - ); + #[async_trait::async_trait] + impl< + A: PeerManageRpc + Send + Sync, + B: ConnectorManageRpc + Send + Sync, + C: MappedListenerManageRpc + Send + Sync, + D: VpnPortalRpc + Send + Sync, + E: AclManageRpc + Send + Sync, + F: PortForwardManageRpc + Send + Sync, + G: StatsRpc + Send + Sync, + H: ConfigRpc + Send + Sync, + > InstanceRpcService for ApiRpcServiceImpl + { + fn get_peer_manage_service(&self) -> &dyn PeerManageRpc { + &self.peer_mgr_rpc_service + } + + fn get_connector_manage_service( + &self, + ) -> &dyn ConnectorManageRpc { + &self.connector_mgr_rpc_service + } + + fn get_mapped_listener_manage_service( + &self, + ) -> &dyn MappedListenerManageRpc { + &self.mapped_listener_mgr_rpc_service + } + + fn get_vpn_portal_service(&self) -> &dyn VpnPortalRpc { + &self.vpn_portal_rpc_service + } + + fn get_proxy_service( + &self, + client_type: &str, + ) -> Option + Send + Sync>> + { + self.tcp_proxy_rpc_services + .get(client_type) + .map(|e| e.clone()) + } + + fn get_acl_manage_service(&self) -> &dyn AclManageRpc { + &self.acl_manage_rpc_service + } + + fn get_port_forward_manage_service( + &self, + ) -> &dyn PortForwardManageRpc { + &self.port_forward_manage_rpc_service + } + + fn get_stats_service(&self) -> &dyn StatsRpc { + &self.stats_rpc_service + } + + fn get_config_service(&self) -> &dyn ConfigRpc { + &self.config_rpc_service + } } - if let Some(quic_proxy) = self.quic_proxy_src.as_ref() { - s.registry().register( - TcpProxyRpcServer::new(TcpProxyRpcService::new(quic_proxy.get_tcp_proxy())), - "quic_src", - ); + ApiRpcServiceImpl { + peer_mgr_rpc_service: PeerManagerRpcService::new(self.peer_manager.clone()), + connector_mgr_rpc_service: ConnectorManagerRpcService(Arc::downgrade( + &self.conn_manager, + )), + mapped_listener_mgr_rpc_service: self.get_mapped_listener_manager_rpc_service(), + vpn_portal_rpc_service: self.get_vpn_portal_rpc_service(), + tcp_proxy_rpc_services: { + let tcp_proxy_rpc_services: dashmap::DashMap< + String, + Arc + Send + Sync>, + > = dashmap::DashMap::new(); + + if let Some(ip_proxy) = self.ip_proxy.as_ref() { + tcp_proxy_rpc_services.insert( + "tcp".to_string(), + Arc::new(TcpProxyRpcService::new(ip_proxy.tcp_proxy.clone())), + ); + } + if let Some(kcp_proxy) = self.kcp_proxy_src.as_ref() { + tcp_proxy_rpc_services.insert( + "kcp_src".to_string(), + Arc::new(TcpProxyRpcService::new(kcp_proxy.get_tcp_proxy())), + ); + } + + if let Some(kcp_proxy) = self.kcp_proxy_dst.as_ref() { + tcp_proxy_rpc_services.insert( + "kcp_dst".to_string(), + Arc::new(KcpProxyDstRpcService::new(kcp_proxy)), + ); + } + + if let Some(quic_proxy) = self.quic_proxy_src.as_ref() { + tcp_proxy_rpc_services.insert( + "quic_src".to_string(), + Arc::new(TcpProxyRpcService::new(quic_proxy.get_tcp_proxy())), + ); + } + + if let Some(quic_proxy) = self.quic_proxy_dst.as_ref() { + tcp_proxy_rpc_services.insert( + "quic_dst".to_string(), + Arc::new(QUICProxyDstRpcService::new(quic_proxy)), + ); + } + + tcp_proxy_rpc_services + }, + acl_manage_rpc_service: PeerManagerRpcService::new(self.peer_manager.clone()), + port_forward_manage_rpc_service: self.get_port_forward_manager_rpc_service(), + stats_rpc_service: self.get_stats_rpc_service(), + config_rpc_service: self.get_config_service(), } - - if let Some(quic_proxy) = self.quic_proxy_dst.as_ref() { - s.registry().register( - TcpProxyRpcServer::new(QUICProxyDstRpcService::new(quic_proxy)), - "quic_dst", - ); - } - - s.set_hook(Arc::new(InstanceRpcServerHook::new( - self.global_ctx.config.get_rpc_portal_whitelist(), - ))); - - let _g = self.global_ctx.net_ns.guard(); - Ok(s.serve().await.with_context(|| "rpc server start failed")?) } pub fn get_global_ctx(&self) -> ArcGlobalCtx { @@ -1328,9 +1408,6 @@ impl Instance { pub async fn clear_resources(&mut self) { self.peer_manager.clear_resources().await; let _ = self.nic_ctx.lock().await.take(); - if let Some(rpc_server) = self.rpc_server.take() { - rpc_server.registry().unregister_all(); - }; } } @@ -1339,9 +1416,6 @@ impl Drop for Instance { let my_peer_id = self.peer_manager.my_peer_id(); let pm = Arc::downgrade(&self.peer_manager); let nic_ctx = self.nic_ctx.clone(); - if let Some(rpc_server) = self.rpc_server.take() { - rpc_server.registry().unregister_all(); - }; tokio::spawn(async move { nic_ctx.lock().await.take(); if let Some(pm) = pm.upgrade() { diff --git a/easytier/src/instance/mod.rs b/easytier/src/instance/mod.rs index 8575b73..82918ff 100644 --- a/easytier/src/instance/mod.rs +++ b/easytier/src/instance/mod.rs @@ -6,5 +6,3 @@ pub mod listeners; #[cfg(feature = "tun")] pub mod virtual_nic; - -pub mod logger_rpc_service; diff --git a/easytier/src/instance_manager.rs b/easytier/src/instance_manager.rs index 7b2a640..cc86761 100644 --- a/easytier/src/instance_manager.rs +++ b/easytier/src/instance_manager.rs @@ -9,7 +9,8 @@ use crate::{ scoped_task::ScopedTask, }, launcher::{ConfigSource, NetworkInstance, NetworkInstanceRunningInfo}, - proto, + proto::{self}, + rpc_service::InstanceRpcService, }; pub struct NetworkInstanceManager { @@ -150,6 +151,26 @@ impl NetworkInstanceManager { .map(|instance| instance.value().get_inst_name()) } + pub fn filter_network_instance( + &self, + filter: impl Fn(&uuid::Uuid, &NetworkInstance) -> bool, + ) -> Vec { + self.instance_map + .iter() + .filter(|item| filter(item.key(), item.value())) + .map(|item| *item.key()) + .collect() + } + + pub fn get_instance_service( + &self, + instance_id: &uuid::Uuid, + ) -> Option> { + self.instance_map + .get(instance_id) + .and_then(|instance| instance.value().get_api_service()) + } + pub fn set_tun_fd(&self, instance_id: &uuid::Uuid, fd: i32) -> Result<(), anyhow::Error> { let mut instance = self .instance_map @@ -340,7 +361,7 @@ fn print_event(instance_id: uuid::Uuid, msg: String) { ); } -fn peer_conn_info_to_string(p: proto::cli::PeerConnInfo) -> String { +fn peer_conn_info_to_string(p: proto::api::instance::PeerConnInfo) -> String { format!( "my_peer_id: {}, dst_peer_id: {}, tunnel_info: {:?}", p.my_peer_id, p.peer_id, p.tunnel diff --git a/easytier/src/launcher.rs b/easytier/src/launcher.rs index 5304286..60ecbb1 100644 --- a/easytier/src/launcher.rs +++ b/easytier/src/launcher.rs @@ -1,6 +1,7 @@ use crate::common::config::PortForwardConfig; +use crate::proto::api::manage; use crate::proto::peer_rpc::RouteForeignNetworkSummary; -use crate::proto::web; +use crate::rpc_service::InstanceRpcService; use crate::{ common::{ config::{ @@ -13,7 +14,7 @@ use crate::{ }, instance::instance::Instance, peers::rpc_service::PeerManagerRpcService, - proto::cli::{list_peer_route_pair, PeerInfo, Route}, + proto::api::instance::{list_peer_route_pair, PeerInfo, Route}, }; use anyhow::Context; use chrono::{DateTime, Local}; @@ -24,7 +25,9 @@ use std::{ }; use tokio::{sync::broadcast, task::JoinSet}; -pub type MyNodeInfo = crate::proto::web::MyNodeInfo; +pub type MyNodeInfo = crate::proto::api::manage::MyNodeInfo; + +type ArcMutApiService = Arc>>>; #[derive(serde::Serialize, Clone)] pub struct Event { @@ -65,6 +68,7 @@ pub struct EasyTierLauncher { instance_alive: Arc, stop_flag: Arc, thread_handle: Option>, + api_service: ArcMutApiService, running_cfg: String, fetch_node_info: bool, @@ -78,6 +82,7 @@ impl EasyTierLauncher { Self { instance_alive, thread_handle: None, + api_service: Arc::new(RwLock::new(None)), error_msg: Arc::new(RwLock::new(None)), running_cfg: String::new(), fetch_node_info, @@ -136,12 +141,19 @@ impl EasyTierLauncher { async fn easytier_routine( cfg: TomlConfigLoader, stop_signal: Arc, + api_service: ArcMutApiService, data: Arc, fetch_node_info: bool, ) -> Result<(), anyhow::Error> { let mut instance = Instance::new(cfg); let mut tasks = JoinSet::new(); + api_service + .write() + .unwrap() + .replace(Arc::new(instance.get_api_rpc_service())); + drop(api_service); + // Subscribe to global context events let global_ctx = instance.get_global_ctx(); let data_c = data.clone(); @@ -220,21 +232,6 @@ impl EasyTierLauncher { Ok(()) } - fn select_proper_rpc_port(cfg: &TomlConfigLoader) { - let Some(mut f) = cfg.get_rpc_portal() else { - return; - }; - - if f.port() == 0 { - let Some(port) = crate::utils::find_free_tcp_port(15888..15900) else { - tracing::warn!("No free port found for RPC portal, skipping setting RPC portal"); - return; - }; - f.set_port(port); - cfg.set_rpc_portal(f); - } - } - pub fn start(&mut self, cfg_generator: F) where F: FnOnce() -> Result + Send + Sync, @@ -250,8 +247,6 @@ impl EasyTierLauncher { self.running_cfg = cfg.dump(); - Self::select_proper_rpc_port(&cfg); - let stop_flag = self.stop_flag.clone(); let instance_alive = self.instance_alive.clone(); @@ -259,6 +254,7 @@ impl EasyTierLauncher { let data = self.data.clone(); let fetch_node_info = self.fetch_node_info; + let api_service = self.api_service.clone(); self.thread_handle = Some(std::thread::spawn(move || { let rt = if cfg.get_flags().multi_thread { @@ -288,6 +284,7 @@ impl EasyTierLauncher { let ret = rt.block_on(Self::easytier_routine( cfg, stop_notifier.clone(), + api_service, data, fetch_node_info, )); @@ -332,6 +329,16 @@ impl EasyTierLauncher { pub fn get_foreign_network_summary(&self) -> RouteForeignNetworkSummary { self.data.foreign_network_summary.read().unwrap().clone() } + + pub fn get_api_service(&self) -> Option> { + match self.api_service.read() { + Ok(guard) => guard.clone(), + Err(e) => { + tracing::error!("Failed to acquire read lock for api_service: {:?}", e); + None + } + } + } } impl Drop for EasyTierLauncher { @@ -346,7 +353,7 @@ impl Drop for EasyTierLauncher { } } -pub type NetworkInstanceRunningInfo = crate::proto::web::NetworkInstanceRunningInfo; +pub type NetworkInstanceRunningInfo = crate::proto::api::manage::NetworkInstanceRunningInfo; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConfigSource { @@ -460,6 +467,12 @@ impl NetworkInstance { None } } + + pub fn get_api_service(&self) -> Option> { + self.launcher + .as_ref() + .and_then(|launcher| launcher.get_api_service()) + } } pub fn add_proxy_network_to_config( @@ -492,8 +505,8 @@ pub fn add_proxy_network_to_config( Ok(()) } -pub type NetworkingMethod = crate::proto::web::NetworkingMethod; -pub type NetworkConfig = crate::proto::web::NetworkConfig; +pub type NetworkingMethod = crate::proto::api::manage::NetworkingMethod; +pub type NetworkConfig = crate::proto::api::manage::NetworkConfig; impl NetworkConfig { pub fn gen_config(&self) -> Result { @@ -574,26 +587,6 @@ impl NetworkConfig { add_proxy_network_to_config(n, &cfg)?; } - cfg.set_rpc_portal( - format!("0.0.0.0:{}", self.rpc_port.unwrap_or_default()) - .parse() - .with_context(|| format!("failed to parse rpc portal port: {:?}", self.rpc_port))?, - ); - - if self.rpc_portal_whitelists.is_empty() { - cfg.set_rpc_portal_whitelist(None); - } else { - cfg.set_rpc_portal_whitelist(Some( - self.rpc_portal_whitelists - .iter() - .map(|s| { - s.parse() - .with_context(|| format!("failed to parse rpc portal whitelist: {}", s)) - }) - .collect::, _>>()?, - )); - } - if !self.port_forwards.is_empty() { cfg.set_port_forwards( self.port_forwards @@ -848,19 +841,11 @@ impl NetworkConfig { }) .collect(); - if let Some(rpc_portal) = config.get_rpc_portal() { - result.rpc_port = Some(rpc_portal.port() as i32); - } - - if let Some(whitelist) = config.get_rpc_portal_whitelist() { - result.rpc_portal_whitelists = whitelist.iter().map(|w| w.to_string()).collect(); - } - let port_forwards = config.get_port_forwards(); if !port_forwards.is_empty() { result.port_forwards = port_forwards .iter() - .map(|f| web::PortForwardConfig { + .map(|f| manage::PortForwardConfig { proto: f.proto.clone(), bind_ip: f.bind_addr.ip().to_string(), bind_port: f.bind_addr.port() as u32, @@ -949,7 +934,6 @@ mod tests { config.set_dhcp(false); config.set_inst_name("default".to_string()); config.set_listeners(vec![]); - config.set_rpc_portal(std::net::SocketAddr::from(([0, 0, 0, 0], 0))); config } @@ -1061,27 +1045,6 @@ mod tests { } } - if rng.gen_bool(0.8) { - let port = rng.gen_range(0..65535); - config.set_rpc_portal(std::net::SocketAddr::from(([0, 0, 0, 0], port))); - - if rng.gen_bool(0.6) { - let whitelist_count = rng.gen_range(1..3); - let mut whitelist = Vec::new(); - for _ in 0..whitelist_count { - let ip = Ipv4Addr::new( - rng.gen_range(1..254), - rng.gen_range(0..255), - rng.gen_range(0..255), - rng.gen_range(0..255), - ); - let cidr = format!("{}/32", ip); - whitelist.push(cidr.parse().unwrap()); - } - config.set_rpc_portal_whitelist(Some(whitelist)); - } - } - if rng.gen_bool(0.5) { let vpn_network = format!( "{}.{}.{}.0/{}", diff --git a/easytier/src/lib.rs b/easytier/src/lib.rs index ac99209..c390977 100644 --- a/easytier/src/lib.rs +++ b/easytier/src/lib.rs @@ -17,6 +17,7 @@ pub mod instance_manager; pub mod launcher; pub mod peers; pub mod proto; +pub mod rpc_service; pub mod tunnel; pub mod utils; pub mod web_client; diff --git a/easytier/src/peer_center/instance.rs b/easytier/src/peer_center/instance.rs index 430acad..90f88d0 100644 --- a/easytier/src/peer_center/instance.rs +++ b/easytier/src/peer_center/instance.rs @@ -39,7 +39,7 @@ pub trait PeerCenterPeerManagerTrait: Send + Sync + 'static { fn my_peer_id(&self) -> PeerId; fn get_global_ctx(&self) -> Arc; fn get_rpc_mgr(&self) -> Weak; - async fn list_routes(&self) -> Vec; + async fn list_routes(&self) -> Vec; } struct PeerCenterBase { @@ -426,7 +426,7 @@ impl PeerCenterPeerManagerTrait for PeerManager { Arc::downgrade(&self.get_peer_rpc_mgr()) } - async fn list_routes(&self) -> Vec { + async fn list_routes(&self) -> Vec { self.list_routes().await } } @@ -478,7 +478,7 @@ impl PeerCenterPeerManagerTrait for PeerMapWithPeerRpcManager { Arc::downgrade(&self.rpc_mgr) } - async fn list_routes(&self) -> Vec { + async fn list_routes(&self) -> Vec { self.peer_map.list_route_infos().await } } diff --git a/easytier/src/peer_center/mod.rs b/easytier/src/peer_center/mod.rs index f1e4ec7..690f207 100644 --- a/easytier/src/peer_center/mod.rs +++ b/easytier/src/peer_center/mod.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; -use crate::proto::cli::PeerInfo; +use crate::proto::api::instance::PeerInfo; use crate::proto::peer_rpc::{DirectConnectedPeerInfo, PeerInfoForGlobalMap}; pub mod instance; diff --git a/easytier/src/peers/foreign_network_manager.rs b/easytier/src/peers/foreign_network_manager.rs index 7504d2c..150ec3a 100644 --- a/easytier/src/peers/foreign_network_manager.rs +++ b/easytier/src/peers/foreign_network_manager.rs @@ -32,7 +32,7 @@ use crate::{ peer_center::instance::{PeerCenterInstance, PeerMapWithPeerRpcManager}, peers::route_trait::{Route, RouteInterface}, proto::{ - cli::{ForeignNetworkEntryPb, ListForeignNetworkResponse, PeerInfo}, + api::instance::{ForeignNetworkEntryPb, ListForeignNetworkResponse, PeerInfo}, common::LimiterConfig, peer_rpc::DirectConnectorRpcServer, }, diff --git a/easytier/src/peers/peer.rs b/easytier/src/peers/peer.rs index bdcb712..09ccd8d 100644 --- a/easytier/src/peers/peer.rs +++ b/easytier/src/peers/peer.rs @@ -11,7 +11,7 @@ use super::{ peer_conn::{PeerConn, PeerConnId}, PacketRecvChan, }; -use crate::{common::scoped_task::ScopedTask, proto::cli::PeerConnInfo}; +use crate::{common::scoped_task::ScopedTask, proto::api::instance::PeerConnInfo}; use crate::{ common::{ error::Error, diff --git a/easytier/src/peers/peer_conn.rs b/easytier/src/peers/peer_conn.rs index 5a45e46..fe5e702 100644 --- a/easytier/src/peers/peer_conn.rs +++ b/easytier/src/peers/peer_conn.rs @@ -32,7 +32,7 @@ use crate::{ PeerId, }, proto::{ - cli::{PeerConnInfo, PeerConnStats}, + api::instance::{PeerConnInfo, PeerConnStats}, common::TunnelInfo, peer_rpc::HandshakeRequest, }, diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index aa4fd00..8e8f554 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -36,7 +36,7 @@ use crate::{ PeerPacketFilter, }, proto::{ - cli::{ + api::instance::{ self, list_global_foreign_network_response::OneForeignNetwork, ListGlobalForeignNetworkResponse, }, @@ -920,7 +920,7 @@ impl PeerManager { } } - pub async fn list_routes(&self) -> Vec { + pub async fn list_routes(&self) -> Vec { self.get_route().list_routes().await } @@ -1305,8 +1305,8 @@ impl PeerManager { self.foreign_network_client.clone() } - pub async fn get_my_info(&self) -> cli::NodeInfo { - cli::NodeInfo { + pub async fn get_my_info(&self) -> instance::NodeInfo { + instance::NodeInfo { peer_id: self.my_peer_id, ipv4_addr: self .global_ctx diff --git a/easytier/src/peers/peer_map.rs b/easytier/src/peers/peer_map.rs index 4c900b2..7f49d46 100644 --- a/easytier/src/peers/peer_map.rs +++ b/easytier/src/peers/peer_map.rs @@ -14,7 +14,7 @@ use crate::{ PeerId, }, proto::{ - cli::{self, PeerConnInfo}, + api::instance::{self, PeerConnInfo}, peer_rpc::RoutePeerInfo, }, tunnel::{packet_def::ZCPacket, TunnelError}, @@ -336,7 +336,7 @@ impl PeerMap { route_map } - pub async fn list_route_infos(&self) -> Vec { + pub async fn list_route_infos(&self) -> Vec { if let Some(route) = self.routes.read().await.iter().next() { return route.list_routes().await; } diff --git a/easytier/src/peers/peer_ospf_route.rs b/easytier/src/peers/peer_ospf_route.rs index a3a7010..c0bf51c 100644 --- a/easytier/src/peers/peer_ospf_route.rs +++ b/easytier/src/peers/peer_ospf_route.rs @@ -193,7 +193,7 @@ impl RoutePeerInfo { } } -impl From for crate::proto::cli::Route { +impl From for crate::proto::api::instance::Route { fn from(val: RoutePeerInfo) -> Self { let network_length = if val.network_length == 0 { 24 @@ -201,7 +201,7 @@ impl From for crate::proto::cli::Route { val.network_length }; - crate::proto::cli::Route { + crate::proto::api::instance::Route { peer_id: val.peer_id, ipv4_addr: val.ipv4_addr.map(|ipv4_addr| Ipv4Inet { address: Some(ipv4_addr), @@ -2361,7 +2361,7 @@ impl Route for PeerRoute { .map(|x| x.next_hop_peer_id) } - async fn list_routes(&self) -> Vec { + async fn list_routes(&self) -> Vec { let route_table = &self.service_impl.route_table; let route_table_with_cost = &self.service_impl.route_table_with_cost; let mut routes = Vec::new(); @@ -2373,7 +2373,7 @@ impl Route for PeerRoute { continue; }; let next_hop_peer_latency_first = route_table_with_cost.get_next_hop(*item.key()); - let mut route: crate::proto::cli::Route = item.value().clone().into(); + let mut route: crate::proto::api::instance::Route = item.value().clone().into(); route.next_hop_peer_id = next_hop_peer.next_hop_peer_id; route.cost = next_hop_peer.path_len as i32; route.path_latency = next_hop_peer.path_latency; diff --git a/easytier/src/peers/route_trait.rs b/easytier/src/peers/route_trait.rs index 9e29915..ef9b99c 100644 --- a/easytier/src/peers/route_trait.rs +++ b/easytier/src/peers/route_trait.rs @@ -74,7 +74,7 @@ pub trait Route { self.get_next_hop(peer_id).await } - async fn list_routes(&self) -> Vec; + async fn list_routes(&self) -> Vec; async fn get_peer_id_by_ipv4(&self, _ipv4: &Ipv4Addr) -> Option { None @@ -161,7 +161,7 @@ impl Route for MockRoute { panic!("mock route") } - async fn list_routes(&self) -> Vec { + async fn list_routes(&self) -> Vec { panic!("mock route") } diff --git a/easytier/src/peers/rpc_service.rs b/easytier/src/peers/rpc_service.rs index 007c13f..dedfb1b 100644 --- a/easytier/src/peers/rpc_service.rs +++ b/easytier/src/peers/rpc_service.rs @@ -1,26 +1,34 @@ -use std::sync::Arc; +use std::{ + ops::Deref, + sync::{Arc, Weak}, +}; -use crate::proto::{ - cli::{ - AclManageRpc, DumpRouteRequest, DumpRouteResponse, GetAclStatsRequest, GetAclStatsResponse, - GetWhitelistRequest, GetWhitelistResponse, ListForeignNetworkRequest, - ListForeignNetworkResponse, ListGlobalForeignNetworkRequest, - ListGlobalForeignNetworkResponse, ListPeerRequest, ListPeerResponse, ListRouteRequest, - ListRouteResponse, PeerInfo, PeerManageRpc, ShowNodeInfoRequest, ShowNodeInfoResponse, +use crate::{ + proto::{ + api::instance::{ + AclManageRpc, DumpRouteRequest, DumpRouteResponse, GetAclStatsRequest, + GetAclStatsResponse, GetWhitelistRequest, GetWhitelistResponse, + ListForeignNetworkRequest, ListForeignNetworkResponse, ListGlobalForeignNetworkRequest, + ListGlobalForeignNetworkResponse, ListPeerRequest, ListPeerResponse, ListRouteRequest, + ListRouteResponse, PeerInfo, PeerManageRpc, ShowNodeInfoRequest, ShowNodeInfoResponse, + }, + rpc_types::{self, controller::BaseController}, }, - rpc_types::{self, controller::BaseController}, + utils::weak_upgrade, }; use super::peer_manager::PeerManager; #[derive(Clone)] pub struct PeerManagerRpcService { - peer_manager: Arc, + peer_manager: Weak, } impl PeerManagerRpcService { pub fn new(peer_manager: Arc) -> Self { - PeerManagerRpcService { peer_manager } + PeerManagerRpcService { + peer_manager: Arc::downgrade(&peer_manager), + } } pub async fn list_peers(peer_manager: &PeerManager) -> Vec { @@ -78,7 +86,8 @@ impl PeerManageRpc for PeerManagerRpcService { ) -> Result { let mut reply = ListPeerResponse::default(); - let peers = PeerManagerRpcService::list_peers(&self.peer_manager).await; + let peers = + PeerManagerRpcService::list_peers(weak_upgrade(&self.peer_manager)?.deref()).await; for peer in peers { reply.peer_infos.push(peer); } @@ -92,7 +101,7 @@ impl PeerManageRpc for PeerManagerRpcService { _request: ListRouteRequest, // Accept request of type HelloRequest ) -> Result { let reply = ListRouteResponse { - routes: self.peer_manager.list_routes().await, + routes: weak_upgrade(&self.peer_manager)?.list_routes().await, }; Ok(reply) } @@ -103,7 +112,7 @@ impl PeerManageRpc for PeerManagerRpcService { _request: DumpRouteRequest, // Accept request of type HelloRequest ) -> Result { let reply = DumpRouteResponse { - result: self.peer_manager.dump_route().await, + result: weak_upgrade(&self.peer_manager)?.dump_route().await, }; Ok(reply) } @@ -113,8 +122,7 @@ impl PeerManageRpc for PeerManagerRpcService { _: BaseController, _request: ListForeignNetworkRequest, // Accept request of type HelloRequest ) -> Result { - let reply = self - .peer_manager + let reply = weak_upgrade(&self.peer_manager)? .get_foreign_network_manager() .list_foreign_networks() .await; @@ -126,7 +134,9 @@ impl PeerManageRpc for PeerManagerRpcService { _: BaseController, _request: ListGlobalForeignNetworkRequest, ) -> Result { - Ok(self.peer_manager.list_global_foreign_network().await) + Ok(weak_upgrade(&self.peer_manager)? + .list_global_foreign_network() + .await) } async fn show_node_info( @@ -135,7 +145,7 @@ impl PeerManageRpc for PeerManagerRpcService { _request: ShowNodeInfoRequest, // Accept request of type HelloRequest ) -> Result { Ok(ShowNodeInfoResponse { - node_info: Some(self.peer_manager.get_my_info().await), + node_info: Some(weak_upgrade(&self.peer_manager)?.get_my_info().await), }) } } @@ -149,8 +159,7 @@ impl AclManageRpc for PeerManagerRpcService { _: BaseController, _request: GetAclStatsRequest, ) -> Result { - let acl_stats = self - .peer_manager + let acl_stats = weak_upgrade(&self.peer_manager)? .get_global_ctx() .get_acl_filter() .get_stats(); @@ -164,7 +173,7 @@ impl AclManageRpc for PeerManagerRpcService { _: BaseController, _request: GetWhitelistRequest, ) -> Result { - let global_ctx = self.peer_manager.get_global_ctx(); + let global_ctx = weak_upgrade(&self.peer_manager)?.get_global_ctx(); let tcp_ports = global_ctx.config.get_tcp_whitelist(); let udp_ports = global_ctx.config.get_udp_whitelist(); tracing::info!( diff --git a/easytier/src/proto/api.rs b/easytier/src/proto/api.rs new file mode 100644 index 0000000..c9cdb76 --- /dev/null +++ b/easytier/src/proto/api.rs @@ -0,0 +1,262 @@ +pub mod config { + include!(concat!(env!("OUT_DIR"), "/api.config.rs")); + pub struct Patchable { + pub action: Option, + pub value: Option, + } + + impl From for Patchable { + fn from(patch: PortForwardPatch) -> Self { + Patchable { + action: ConfigPatchAction::try_from(patch.action).ok(), + value: patch.cfg.map(Into::into), + } + } + } + + impl From for Patchable { + fn from(value: RoutePatch) -> Self { + Patchable { + action: ConfigPatchAction::try_from(value.action).ok(), + value: value.cidr.map(Into::into), + } + } + } + + impl From for Patchable { + fn from(value: ExitNodePatch) -> Self { + Patchable { + action: ConfigPatchAction::try_from(value.action).ok(), + value: value.node.map(Into::into), + } + } + } + + impl From for Patchable { + fn from(value: StringPatch) -> Self { + Patchable { + action: ConfigPatchAction::try_from(value.action).ok(), + value: Some(value.value), + } + } + } + + impl From for Patchable { + fn from(value: UrlPatch) -> Self { + Patchable { + action: ConfigPatchAction::try_from(value.action).ok(), + value: value.url.map(Into::into), + } + } + } + + pub fn patch_vec(v: &mut Vec, patches: Vec>) + where + T: PartialEq, + { + for patch in patches { + match patch.action { + Some(ConfigPatchAction::Add) => { + if let Some(value) = patch.value { + v.push(value); + } + } + Some(ConfigPatchAction::Remove) => { + if let Some(value) = patch.value { + v.retain(|x| x != &value); + } + } + Some(ConfigPatchAction::Clear) => { + v.clear(); + } + None => {} + } + } + } +} + +pub mod instance { + include!(concat!(env!("OUT_DIR"), "/api.instance.rs")); + + impl PeerRoutePair { + pub fn get_latency_ms(&self) -> Option { + let mut ret = u64::MAX; + let p = self.peer.as_ref()?; + let default_conn_id = p.default_conn_id.map(|id| id.to_string()); + for conn in p.conns.iter() { + let Some(stats) = &conn.stats else { + continue; + }; + if default_conn_id == Some(conn.conn_id.to_string()) { + return Some(f64::from(stats.latency_us as u32) / 1000.0); + } + ret = ret.min(stats.latency_us); + } + + if ret == u64::MAX { + None + } else { + Some(f64::from(ret as u32) / 1000.0) + } + } + + pub fn get_rx_bytes(&self) -> Option { + let mut ret = 0; + let p = self.peer.as_ref()?; + for conn in p.conns.iter() { + let Some(stats) = &conn.stats else { + continue; + }; + ret += stats.rx_bytes; + } + + if ret == 0 { + None + } else { + Some(ret) + } + } + + pub fn get_tx_bytes(&self) -> Option { + let mut ret = 0; + let p = self.peer.as_ref()?; + for conn in p.conns.iter() { + let Some(stats) = &conn.stats else { + continue; + }; + ret += stats.tx_bytes; + } + + if ret == 0 { + None + } else { + Some(ret) + } + } + + pub fn get_loss_rate(&self) -> Option { + let mut ret = 0.0; + let p = self.peer.as_ref()?; + for conn in p.conns.iter() { + ret += conn.loss_rate; + } + + if ret == 0.0 { + None + } else { + Some(ret as f64) + } + } + + fn is_tunnel_ipv6(tunnel_info: &super::super::common::TunnelInfo) -> bool { + let Some(local_addr) = &tunnel_info.local_addr else { + return false; + }; + + let u: url::Url = local_addr.clone().into(); + u.host() + .map(|h| matches!(h, url::Host::Ipv6(_))) + .unwrap_or(false) + } + + fn get_tunnel_proto_str(tunnel_info: &super::super::common::TunnelInfo) -> String { + if Self::is_tunnel_ipv6(tunnel_info) { + format!("{}6", tunnel_info.tunnel_type) + } else { + tunnel_info.tunnel_type.clone() + } + } + + pub fn get_conn_protos(&self) -> Option> { + let mut ret = vec![]; + let p = self.peer.as_ref()?; + for conn in p.conns.iter() { + let Some(tunnel_info) = &conn.tunnel else { + continue; + }; + // insert if not exists + let tunnel_type = Self::get_tunnel_proto_str(tunnel_info); + if !ret.contains(&tunnel_type) { + ret.push(tunnel_type); + } + } + + if ret.is_empty() { + None + } else { + Some(ret) + } + } + + pub fn get_udp_nat_type(&self) -> String { + use crate::proto::common::NatType; + let mut ret = NatType::Unknown; + if let Some(r) = &self.route.clone().unwrap_or_default().stun_info { + ret = NatType::try_from(r.udp_nat_type).unwrap(); + } + format!("{:?}", ret) + } + } + + pub fn list_peer_route_pair(peers: Vec, routes: Vec) -> Vec { + let mut pairs: Vec = vec![]; + + for route in routes.iter() { + let peer = peers.iter().find(|peer| peer.peer_id == route.peer_id); + let pair = PeerRoutePair { + route: Some(route.clone()), + peer: peer.cloned(), + }; + + pairs.push(pair); + } + + pairs.sort_by(|a, b| { + let a_is_public_server = a + .route + .as_ref() + .and_then(|r| r.feature_flag.as_ref()) + .is_some_and(|f| f.is_public_server); + + let b_is_public_server = b + .route + .as_ref() + .and_then(|r| r.feature_flag.as_ref()) + .is_some_and(|f| f.is_public_server); + + if a_is_public_server != b_is_public_server { + return if a_is_public_server { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + }; + } + + let a_ip = a + .route + .as_ref() + .and_then(|r| r.ipv4_addr.as_ref()) + .and_then(|ipv4| ipv4.address.as_ref()) + .map_or(0, |addr| addr.addr); + + let b_ip = b + .route + .as_ref() + .and_then(|r| r.ipv4_addr.as_ref()) + .and_then(|ipv4| ipv4.address.as_ref()) + .map_or(0, |addr| addr.addr); + + a_ip.cmp(&b_ip) + }); + + pairs + } +} + +pub mod logger { + include!(concat!(env!("OUT_DIR"), "/api.logger.rs")); +} + +pub mod manage { + include!(concat!(env!("OUT_DIR"), "/api.manage.rs")); +} diff --git a/easytier/src/proto/config.proto b/easytier/src/proto/api_config.proto similarity index 78% rename from easytier/src/proto/config.proto rename to easytier/src/proto/api_config.proto index 4aa7ae2..4a48672 100644 --- a/easytier/src/proto/config.proto +++ b/easytier/src/proto/api_config.proto @@ -2,8 +2,9 @@ syntax = "proto3"; import "common.proto"; import "acl.proto"; +import "api_instance.proto"; -package config; +package api.config; enum ConfigPatchAction { ADD = 0; @@ -21,6 +22,7 @@ message InstanceConfigPatch { repeated RoutePatch routes = 7; repeated ExitNodePatch exit_nodes = 8; repeated UrlPatch mapped_listeners = 9; + repeated UrlPatch connectors = 10; } message PortForwardPatch { @@ -60,6 +62,13 @@ message ExitNodePatch { common.IpAddr node = 2; } -message PatchConfigRequest { InstanceConfigPatch patch = 1; } +message PatchConfigRequest { + InstanceConfigPatch patch = 1; + api.instance.InstanceIdentifier instance = 2; +} -service ConfigRpc { rpc PatchConfig(PatchConfigRequest) returns (common.Void); } +message PatchConfigResponse {} + +service ConfigRpc { + rpc PatchConfig(PatchConfigRequest) returns (PatchConfigResponse); +} diff --git a/easytier/src/proto/cli.proto b/easytier/src/proto/api_instance.proto similarity index 69% rename from easytier/src/proto/cli.proto rename to easytier/src/proto/api_instance.proto index df3313d..a01af16 100644 --- a/easytier/src/proto/cli.proto +++ b/easytier/src/proto/api_instance.proto @@ -4,7 +4,16 @@ import "common.proto"; import "peer_rpc.proto"; import "acl.proto"; -package cli; +package api.instance; + +message InstanceIdentifier { + message InstanceSelector { optional string name = 1; } + + oneof selector { + common.UUID id = 1; + InstanceSelector instance_selector = 2; + } +} message Status { int32 code = 1; @@ -41,7 +50,7 @@ message PeerInfo { repeated common.UUID directly_connected_conns = 4; } -message ListPeerRequest {} +message ListPeerRequest { InstanceIdentifier instance = 1; } message ListPeerResponse { repeated PeerInfo peer_infos = 1; @@ -89,19 +98,19 @@ message NodeInfo { peer_rpc.GetIpListResponse ip_list = 11; } -message ShowNodeInfoRequest {} +message ShowNodeInfoRequest { InstanceIdentifier instance = 1; } message ShowNodeInfoResponse { NodeInfo node_info = 1; } -message ListRouteRequest {} +message ListRouteRequest { InstanceIdentifier instance = 1; } message ListRouteResponse { repeated Route routes = 1; } -message DumpRouteRequest {} +message DumpRouteRequest { InstanceIdentifier instance = 1; } message DumpRouteResponse { string result = 1; } -message ListForeignNetworkRequest {} +message ListForeignNetworkRequest { InstanceIdentifier instance = 1; } message ForeignNetworkEntryPb { repeated PeerInfo peers = 1; @@ -114,7 +123,7 @@ message ListForeignNetworkResponse { map foreign_networks = 1; } -message ListGlobalForeignNetworkRequest {} +message ListGlobalForeignNetworkRequest { InstanceIdentifier instance = 1; } message ListGlobalForeignNetworkResponse { // foreign network in the entire network @@ -152,37 +161,25 @@ message Connector { ConnectorStatus status = 2; } -message ListConnectorRequest {} +message ListConnectorRequest { InstanceIdentifier instance = 1; } message ListConnectorResponse { repeated Connector connectors = 1; } -enum ConnectorManageAction { - ADD = 0; - REMOVE = 1; -} - -message ManageConnectorRequest { - ConnectorManageAction action = 1; - common.Url url = 2; -} - -message ManageConnectorResponse {} - service ConnectorManageRpc { rpc ListConnector(ListConnectorRequest) returns (ListConnectorResponse); - rpc ManageConnector(ManageConnectorRequest) returns (ManageConnectorResponse); } -message MappedListener { - common.Url url = 1; +message MappedListener { common.Url url = 1; } + +message ListMappedListenerRequest { InstanceIdentifier instance = 1; } + +message ListMappedListenerResponse { + repeated MappedListener mappedlisteners = 1; } -message ListMappedListenerRequest {} - -message ListMappedListenerResponse { repeated MappedListener mappedlisteners = 1; } - service MappedListenerManageRpc { - rpc ListMappedListener(ListMappedListenerRequest) returns (ListMappedListenerResponse); + rpc ListMappedListener(ListMappedListenerRequest) + returns (ListMappedListenerResponse); } message VpnPortalInfo { @@ -191,7 +188,7 @@ message VpnPortalInfo { repeated string connected_clients = 3; } -message GetVpnPortalInfoRequest {} +message GetVpnPortalInfoRequest { InstanceIdentifier instance = 1; } message GetVpnPortalInfoResponse { VpnPortalInfo vpn_portal_info = 1; } service VpnPortalRpc { @@ -206,19 +203,19 @@ enum TcpProxyEntryTransportType { } enum TcpProxyEntryState { - Unknown = 0; - // receive syn packet but not start connecting to dst - SynReceived = 1; - // connecting to dst - ConnectingDst = 2; - // connected to dst - Connected = 3; - // connection closed - Closed = 4; - // closing src - ClosingSrc = 5; - // closing dst - ClosingDst = 6; + Unknown = 0; + // receive syn packet but not start connecting to dst + SynReceived = 1; + // connecting to dst + ConnectingDst = 2; + // connected to dst + Connected = 3; + // connection closed + Closed = 4; + // closing src + ClosingSrc = 5; + // closing dst + ClosingDst = 6; } message TcpProxyEntry { @@ -229,36 +226,32 @@ message TcpProxyEntry { TcpProxyEntryTransportType transport_type = 5; } -message ListTcpProxyEntryRequest {} +message ListTcpProxyEntryRequest { InstanceIdentifier instance = 1; } -message ListTcpProxyEntryResponse { - repeated TcpProxyEntry entries = 1; -} +message ListTcpProxyEntryResponse { repeated TcpProxyEntry entries = 1; } service TcpProxyRpc { rpc ListTcpProxyEntry(ListTcpProxyEntryRequest) returns (ListTcpProxyEntryResponse); } -message GetAclStatsRequest {} +message GetAclStatsRequest { InstanceIdentifier instance = 1; } -message GetAclStatsResponse { - acl.AclStats acl_stats = 1; -} +message GetAclStatsResponse { acl.AclStats acl_stats = 1; } service AclManageRpc { rpc GetAclStats(GetAclStatsRequest) returns (GetAclStatsResponse); rpc GetWhitelist(GetWhitelistRequest) returns (GetWhitelistResponse); } -message GetWhitelistRequest {} +message GetWhitelistRequest { InstanceIdentifier instance = 1; } message GetWhitelistResponse { repeated string tcp_ports = 1; repeated string udp_ports = 2; } -message ListPortForwardRequest {} +message ListPortForwardRequest { InstanceIdentifier instance = 1; } message ListPortForwardResponse { repeated common.PortForwardConfigPb cfgs = 1; @@ -274,47 +267,16 @@ message MetricSnapshot { map labels = 3; } -message GetStatsRequest {} +message GetStatsRequest { InstanceIdentifier instance = 1; } -message GetStatsResponse { - repeated MetricSnapshot metrics = 1; -} +message GetStatsResponse { repeated MetricSnapshot metrics = 1; } -message GetPrometheusStatsRequest {} +message GetPrometheusStatsRequest { InstanceIdentifier instance = 1; } -message GetPrometheusStatsResponse { - string prometheus_text = 1; -} +message GetPrometheusStatsResponse { string prometheus_text = 1; } service StatsRpc { rpc GetStats(GetStatsRequest) returns (GetStatsResponse); - rpc GetPrometheusStats(GetPrometheusStatsRequest) returns (GetPrometheusStatsResponse); -} - -enum LogLevel { - DISABLED = 0; - ERROR = 1; - WARNING = 2; - INFO = 3; - DEBUG = 4; - TRACE = 5; -} - -message SetLoggerConfigRequest { - LogLevel level = 1; -} - -message SetLoggerConfigResponse { -} - -message GetLoggerConfigRequest { -} - -message GetLoggerConfigResponse { - LogLevel level = 1; -} - -service LoggerRpc { - rpc SetLoggerConfig(SetLoggerConfigRequest) returns (SetLoggerConfigResponse); - rpc GetLoggerConfig(GetLoggerConfigRequest) returns (GetLoggerConfigResponse); + rpc GetPrometheusStats(GetPrometheusStatsRequest) + returns (GetPrometheusStatsResponse); } diff --git a/easytier/src/proto/api_logger.proto b/easytier/src/proto/api_logger.proto new file mode 100644 index 0000000..c738ba2 --- /dev/null +++ b/easytier/src/proto/api_logger.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package api.logger; + +enum LogLevel { + DISABLED = 0; + ERROR = 1; + WARNING = 2; + INFO = 3; + DEBUG = 4; + TRACE = 5; +} + +message SetLoggerConfigRequest { LogLevel level = 1; } + +message SetLoggerConfigResponse {} + +message GetLoggerConfigRequest {} + +message GetLoggerConfigResponse { LogLevel level = 1; } +service LoggerRpc { + rpc SetLoggerConfig(SetLoggerConfigRequest) returns (SetLoggerConfigResponse); + rpc GetLoggerConfig(GetLoggerConfigRequest) returns (GetLoggerConfigResponse); +} diff --git a/easytier/src/proto/api_manage.proto b/easytier/src/proto/api_manage.proto new file mode 100644 index 0000000..73cf922 --- /dev/null +++ b/easytier/src/proto/api_manage.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; + +import "common.proto"; +import "peer_rpc.proto"; +import "api_instance.proto"; + +package api.manage; + +enum NetworkingMethod { + PublicServer = 0; + Manual = 1; + Standalone = 2; +} + +message NetworkConfig { + optional string instance_id = 1; + + optional bool dhcp = 2; + optional string virtual_ipv4 = 3; + optional int32 network_length = 4; + optional string hostname = 5; + optional string network_name = 6; + optional string network_secret = 7; + optional NetworkingMethod networking_method = 8; + + optional string public_server_url = 9; + repeated string peer_urls = 10; + + repeated string proxy_cidrs = 11; + + optional bool enable_vpn_portal = 12; + optional int32 vpn_portal_listen_port = 13; + optional string vpn_portal_client_network_addr = 14; + optional int32 vpn_portal_client_network_len = 15; + + optional bool advanced_settings = 16; + + repeated string listener_urls = 17; + // optional int32 rpc_port = 18; + optional bool latency_first = 19; + + optional string dev_name = 20; + + optional bool use_smoltcp = 21; + optional bool disable_ipv6 = 47; + optional bool enable_kcp_proxy = 22; + optional bool disable_kcp_input = 23; + optional bool disable_p2p = 24; + optional bool bind_device = 25; + optional bool no_tun = 26; + + optional bool enable_exit_node = 27; + optional bool relay_all_peer_rpc = 28; + optional bool multi_thread = 29; + optional bool enable_relay_network_whitelist = 30; + repeated string relay_network_whitelist = 31; + optional bool enable_manual_routes = 32; + repeated string routes = 33; + repeated string exit_nodes = 34; + optional bool proxy_forward_by_system = 35; + optional bool disable_encryption = 36; + optional bool enable_socks5 = 37; + optional int32 socks5_port = 38; + optional bool disable_udp_hole_punching = 39; + optional int32 mtu = 40; + repeated string mapped_listeners = 41; + + optional bool enable_magic_dns = 42; + optional bool enable_private_mode = 43; + + // repeated string rpc_portal_whitelists = 44; + + optional bool enable_quic_proxy = 45; + optional bool disable_quic_input = 46; + repeated PortForwardConfig port_forwards = 48; + + optional bool disable_sym_hole_punching = 49; +} + +message PortForwardConfig { + string bind_ip = 1; + uint32 bind_port = 2; + string dst_ip = 3; + uint32 dst_port = 4; + string proto = 5; +} + +message MyNodeInfo { + common.Ipv4Inet virtual_ipv4 = 1; + string hostname = 2; + string version = 3; + peer_rpc.GetIpListResponse ips = 4; + common.StunInfo stun_info = 5; + repeated common.Url listeners = 6; + optional string vpn_portal_cfg = 7; +} + +message NetworkInstanceRunningInfo { + string dev_name = 1; + MyNodeInfo my_node_info = 2; + repeated string events = 3; + repeated api.instance.Route routes = 4; + repeated api.instance.PeerInfo peers = 5; + repeated api.instance.PeerRoutePair peer_route_pairs = 6; + bool running = 7; + optional string error_msg = 8; + peer_rpc.RouteForeignNetworkSummary foreign_network_summary = 9; +} + +message NetworkInstanceRunningInfoMap { + map map = 1; +} + +message ValidateConfigRequest { NetworkConfig config = 1; } + +message ValidateConfigResponse { string toml_config = 1; } + +message RunNetworkInstanceRequest { + common.UUID inst_id = 1; + NetworkConfig config = 2; +} + +message RunNetworkInstanceResponse { common.UUID inst_id = 1; } + +message RetainNetworkInstanceRequest { repeated common.UUID inst_ids = 1; } + +message RetainNetworkInstanceResponse { + repeated common.UUID remain_inst_ids = 1; +} + +message CollectNetworkInfoRequest { repeated common.UUID inst_ids = 1; } + +message CollectNetworkInfoResponse { NetworkInstanceRunningInfoMap info = 1; } + +message ListNetworkInstanceRequest {} + +message ListNetworkInstanceResponse { repeated common.UUID inst_ids = 1; } + +message DeleteNetworkInstanceRequest { repeated common.UUID inst_ids = 1; } + +message DeleteNetworkInstanceResponse { + repeated common.UUID remain_inst_ids = 1; +} + +service WebClientService { + rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {} + rpc RunNetworkInstance(RunNetworkInstanceRequest) + returns (RunNetworkInstanceResponse) {} + rpc RetainNetworkInstance(RetainNetworkInstanceRequest) + returns (RetainNetworkInstanceResponse) {} + rpc CollectNetworkInfo(CollectNetworkInfoRequest) + returns (CollectNetworkInfoResponse) {} + rpc ListNetworkInstance(ListNetworkInstanceRequest) + returns (ListNetworkInstanceResponse) {} + rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) + returns (DeleteNetworkInstanceResponse) {} +} diff --git a/easytier/src/proto/cli.rs b/easytier/src/proto/cli.rs deleted file mode 100644 index 5ee8752..0000000 --- a/easytier/src/proto/cli.rs +++ /dev/null @@ -1,177 +0,0 @@ -use url::Host; - -include!(concat!(env!("OUT_DIR"), "/cli.rs")); - -impl PeerRoutePair { - pub fn get_latency_ms(&self) -> Option { - let mut ret = u64::MAX; - let p = self.peer.as_ref()?; - let default_conn_id = p.default_conn_id.map(|id| id.to_string()); - for conn in p.conns.iter() { - let Some(stats) = &conn.stats else { - continue; - }; - if default_conn_id == Some(conn.conn_id.to_string()) { - return Some(f64::from(stats.latency_us as u32) / 1000.0); - } - ret = ret.min(stats.latency_us); - } - - if ret == u64::MAX { - None - } else { - Some(f64::from(ret as u32) / 1000.0) - } - } - - pub fn get_rx_bytes(&self) -> Option { - let mut ret = 0; - let p = self.peer.as_ref()?; - for conn in p.conns.iter() { - let Some(stats) = &conn.stats else { - continue; - }; - ret += stats.rx_bytes; - } - - if ret == 0 { - None - } else { - Some(ret) - } - } - - pub fn get_tx_bytes(&self) -> Option { - let mut ret = 0; - let p = self.peer.as_ref()?; - for conn in p.conns.iter() { - let Some(stats) = &conn.stats else { - continue; - }; - ret += stats.tx_bytes; - } - - if ret == 0 { - None - } else { - Some(ret) - } - } - - pub fn get_loss_rate(&self) -> Option { - let mut ret = 0.0; - let p = self.peer.as_ref()?; - for conn in p.conns.iter() { - ret += conn.loss_rate; - } - - if ret == 0.0 { - None - } else { - Some(ret as f64) - } - } - - fn is_tunnel_ipv6(tunnel_info: &super::common::TunnelInfo) -> bool { - let Some(local_addr) = &tunnel_info.local_addr else { - return false; - }; - - let u: url::Url = local_addr.clone().into(); - u.host() - .map(|h| matches!(h, Host::Ipv6(_))) - .unwrap_or(false) - } - - fn get_tunnel_proto_str(tunnel_info: &super::common::TunnelInfo) -> String { - if Self::is_tunnel_ipv6(tunnel_info) { - format!("{}6", tunnel_info.tunnel_type) - } else { - tunnel_info.tunnel_type.clone() - } - } - - pub fn get_conn_protos(&self) -> Option> { - let mut ret = vec![]; - let p = self.peer.as_ref()?; - for conn in p.conns.iter() { - let Some(tunnel_info) = &conn.tunnel else { - continue; - }; - // insert if not exists - let tunnel_type = Self::get_tunnel_proto_str(tunnel_info); - if !ret.contains(&tunnel_type) { - ret.push(tunnel_type); - } - } - - if ret.is_empty() { - None - } else { - Some(ret) - } - } - - pub fn get_udp_nat_type(&self) -> String { - use crate::proto::common::NatType; - let mut ret = NatType::Unknown; - if let Some(r) = &self.route.clone().unwrap_or_default().stun_info { - ret = NatType::try_from(r.udp_nat_type).unwrap(); - } - format!("{:?}", ret) - } -} - -pub fn list_peer_route_pair(peers: Vec, routes: Vec) -> Vec { - let mut pairs: Vec = vec![]; - - for route in routes.iter() { - let peer = peers.iter().find(|peer| peer.peer_id == route.peer_id); - let pair = PeerRoutePair { - route: Some(route.clone()), - peer: peer.cloned(), - }; - - pairs.push(pair); - } - - pairs.sort_by(|a, b| { - let a_is_public_server = a - .route - .as_ref() - .and_then(|r| r.feature_flag.as_ref()) - .is_some_and(|f| f.is_public_server); - - let b_is_public_server = b - .route - .as_ref() - .and_then(|r| r.feature_flag.as_ref()) - .is_some_and(|f| f.is_public_server); - - if a_is_public_server != b_is_public_server { - return if a_is_public_server { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - }; - } - - let a_ip = a - .route - .as_ref() - .and_then(|r| r.ipv4_addr.as_ref()) - .and_then(|ipv4| ipv4.address.as_ref()) - .map_or(0, |addr| addr.addr); - - let b_ip = b - .route - .as_ref() - .and_then(|r| r.ipv4_addr.as_ref()) - .and_then(|ipv4| ipv4.address.as_ref()) - .map_or(0, |addr| addr.addr); - - a_ip.cmp(&b_ip) - }); - - pairs -} diff --git a/easytier/src/proto/config.rs b/easytier/src/proto/config.rs deleted file mode 100644 index 1552636..0000000 --- a/easytier/src/proto/config.rs +++ /dev/null @@ -1,75 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/config.rs")); - -pub struct Patchable { - pub action: Option, - pub value: Option, -} - -impl From for Patchable { - fn from(patch: PortForwardPatch) -> Self { - Patchable { - action: ConfigPatchAction::try_from(patch.action).ok(), - value: patch.cfg.map(Into::into), - } - } -} - -impl From for Patchable { - fn from(value: RoutePatch) -> Self { - Patchable { - action: ConfigPatchAction::try_from(value.action).ok(), - value: value.cidr.map(Into::into), - } - } -} - -impl From for Patchable { - fn from(value: ExitNodePatch) -> Self { - Patchable { - action: ConfigPatchAction::try_from(value.action).ok(), - value: value.node.map(Into::into), - } - } -} - -impl From for Patchable { - fn from(value: StringPatch) -> Self { - Patchable { - action: ConfigPatchAction::try_from(value.action).ok(), - value: Some(value.value), - } - } -} - -impl From for Patchable { - fn from(value: UrlPatch) -> Self { - Patchable { - action: ConfigPatchAction::try_from(value.action).ok(), - value: value.url.map(Into::into), - } - } -} - -pub fn patch_vec(v: &mut Vec, patches: Vec>) -where - T: PartialEq, -{ - for patch in patches { - match patch.action { - Some(ConfigPatchAction::Add) => { - if let Some(value) = patch.value { - v.push(value); - } - } - Some(ConfigPatchAction::Remove) => { - if let Some(value) = patch.value { - v.retain(|x| x != &value); - } - } - Some(ConfigPatchAction::Clear) => { - v.clear(); - } - None => {} - } - } -} diff --git a/easytier/src/proto/magic_dns.proto b/easytier/src/proto/magic_dns.proto index 54790aa..788ccc6 100644 --- a/easytier/src/proto/magic_dns.proto +++ b/easytier/src/proto/magic_dns.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "google/protobuf/timestamp.proto"; import "common.proto"; -import "cli.proto"; +import "api_instance.proto"; package magic_dns; @@ -30,7 +30,7 @@ message DnsRecordList { message UpdateDnsRecordRequest { string zone = 1; - repeated cli.Route routes = 2; + repeated api.instance.Route routes = 2; } message GetDnsRecordResponse { diff --git a/easytier/src/proto/mod.rs b/easytier/src/proto/mod.rs index b99fbe7..ba3a16e 100644 --- a/easytier/src/proto/mod.rs +++ b/easytier/src/proto/mod.rs @@ -2,9 +2,8 @@ pub mod rpc_impl; pub mod rpc_types; pub mod acl; -pub mod cli; +pub mod api; pub mod common; -pub mod config; pub mod error; pub mod magic_dns; pub mod peer_rpc; diff --git a/easytier/src/proto/rpc_types/error.rs b/easytier/src/proto/rpc_types/error.rs index ba86d75..351bb46 100644 --- a/easytier/src/proto/rpc_types/error.rs +++ b/easytier/src/proto/rpc_types/error.rs @@ -6,7 +6,7 @@ use thiserror; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("rust tun error {0}")] + #[error("Rust error: {0}")] ExecutionError(#[from] anyhow::Error), #[error("Decode error: {0}")] diff --git a/easytier/src/proto/web.proto b/easytier/src/proto/web.proto index 9b73856..68429bc 100644 --- a/easytier/src/proto/web.proto +++ b/easytier/src/proto/web.proto @@ -1,188 +1,23 @@ syntax = "proto3"; import "common.proto"; -import "peer_rpc.proto"; -import "cli.proto"; package web; -enum NetworkingMethod { - PublicServer = 0; - Manual = 1; - Standalone = 2; -} - -message NetworkConfig { - optional string instance_id = 1; - - optional bool dhcp = 2; - optional string virtual_ipv4 = 3; - optional int32 network_length = 4; - optional string hostname = 5; - optional string network_name = 6; - optional string network_secret = 7; - optional NetworkingMethod networking_method = 8; - - optional string public_server_url = 9; - repeated string peer_urls = 10; - - repeated string proxy_cidrs = 11; - - optional bool enable_vpn_portal = 12; - optional int32 vpn_portal_listen_port = 13; - optional string vpn_portal_client_network_addr = 14; - optional int32 vpn_portal_client_network_len = 15; - - optional bool advanced_settings = 16; - - repeated string listener_urls = 17; - optional int32 rpc_port = 18; - optional bool latency_first = 19; - - optional string dev_name = 20; - - optional bool use_smoltcp = 21; - optional bool disable_ipv6 = 47; - optional bool enable_kcp_proxy = 22; - optional bool disable_kcp_input = 23; - optional bool disable_p2p = 24; - optional bool bind_device = 25; - optional bool no_tun = 26; - - optional bool enable_exit_node = 27; - optional bool relay_all_peer_rpc = 28; - optional bool multi_thread = 29; - optional bool enable_relay_network_whitelist = 30; - repeated string relay_network_whitelist = 31; - optional bool enable_manual_routes = 32; - repeated string routes = 33; - repeated string exit_nodes = 34; - optional bool proxy_forward_by_system = 35; - optional bool disable_encryption = 36; - optional bool enable_socks5 = 37; - optional int32 socks5_port = 38; - optional bool disable_udp_hole_punching = 39; - optional int32 mtu = 40; - repeated string mapped_listeners = 41; - - optional bool enable_magic_dns = 42; - optional bool enable_private_mode = 43; - - repeated string rpc_portal_whitelists = 44; - - optional bool enable_quic_proxy = 45; - optional bool disable_quic_input = 46; - repeated PortForwardConfig port_forwards = 48; - - optional bool disable_sym_hole_punching = 49; -} - -message PortForwardConfig { - string bind_ip = 1; - uint32 bind_port = 2; - string dst_ip = 3; - uint32 dst_port = 4; - string proto = 5; -} - -message MyNodeInfo { - common.Ipv4Inet virtual_ipv4 = 1; - string hostname = 2; - string version = 3; - peer_rpc.GetIpListResponse ips = 4; - common.StunInfo stun_info = 5; - repeated common.Url listeners = 6; - optional string vpn_portal_cfg = 7; -} - -message NetworkInstanceRunningInfo { - string dev_name = 1; - MyNodeInfo my_node_info = 2; - repeated string events = 3; - repeated cli.Route routes = 4; - repeated cli.PeerInfo peers = 5; - repeated cli.PeerRoutePair peer_route_pairs = 6; - bool running = 7; - optional string error_msg = 8; - peer_rpc.RouteForeignNetworkSummary foreign_network_summary = 9; -} - -message NetworkInstanceRunningInfoMap { - map map = 1; -} - message HeartbeatRequest { - common.UUID machine_id = 1; - common.UUID inst_id = 2; - string user_token = 3; + common.UUID machine_id = 1; + common.UUID inst_id = 2; + string user_token = 3; - string easytier_version = 4; - string report_time = 5; - string hostname = 6; + string easytier_version = 4; + string report_time = 5; + string hostname = 6; - repeated common.UUID running_network_instances = 7; + repeated common.UUID running_network_instances = 7; } -message HeartbeatResponse { -} +message HeartbeatResponse {} service WebServerService { - rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse) {} -} - -message ValidateConfigRequest { - NetworkConfig config = 1; -} - -message ValidateConfigResponse { - string toml_config = 1; -} - -message RunNetworkInstanceRequest { - common.UUID inst_id = 1; - NetworkConfig config = 2; -} - -message RunNetworkInstanceResponse { - common.UUID inst_id = 1; -} - -message RetainNetworkInstanceRequest { - repeated common.UUID inst_ids = 1; -} - -message RetainNetworkInstanceResponse { - repeated common.UUID remain_inst_ids = 1; -} - -message CollectNetworkInfoRequest { - repeated common.UUID inst_ids = 1; -} - -message CollectNetworkInfoResponse { - NetworkInstanceRunningInfoMap info = 1; -} - -message ListNetworkInstanceRequest { -} - -message ListNetworkInstanceResponse { - repeated common.UUID inst_ids = 1; -} - -message DeleteNetworkInstanceRequest { - repeated common.UUID inst_ids = 1; -} - -message DeleteNetworkInstanceResponse { - repeated common.UUID remain_inst_ids = 1; -} - -service WebClientService { - rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {} - rpc RunNetworkInstance(RunNetworkInstanceRequest) returns (RunNetworkInstanceResponse) {} - rpc RetainNetworkInstance(RetainNetworkInstanceRequest) returns (RetainNetworkInstanceResponse) {} - rpc CollectNetworkInfo(CollectNetworkInfoRequest) returns (CollectNetworkInfoResponse) {} - rpc ListNetworkInstance(ListNetworkInstanceRequest) returns (ListNetworkInstanceResponse) {} - rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) returns (DeleteNetworkInstanceResponse) {} -} + rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); +} \ No newline at end of file diff --git a/easytier/src/rpc_service/acl_manage.rs b/easytier/src/rpc_service/acl_manage.rs new file mode 100644 index 0000000..452375f --- /dev/null +++ b/easytier/src/rpc_service/acl_manage.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ + AclManageRpc, GetAclStatsRequest, GetAclStatsResponse, GetWhitelistRequest, + GetWhitelistResponse, + }, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct AclManageRpcService { + instance_manager: Arc, +} + +impl AclManageRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl AclManageRpc for AclManageRpcService { + type Controller = BaseController; + + async fn get_acl_stats( + &self, + ctrl: Self::Controller, + req: GetAclStatsRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_acl_manage_service() + .get_acl_stats(ctrl, req) + .await + } + + async fn get_whitelist( + &self, + ctrl: Self::Controller, + req: GetWhitelistRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_acl_manage_service() + .get_whitelist(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/api.rs b/easytier/src/rpc_service/api.rs new file mode 100644 index 0000000..6de125d --- /dev/null +++ b/easytier/src/rpc_service/api.rs @@ -0,0 +1,164 @@ +use std::{net::SocketAddr, sync::Arc}; + +use anyhow::Context; +use cidr::IpCidr; + +use crate::{ + instance::instance::InstanceRpcServerHook, + instance_manager::NetworkInstanceManager, + proto::{ + api::{ + config::ConfigRpcServer, + instance::{ + AclManageRpcServer, ConnectorManageRpcServer, MappedListenerManageRpcServer, + PeerManageRpcServer, PortForwardManageRpcServer, StatsRpcServer, TcpProxyRpcServer, + VpnPortalRpcServer, + }, + logger::LoggerRpcServer, + manage::WebClientServiceServer, + }, + rpc_impl::{service_registry::ServiceRegistry, standalone::StandAloneServer}, + rpc_types::error::Error, + }, + rpc_service::{ + acl_manage::AclManageRpcService, config::ConfigRpcService, + connector_manage::ConnectorManageRpcService, instance_manage::InstanceManageRpcService, + logger::LoggerRpcService, mapped_listener_manage::MappedListenerManageRpcService, + peer_manage::PeerManageRpcService, port_forward_manage::PortForwardManageRpcService, + proxy::TcpProxyRpcService, stats::StatsRpcService, vpn_portal::VpnPortalRpcService, + }, + tunnel::tcp::TcpTunnelListener, +}; + +pub struct ApiRpcServer { + rpc_server: StandAloneServer, +} + +impl ApiRpcServer { + pub fn new( + rpc_portal: Option, + rpc_portal_whitelist: Option>, + instance_manager: Arc, + ) -> anyhow::Result { + let mut rpc_server = StandAloneServer::new(TcpTunnelListener::new( + format!("tcp://{}", parse_rpc_portal(rpc_portal)?) + .parse() + .context("failed to parse rpc portal address")?, + )); + rpc_server.set_hook(Arc::new(InstanceRpcServerHook::new(rpc_portal_whitelist))); + register_api_rpc_service(&instance_manager, rpc_server.registry()); + Ok(Self { rpc_server }) + } + + pub async fn serve(mut self) -> Result { + self.rpc_server.serve().await?; + Ok(self) + } +} + +impl Drop for ApiRpcServer { + fn drop(&mut self) { + self.rpc_server.registry().unregister_all(); + } +} + +fn register_api_rpc_service( + instance_manager: &Arc, + registry: &ServiceRegistry, +) { + registry.register( + PeerManageRpcServer::new(PeerManageRpcService::new(instance_manager.clone())), + "", + ); + + registry.register( + ConnectorManageRpcServer::new(ConnectorManageRpcService::new(instance_manager.clone())), + "", + ); + + registry.register( + MappedListenerManageRpcServer::new(MappedListenerManageRpcService::new( + instance_manager.clone(), + )), + "", + ); + + registry.register( + VpnPortalRpcServer::new(VpnPortalRpcService::new(instance_manager.clone())), + "", + ); + + for client_type in ["tcp", "kcp_src", "kcp_dst", "quic_src", "quic_dst"] { + registry.register( + TcpProxyRpcServer::new(TcpProxyRpcService::new( + instance_manager.clone(), + client_type, + )), + client_type, + ); + } + + registry.register( + AclManageRpcServer::new(AclManageRpcService::new(instance_manager.clone())), + "", + ); + + registry.register( + PortForwardManageRpcServer::new(PortForwardManageRpcService::new(instance_manager.clone())), + "", + ); + + registry.register( + StatsRpcServer::new(StatsRpcService::new(instance_manager.clone())), + "", + ); + + registry.register(LoggerRpcServer::new(LoggerRpcService), ""); + + registry.register( + ConfigRpcServer::new(ConfigRpcService::new(instance_manager.clone())), + "", + ); + + registry.register( + WebClientServiceServer::new(InstanceManageRpcService::new(instance_manager.clone())), + "", + ); +} + +fn parse_rpc_portal(rpc_portal: Option) -> anyhow::Result { + if let Some(Ok(port)) = rpc_portal.as_ref().map(|s| s.parse::()) { + Ok(SocketAddr::from(([0, 0, 0, 0], port))) + } else { + let mut rpc_addr = rpc_portal + .map(|addr| { + addr.parse::() + .context("failed to parse rpc portal address") + }) + .transpose()?; + select_proper_rpc_port(&mut rpc_addr)?; + rpc_addr.ok_or_else(|| anyhow::anyhow!("failed to parse rpc portal address")) + } +} + +fn select_proper_rpc_port(addr: &mut Option) -> anyhow::Result<()> { + match addr { + None => { + *addr = Some(SocketAddr::from(([0, 0, 0, 0], 0))); + select_proper_rpc_port(addr)?; + Ok(()) + } + Some(addr) => { + if addr.port() == 0 { + let Some(port) = crate::utils::find_free_tcp_port(15888..15900) else { + tracing::warn!( + "No free port found for RPC portal, skipping setting RPC portal" + ); + return Err(anyhow::anyhow!("No free port found for RPC portal")); + }; + addr.set_port(port); + } + Ok(()) + } + } +} diff --git a/easytier/src/rpc_service/config.rs b/easytier/src/rpc_service/config.rs new file mode 100644 index 0000000..ccd52fd --- /dev/null +++ b/easytier/src/rpc_service/config.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::config::{ConfigRpc, PatchConfigRequest, PatchConfigResponse}, + rpc_types::{self, controller::BaseController}, + }, +}; + +#[derive(Clone)] +pub struct ConfigRpcService { + instance_manager: Arc, +} + +impl ConfigRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl ConfigRpc for ConfigRpcService { + type Controller = BaseController; + + async fn patch_config( + &self, + ctrl: Self::Controller, + input: PatchConfigRequest, + ) -> Result { + super::get_instance_service(&self.instance_manager, &input.instance)? + .get_config_service() + .patch_config(ctrl, input) + .await + } +} diff --git a/easytier/src/rpc_service/connector_manage.rs b/easytier/src/rpc_service/connector_manage.rs new file mode 100644 index 0000000..6030159 --- /dev/null +++ b/easytier/src/rpc_service/connector_manage.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ConnectorManageRpc, ListConnectorRequest, ListConnectorResponse}, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct ConnectorManageRpcService { + instance_manager: Arc, +} + +impl ConnectorManageRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl ConnectorManageRpc for ConnectorManageRpcService { + type Controller = BaseController; + + async fn list_connector( + &self, + ctrl: Self::Controller, + req: ListConnectorRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_connector_manage_service() + .list_connector(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/instance_manage.rs b/easytier/src/rpc_service/instance_manage.rs new file mode 100644 index 0000000..ac06d29 --- /dev/null +++ b/easytier/src/rpc_service/instance_manage.rs @@ -0,0 +1,135 @@ +use std::sync::Arc; + +use crate::{ + common::config::ConfigLoader, + instance_manager::NetworkInstanceManager, + launcher::ConfigSource, + proto::{ + api::manage::*, + rpc_types::{self, controller::BaseController}, + }, +}; + +#[derive(Clone)] +pub struct InstanceManageRpcService { + manager: Arc, +} + +impl InstanceManageRpcService { + pub fn new(manager: Arc) -> Self { + Self { manager } + } +} + +#[async_trait::async_trait] +impl WebClientService for InstanceManageRpcService { + type Controller = BaseController; + + async fn validate_config( + &self, + _: BaseController, + req: ValidateConfigRequest, + ) -> Result { + let toml_config = req.config.unwrap_or_default().gen_config()?.dump(); + Ok(ValidateConfigResponse { toml_config }) + } + + async fn run_network_instance( + &self, + _: BaseController, + req: RunNetworkInstanceRequest, + ) -> Result { + if req.config.is_none() { + return Err(anyhow::anyhow!("config is required").into()); + } + let cfg = req.config.unwrap().gen_config()?; + let id = cfg.get_id(); + if let Some(inst_id) = req.inst_id { + cfg.set_id(inst_id.into()); + } + self.manager.run_network_instance(cfg, ConfigSource::Web)?; + println!("instance {} started", id); + Ok(RunNetworkInstanceResponse { + inst_id: Some(id.into()), + }) + } + + async fn retain_network_instance( + &self, + _: BaseController, + req: RetainNetworkInstanceRequest, + ) -> Result { + let remain = self + .manager + .retain_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?; + println!("instance {:?} retained", remain); + Ok(RetainNetworkInstanceResponse { + remain_inst_ids: remain.iter().map(|item| (*item).into()).collect(), + }) + } + + async fn collect_network_info( + &self, + _: BaseController, + req: CollectNetworkInfoRequest, + ) -> Result { + let mut ret = NetworkInstanceRunningInfoMap { + map: self + .manager + .collect_network_infos()? + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + }; + let include_inst_ids = req + .inst_ids + .iter() + .cloned() + .map(|id| id.to_string()) + .collect::>(); + if !include_inst_ids.is_empty() { + let mut to_remove = Vec::new(); + for (k, _) in ret.map.iter() { + if !include_inst_ids.contains(k) { + to_remove.push(k.clone()); + } + } + + for k in to_remove { + ret.map.remove(&k); + } + } + Ok(CollectNetworkInfoResponse { info: Some(ret) }) + } + + // rpc ListNetworkInstance(ListNetworkInstanceRequest) returns (ListNetworkInstanceResponse) {} + async fn list_network_instance( + &self, + _: BaseController, + _: ListNetworkInstanceRequest, + ) -> Result { + Ok(ListNetworkInstanceResponse { + inst_ids: self + .manager + .list_network_instance_ids() + .into_iter() + .map(Into::into) + .collect(), + }) + } + + // rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) returns (DeleteNetworkInstanceResponse) {} + async fn delete_network_instance( + &self, + _: BaseController, + req: DeleteNetworkInstanceRequest, + ) -> Result { + let remain_inst_ids = self + .manager + .delete_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?; + println!("instance {:?} retained", remain_inst_ids); + Ok(DeleteNetworkInstanceResponse { + remain_inst_ids: remain_inst_ids.into_iter().map(Into::into).collect(), + }) + } +} diff --git a/easytier/src/instance/logger_rpc_service.rs b/easytier/src/rpc_service/logger.rs similarity index 97% rename from easytier/src/instance/logger_rpc_service.rs rename to easytier/src/rpc_service/logger.rs index 07ee785..d718863 100644 --- a/easytier/src/instance/logger_rpc_service.rs +++ b/easytier/src/rpc_service/logger.rs @@ -1,7 +1,7 @@ use std::sync::{mpsc::Sender, Mutex, OnceLock}; use crate::proto::{ - cli::{ + api::logger::{ GetLoggerConfigRequest, GetLoggerConfigResponse, LogLevel, LoggerRpc, SetLoggerConfigRequest, SetLoggerConfigResponse, }, @@ -11,14 +11,10 @@ use crate::proto::{ pub static LOGGER_LEVEL_SENDER: std::sync::OnceLock>> = OnceLock::new(); pub static CURRENT_LOG_LEVEL: std::sync::OnceLock> = OnceLock::new(); -#[derive(Clone)] +#[derive(Clone, Default)] pub struct LoggerRpcService; impl LoggerRpcService { - pub fn new() -> Self { - Self - } - fn log_level_to_string(level: LogLevel) -> String { match level { LogLevel::Disabled => "off".to_string(), diff --git a/easytier/src/rpc_service/mapped_listener_manage.rs b/easytier/src/rpc_service/mapped_listener_manage.rs new file mode 100644 index 0000000..3943763 --- /dev/null +++ b/easytier/src/rpc_service/mapped_listener_manage.rs @@ -0,0 +1,38 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ + ListMappedListenerRequest, ListMappedListenerResponse, MappedListenerManageRpc, + }, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct MappedListenerManageRpcService { + instance_manager: Arc, +} + +impl MappedListenerManageRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl MappedListenerManageRpc for MappedListenerManageRpcService { + type Controller = BaseController; + + async fn list_mapped_listener( + &self, + ctrl: Self::Controller, + req: ListMappedListenerRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_mapped_listener_manage_service() + .list_mapped_listener(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/mod.rs b/easytier/src/rpc_service/mod.rs new file mode 100644 index 0000000..d1fa884 --- /dev/null +++ b/easytier/src/rpc_service/mod.rs @@ -0,0 +1,108 @@ +mod acl_manage; +mod api; +mod config; +mod connector_manage; +mod mapped_listener_manage; +mod peer_manage; +mod port_forward_manage; +mod proxy; +mod stats; +mod vpn_portal; + +pub mod instance_manage; +pub mod logger; + +pub type ApiRpcServer = self::api::ApiRpcServer; + +pub trait InstanceRpcService: Sync + Send { + fn get_peer_manage_service( + &self, + ) -> &dyn crate::proto::api::instance::PeerManageRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_connector_manage_service( + &self, + ) -> &dyn crate::proto::api::instance::ConnectorManageRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_mapped_listener_manage_service( + &self, + ) -> &dyn crate::proto::api::instance::MappedListenerManageRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_vpn_portal_service( + &self, + ) -> &dyn crate::proto::api::instance::VpnPortalRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_proxy_service( + &self, + client_type: &str, + ) -> Option< + std::sync::Arc< + dyn crate::proto::api::instance::TcpProxyRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + > + Send + + Sync, + >, + >; + fn get_acl_manage_service( + &self, + ) -> &dyn crate::proto::api::instance::AclManageRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_port_forward_manage_service( + &self, + ) -> &dyn crate::proto::api::instance::PortForwardManageRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_stats_service( + &self, + ) -> &dyn crate::proto::api::instance::StatsRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; + fn get_config_service( + &self, + ) -> &dyn crate::proto::api::config::ConfigRpc< + Controller = crate::proto::rpc_types::controller::BaseController, + >; +} + +fn get_instance_service( + instance_manager: &std::sync::Arc, + identifier: &Option, +) -> Result, anyhow::Error> { + use crate::proto::api; + let selector = identifier.as_ref().and_then(|s| s.selector.as_ref()); + + let id = if let Some(api::instance::instance_identifier::Selector::Id(id)) = selector { + (*id).into() + } else { + let ids = instance_manager.filter_network_instance(|_, i| { + if let Some(api::instance::instance_identifier::Selector::InstanceSelector(selector)) = + selector + { + if let Some(name) = selector.name.as_ref() { + if i.get_inst_name() != *name { + return false; + } + } + } + true + }); + match ids.len() { + 0 => return Err(anyhow::anyhow!("No instance matches the selector")), + 1 => ids[0], + _ => { + return Err(anyhow::anyhow!( + "{} instances match the selector, please specify the instance ID", + ids.len() + )) + } + } + }; + + instance_manager + .get_instance_service(&id) + .ok_or_else(|| anyhow::anyhow!("Instance not found or API service not available")) +} diff --git a/easytier/src/rpc_service/peer_manage.rs b/easytier/src/rpc_service/peer_manage.rs new file mode 100644 index 0000000..2a21e08 --- /dev/null +++ b/easytier/src/rpc_service/peer_manage.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{self, ListPeerRequest, ListPeerResponse, PeerManageRpc}, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct PeerManageRpcService { + instance_manager: Arc, +} + +impl PeerManageRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl PeerManageRpc for PeerManageRpcService { + type Controller = BaseController; + + async fn list_peer( + &self, + ctrl: Self::Controller, + req: ListPeerRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .list_peer(ctrl, req) + .await + } + + async fn list_route( + &self, + ctrl: Self::Controller, + req: crate::proto::api::instance::ListRouteRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .list_route(ctrl, req) + .await + } + + async fn dump_route( + &self, + ctrl: Self::Controller, + req: crate::proto::api::instance::DumpRouteRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .dump_route(ctrl, req) + .await + } + + async fn list_foreign_network( + &self, + ctrl: Self::Controller, + req: crate::proto::api::instance::ListForeignNetworkRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .list_foreign_network(ctrl, req) + .await + } + + async fn list_global_foreign_network( + &self, + ctrl: Self::Controller, + req: crate::proto::api::instance::ListGlobalForeignNetworkRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .list_global_foreign_network(ctrl, req) + .await + } + + async fn show_node_info( + &self, + ctrl: Self::Controller, + req: crate::proto::api::instance::ShowNodeInfoRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_peer_manage_service() + .show_node_info(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/port_forward_manage.rs b/easytier/src/rpc_service/port_forward_manage.rs new file mode 100644 index 0000000..5872627 --- /dev/null +++ b/easytier/src/rpc_service/port_forward_manage.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ListPortForwardRequest, ListPortForwardResponse, PortForwardManageRpc}, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct PortForwardManageRpcService { + instance_manager: Arc, +} + +impl PortForwardManageRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl PortForwardManageRpc for PortForwardManageRpcService { + type Controller = BaseController; + + async fn list_port_forward( + &self, + ctrl: Self::Controller, + req: ListPortForwardRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_port_forward_manage_service() + .list_port_forward(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/proxy.rs b/easytier/src/rpc_service/proxy.rs new file mode 100644 index 0000000..d92fb5d --- /dev/null +++ b/easytier/src/rpc_service/proxy.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyRpc}, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct TcpProxyRpcService { + instance_manager: Arc, + client_type: &'static str, +} + +impl TcpProxyRpcService { + pub fn new(instance_manager: Arc, client_type: &'static str) -> Self { + Self { + instance_manager, + client_type, + } + } +} + +#[async_trait::async_trait] +impl TcpProxyRpc for TcpProxyRpcService { + type Controller = BaseController; + + async fn list_tcp_proxy_entry( + &self, + ctrl: Self::Controller, + req: ListTcpProxyEntryRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_proxy_service(self.client_type) + .ok_or_else(|| anyhow::anyhow!("TCP proxy service not found for {}", self.client_type))? + .list_tcp_proxy_entry(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/stats.rs b/easytier/src/rpc_service/stats.rs new file mode 100644 index 0000000..da6e006 --- /dev/null +++ b/easytier/src/rpc_service/stats.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{ + GetPrometheusStatsRequest, GetPrometheusStatsResponse, GetStatsRequest, + GetStatsResponse, StatsRpc, + }, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct StatsRpcService { + instance_manager: Arc, +} + +impl StatsRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl StatsRpc for StatsRpcService { + type Controller = BaseController; + + async fn get_stats( + &self, + ctrl: Self::Controller, + req: GetStatsRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_stats_service() + .get_stats(ctrl, req) + .await + } + + async fn get_prometheus_stats( + &self, + ctrl: Self::Controller, + req: GetPrometheusStatsRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_stats_service() + .get_prometheus_stats(ctrl, req) + .await + } +} diff --git a/easytier/src/rpc_service/vpn_portal.rs b/easytier/src/rpc_service/vpn_portal.rs new file mode 100644 index 0000000..e09694d --- /dev/null +++ b/easytier/src/rpc_service/vpn_portal.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use crate::{ + instance_manager::NetworkInstanceManager, + proto::{ + api::instance::{GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, VpnPortalRpc}, + rpc_types::controller::BaseController, + }, +}; + +#[derive(Clone)] +pub struct VpnPortalRpcService { + instance_manager: Arc, +} + +impl VpnPortalRpcService { + pub fn new(instance_manager: Arc) -> Self { + Self { instance_manager } + } +} + +#[async_trait::async_trait] +impl VpnPortalRpc for VpnPortalRpcService { + type Controller = BaseController; + + async fn get_vpn_portal_info( + &self, + ctrl: Self::Controller, + req: GetVpnPortalInfoRequest, + ) -> crate::proto::rpc_types::error::Result { + super::get_instance_service(&self.instance_manager, &req.instance)? + .get_vpn_portal_service() + .get_vpn_portal_info(ctrl, req) + .await + } +} diff --git a/easytier/src/tests/mod.rs b/easytier/src/tests/mod.rs index 1876314..7996fe3 100644 --- a/easytier/src/tests/mod.rs +++ b/easytier/src/tests/mod.rs @@ -132,7 +132,7 @@ pub fn enable_log() { .init(); } -fn check_route(ipv4: &str, dst_peer_id: PeerId, routes: Vec) { +fn check_route(ipv4: &str, dst_peer_id: PeerId, routes: Vec) { let mut found = false; for r in routes.iter() { if r.ipv4_addr == Some(ipv4.parse().unwrap()) { @@ -148,9 +148,9 @@ fn check_route(ipv4: &str, dst_peer_id: PeerId, routes: Vec, + routes: Vec, peer_id: PeerId, - checker: impl Fn(&crate::proto::cli::Route) -> bool, + checker: impl Fn(&crate::proto::api::instance::Route) -> bool, ) { let mut found = false; for r in routes.iter() { diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index f553acb..1bd753d 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -18,7 +18,7 @@ use crate::{ stats_manager::{LabelType, MetricName}, }, instance::instance::Instance, - proto::{cli::TcpProxyEntryTransportType, common::CompressionAlgoPb}, + proto::{api::instance::TcpProxyEntryTransportType, common::CompressionAlgoPb}, tunnel::{ common::tests::{_tunnel_bench_netns, wait_for_condition}, ring::RingTunnelConnector, @@ -2100,8 +2100,10 @@ pub async fn acl_group_based_test( #[serial_test::serial] pub async fn config_patch_test() { use crate::proto::{ + api::config::{ + ConfigPatchAction, InstanceConfigPatch, PortForwardPatch, ProxyNetworkPatch, + }, common::{PortForwardConfigPb, SocketType}, - config::{ConfigPatchAction, InstanceConfigPatch, PortForwardPatch, ProxyNetworkPatch}, }; use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; diff --git a/easytier/src/utils.rs b/easytier/src/utils.rs index 9a3bc0f..c6254b6 100644 --- a/easytier/src/utils.rs +++ b/easytier/src/utils.rs @@ -10,7 +10,7 @@ use crate::common::{ config::LoggingConfigLoader, get_logger_timer_rfc3339, tracing_rolling_appender::*, }; -pub type PeerRoutePair = crate::proto::cli::PeerRoutePair; +pub type PeerRoutePair = crate::proto::api::instance::PeerRoutePair; pub fn cost_to_str(cost: i32) -> String { if cost == 1 { @@ -30,7 +30,7 @@ pub fn init_logger( config: impl LoggingConfigLoader, need_reload: bool, ) -> Result, anyhow::Error> { - use crate::instance::logger_rpc_service::{CURRENT_LOG_LEVEL, LOGGER_LEVEL_SENDER}; + use crate::rpc_service::logger::{CURRENT_LOG_LEVEL, LOGGER_LEVEL_SENDER}; let file_config = config.get_file_logger_config(); let file_level = file_config @@ -254,6 +254,11 @@ pub fn find_free_tcp_port(mut range: std::ops::Range) -> Option { range.find(|&port| check_tcp_available(port)) } +pub fn weak_upgrade(weak: &std::sync::Weak) -> anyhow::Result> { + weak.upgrade() + .ok_or_else(|| anyhow::anyhow!("{} not available", std::any::type_name::())) +} + #[cfg(test)] mod tests { use crate::common::config::{self}; diff --git a/easytier/src/web_client/controller.rs b/easytier/src/web_client/controller.rs index 1133caa..d681e13 100644 --- a/easytier/src/web_client/controller.rs +++ b/easytier/src/web_client/controller.rs @@ -1,31 +1,22 @@ +use std::sync::Arc; + use crate::{ - common::config::ConfigLoader, instance_manager::NetworkInstanceManager, - launcher::ConfigSource, - proto::{ - rpc_types::{self, controller::BaseController}, - web::{ - CollectNetworkInfoRequest, CollectNetworkInfoResponse, DeleteNetworkInstanceRequest, - DeleteNetworkInstanceResponse, ListNetworkInstanceRequest, ListNetworkInstanceResponse, - NetworkInstanceRunningInfoMap, RetainNetworkInstanceRequest, - RetainNetworkInstanceResponse, RunNetworkInstanceRequest, RunNetworkInstanceResponse, - ValidateConfigRequest, ValidateConfigResponse, WebClientService, - }, - }, + rpc_service::instance_manage::InstanceManageRpcService, }; pub struct Controller { token: String, hostname: String, - manager: NetworkInstanceManager, + manager: Arc, } impl Controller { - pub fn new(token: String, hostname: String) -> Self { + pub fn new(token: String, hostname: String, manager: Arc) -> Self { Controller { token, hostname, - manager: NetworkInstanceManager::new(), + manager, } } @@ -40,117 +31,8 @@ impl Controller { pub fn hostname(&self) -> String { self.hostname.clone() } -} -#[async_trait::async_trait] -impl WebClientService for Controller { - type Controller = BaseController; - - async fn validate_config( - &self, - _: BaseController, - req: ValidateConfigRequest, - ) -> Result { - let toml_config = req.config.unwrap_or_default().gen_config()?.dump(); - Ok(ValidateConfigResponse { toml_config }) - } - - async fn run_network_instance( - &self, - _: BaseController, - req: RunNetworkInstanceRequest, - ) -> Result { - if req.config.is_none() { - return Err(anyhow::anyhow!("config is required").into()); - } - let cfg = req.config.unwrap().gen_config()?; - let id = cfg.get_id(); - if let Some(inst_id) = req.inst_id { - cfg.set_id(inst_id.into()); - } - self.manager.run_network_instance(cfg, ConfigSource::Web)?; - println!("instance {} started", id); - Ok(RunNetworkInstanceResponse { - inst_id: Some(id.into()), - }) - } - - async fn retain_network_instance( - &self, - _: BaseController, - req: RetainNetworkInstanceRequest, - ) -> Result { - let remain = self - .manager - .retain_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?; - println!("instance {:?} retained", remain); - Ok(RetainNetworkInstanceResponse { - remain_inst_ids: remain.iter().map(|item| (*item).into()).collect(), - }) - } - - async fn collect_network_info( - &self, - _: BaseController, - req: CollectNetworkInfoRequest, - ) -> Result { - let mut ret = NetworkInstanceRunningInfoMap { - map: self - .manager - .collect_network_infos()? - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(), - }; - let include_inst_ids = req - .inst_ids - .iter() - .cloned() - .map(|id| id.to_string()) - .collect::>(); - if !include_inst_ids.is_empty() { - let mut to_remove = Vec::new(); - for (k, _) in ret.map.iter() { - if !include_inst_ids.contains(k) { - to_remove.push(k.clone()); - } - } - - for k in to_remove { - ret.map.remove(&k); - } - } - Ok(CollectNetworkInfoResponse { info: Some(ret) }) - } - - // rpc ListNetworkInstance(ListNetworkInstanceRequest) returns (ListNetworkInstanceResponse) {} - async fn list_network_instance( - &self, - _: BaseController, - _: ListNetworkInstanceRequest, - ) -> Result { - Ok(ListNetworkInstanceResponse { - inst_ids: self - .manager - .list_network_instance_ids() - .into_iter() - .map(Into::into) - .collect(), - }) - } - - // rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) returns (DeleteNetworkInstanceResponse) {} - async fn delete_network_instance( - &self, - _: BaseController, - req: DeleteNetworkInstanceRequest, - ) -> Result { - let remain_inst_ids = self - .manager - .delete_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?; - println!("instance {:?} retained", remain_inst_ids); - Ok(DeleteNetworkInstanceResponse { - remain_inst_ids: remain_inst_ids.into_iter().map(Into::into).collect(), - }) + pub fn get_rpc_service(&self) -> InstanceManageRpcService { + InstanceManageRpcService::new(self.manager.clone()) } } diff --git a/easytier/src/web_client/mod.rs b/easytier/src/web_client/mod.rs index beeeaef..14748ab 100644 --- a/easytier/src/web_client/mod.rs +++ b/easytier/src/web_client/mod.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use crate::{common::scoped_task::ScopedTask, tunnel::TunnelConnector}; +use crate::{ + common::scoped_task::ScopedTask, instance_manager::NetworkInstanceManager, + tunnel::TunnelConnector, +}; pub mod controller; pub mod session; @@ -15,10 +18,12 @@ impl WebClient { connector: T, token: S, hostname: H, + manager: Arc, ) -> Self { let controller = Arc::new(controller::Controller::new( token.to_string(), hostname.to_string(), + manager, )); let controller_clone = controller.clone(); diff --git a/easytier/src/web_client/session.rs b/easytier/src/web_client/session.rs index 72b4b72..fdec67e 100644 --- a/easytier/src/web_client/session.rs +++ b/easytier/src/web_client/session.rs @@ -9,12 +9,10 @@ use tokio::{ use crate::{ common::{constants::EASYTIER_VERSION, get_machine_id}, proto::{ + api::manage::WebClientServiceServer, rpc_impl::bidirect::BidirectRpcManager, rpc_types::controller::BaseController, - web::{ - HeartbeatRequest, HeartbeatResponse, WebClientServiceServer, - WebServerServiceClientFactory, - }, + web::{HeartbeatRequest, HeartbeatResponse, WebServerServiceClientFactory}, }, tunnel::Tunnel, }; @@ -41,10 +39,10 @@ impl Session { let rpc_mgr = BidirectRpcManager::new(); rpc_mgr.run_with_tunnel(tunnel); - rpc_mgr - .rpc_server() - .registry() - .register(WebClientServiceServer::new(controller.clone()), ""); + rpc_mgr.rpc_server().registry().register( + WebClientServiceServer::new(controller.get_rpc_service()), + "", + ); let mut tasks: JoinSet<()> = JoinSet::new(); let heartbeat_ctx =