refactor(rpc): Centralize RPC service and unify API (#1427)

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.
This commit is contained in:
Mg Pig
2025-10-02 20:30:39 +08:00
committed by GitHub
parent d2efbbef04
commit 841d525913
65 changed files with 1953 additions and 1153 deletions
+164
View File
@@ -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<TcpTunnelListener>,
}
impl ApiRpcServer {
pub fn new(
rpc_portal: Option<String>,
rpc_portal_whitelist: Option<Vec<IpCidr>>,
instance_manager: Arc<NetworkInstanceManager>,
) -> anyhow::Result<Self> {
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, Error> {
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<NetworkInstanceManager>,
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<String>) -> anyhow::Result<SocketAddr> {
if let Some(Ok(port)) = rpc_portal.as_ref().map(|s| s.parse::<u16>()) {
Ok(SocketAddr::from(([0, 0, 0, 0], port)))
} else {
let mut rpc_addr = rpc_portal
.map(|addr| {
addr.parse::<SocketAddr>()
.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<SocketAddr>) -> 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(())
}
}
}