mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-10 07:55:36 +00:00
513695297c
* [OHOS.with ai] 将配置管理/配置分享/路由聚合/实例状态解析下沉至 Rust 内核,收敛职责并提升性能 (#2209) * feat: add ohrs config store and startup error logging * feat: full ability core for ohos * feat: full ability core for ohos * feat: clean code --------- Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local> * fix: 添加缺失文件 * fix: 修复更新路由启动两次TUN问题,并调整日志 * fix: rustfmt * fix: 适配Cidr忽略/32格式路由 * fix: 修复Option适配错误 * fix: rustfmt * fix: rustfmt --------- Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local>
486 lines
14 KiB
Rust
486 lines
14 KiB
Rust
mod config;
|
|
mod exports;
|
|
mod kernel_bridge;
|
|
mod platform;
|
|
mod runtime;
|
|
|
|
use config::repository::{
|
|
create_config_record, delete_config_record, export_config_toml, get_config_field_value,
|
|
get_default_config_json, import_toml_config, init_config_store as init_repo_store,
|
|
list_config_meta_json, save_config_record, set_config_field_value, start_kernel_with_config_id,
|
|
};
|
|
use config::services::schema_service::{
|
|
ConfigFieldMapping, NetworkConfigSchema,
|
|
get_network_config_field_mappings as build_network_config_field_mappings,
|
|
get_network_config_schema as build_network_config_schema,
|
|
};
|
|
use config::services::share_link_service::{
|
|
build_config_share_link as build_config_share_link_inner,
|
|
import_config_share_link as import_config_share_link_inner,
|
|
parse_config_share_link as parse_config_share_link_inner,
|
|
};
|
|
use config::storage::config_meta::get_config_display_name;
|
|
use config::types::stored_config::{KeyValuePair, SharedConfigLinkPayload};
|
|
use easytier::common::constants::EASYTIER_VERSION;
|
|
use easytier::common::{
|
|
MachineIdOptions,
|
|
config::{ConfigFileControl, ConfigLoader, TomlConfigLoader},
|
|
};
|
|
use easytier::instance_manager::NetworkInstanceManager;
|
|
use easytier::proto::api::manage::NetworkConfig;
|
|
use easytier::proto::api::manage::NetworkingMethod;
|
|
use easytier::web_client::{WebClient, WebClientHooks, run_web_client};
|
|
use kernel_bridge::{
|
|
aggregate_requested_tun_routes, start_local_socket_server as start_local_socket_server_inner,
|
|
stop_local_socket_server as stop_local_socket_server_inner,
|
|
};
|
|
use napi_derive_ohos::napi;
|
|
use ohos_hilog_binding::{hilog_error, hilog_info};
|
|
use runtime::state::runtime_state::{
|
|
RuntimeAggregateState, TunAggregateState, clear_tun_attached, mark_tun_attached,
|
|
runtime_instance_from_running_info,
|
|
};
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::format;
|
|
use std::sync::{Arc, Mutex};
|
|
use tokio::runtime::{Builder, Runtime};
|
|
use uuid::Uuid;
|
|
|
|
pub(crate) static INSTANCE_MANAGER: once_cell::sync::Lazy<Arc<NetworkInstanceManager>> =
|
|
once_cell::sync::Lazy::new(|| Arc::new(NetworkInstanceManager::new()));
|
|
static ASYNC_RUNTIME: once_cell::sync::Lazy<Runtime> = once_cell::sync::Lazy::new(|| {
|
|
Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("tokio runtime for easytier-ohrs")
|
|
});
|
|
static WEB_CLIENTS: once_cell::sync::Lazy<Mutex<HashMap<String, ManagedWebClient>>> =
|
|
once_cell::sync::Lazy::new(|| Mutex::new(HashMap::new()));
|
|
|
|
#[derive(Default)]
|
|
struct TrackedWebClientHooks {
|
|
instance_ids: Mutex<HashSet<Uuid>>,
|
|
}
|
|
|
|
struct ManagedWebClient {
|
|
_client: WebClient,
|
|
hooks: Arc<TrackedWebClientHooks>,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl WebClientHooks for TrackedWebClientHooks {
|
|
async fn post_run_network_instance(&self, id: &Uuid) -> Result<(), String> {
|
|
self.instance_ids
|
|
.lock()
|
|
.map_err(|err| err.to_string())?
|
|
.insert(*id);
|
|
Ok(())
|
|
}
|
|
|
|
async fn post_remove_network_instances(&self, ids: &[Uuid]) -> Result<(), String> {
|
|
let mut guard = self.instance_ids.lock().map_err(|err| err.to_string())?;
|
|
for id in ids {
|
|
guard.remove(id);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn is_config_server_config(config: &NetworkConfig) -> bool {
|
|
matches!(
|
|
NetworkingMethod::try_from(config.networking_method.unwrap_or_default())
|
|
.unwrap_or_default(),
|
|
NetworkingMethod::PublicServer
|
|
) && config
|
|
.public_server_url
|
|
.as_ref()
|
|
.is_some_and(|url| !url.trim().is_empty())
|
|
}
|
|
|
|
fn stop_web_client(config_id: &str) -> bool {
|
|
let managed = match WEB_CLIENTS.lock() {
|
|
Ok(mut guard) => guard.remove(config_id),
|
|
Err(err) => {
|
|
hilog_error!("[Rust] stop_web_client lock failed {}", err);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
let Some(managed) = managed else {
|
|
return false;
|
|
};
|
|
|
|
let tracked_ids = managed
|
|
.hooks
|
|
.instance_ids
|
|
.lock()
|
|
.map(|guard| guard.iter().copied().collect::<Vec<_>>())
|
|
.unwrap_or_default();
|
|
drop(managed);
|
|
|
|
if tracked_ids.is_empty() {
|
|
maybe_stop_local_socket_server();
|
|
return true;
|
|
}
|
|
|
|
let ret = INSTANCE_MANAGER
|
|
.delete_network_instance(tracked_ids)
|
|
.map(|_| true)
|
|
.unwrap_or_else(|err| {
|
|
hilog_error!(
|
|
"[Rust] stop config server instances failed {}: {}",
|
|
config_id,
|
|
err
|
|
);
|
|
false
|
|
});
|
|
maybe_stop_local_socket_server();
|
|
ret
|
|
}
|
|
|
|
fn ensure_local_socket_server_started() -> bool {
|
|
start_local_socket_server_inner()
|
|
}
|
|
|
|
fn maybe_stop_local_socket_server() {
|
|
let no_local_instances = INSTANCE_MANAGER.list_network_instance_ids().is_empty();
|
|
let no_web_clients = WEB_CLIENTS
|
|
.lock()
|
|
.map(|guard| guard.is_empty())
|
|
.unwrap_or(false);
|
|
if no_local_instances && no_web_clients {
|
|
let _ = stop_local_socket_server_inner();
|
|
}
|
|
}
|
|
|
|
fn run_config_server_instance(config_id: &str, config: &NetworkConfig) -> bool {
|
|
if INSTANCE_MANAGER
|
|
.list_network_instance_ids()
|
|
.iter()
|
|
.next()
|
|
.is_some()
|
|
{
|
|
hilog_error!("[Rust] there is a running instance!");
|
|
return false;
|
|
}
|
|
|
|
let Some(config_server_url) = config.public_server_url.clone() else {
|
|
hilog_error!("[Rust] public_server_url missing for config server mode");
|
|
return false;
|
|
};
|
|
let hooks = Arc::new(TrackedWebClientHooks::default());
|
|
let secure_mode = config
|
|
.secure_mode
|
|
.as_ref()
|
|
.map(|mode| mode.enabled)
|
|
.unwrap_or(false);
|
|
let hostname = config.hostname.clone();
|
|
|
|
if !ensure_local_socket_server_started() {
|
|
return false;
|
|
}
|
|
|
|
let client = ASYNC_RUNTIME.block_on(run_web_client(
|
|
&config_server_url,
|
|
MachineIdOptions::default(),
|
|
hostname,
|
|
secure_mode,
|
|
INSTANCE_MANAGER.clone(),
|
|
Some(hooks.clone()),
|
|
));
|
|
|
|
let client = match client {
|
|
Ok(client) => client,
|
|
Err(err) => {
|
|
hilog_error!("[Rust] start config server failed {}", err);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
match WEB_CLIENTS.lock() {
|
|
Ok(mut guard) => {
|
|
guard.insert(
|
|
config_id.to_string(),
|
|
ManagedWebClient {
|
|
_client: client,
|
|
hooks,
|
|
},
|
|
);
|
|
true
|
|
}
|
|
Err(err) => {
|
|
hilog_error!("[Rust] store config server client failed {}", err);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_default_network_config_json() -> Result<String, String> {
|
|
let config = NetworkConfig::new_from_config(TomlConfigLoader::default())
|
|
.map_err(|e| format!("default_network_config failed {}", e))?;
|
|
serde_json::to_string(&config).map_err(|e| format!("default_network_config failed {}", e))
|
|
}
|
|
|
|
fn convert_toml_to_network_config_inner(toml_text: &str) -> Result<String, String> {
|
|
let config = NetworkConfig::new_from_config(
|
|
TomlConfigLoader::new_from_str(toml_text).map_err(|e| e.to_string())?,
|
|
)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_string(&config).map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn parse_network_config_inner(cfg_json: &str) -> bool {
|
|
serde_json::from_str::<NetworkConfig>(cfg_json)
|
|
.ok()
|
|
.and_then(|cfg| cfg.gen_config().ok())
|
|
.is_some()
|
|
}
|
|
|
|
pub(crate) fn run_network_instance_from_json(cfg_json: &str) -> bool {
|
|
let config = match serde_json::from_str::<NetworkConfig>(cfg_json) {
|
|
Ok(cfg) => cfg,
|
|
Err(e) => {
|
|
hilog_error!("[Rust] parse config failed {}", e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if is_config_server_config(&config) {
|
|
let Some(config_id) = config.instance_id.as_deref() else {
|
|
hilog_error!("[Rust] config server config missing instance id");
|
|
return false;
|
|
};
|
|
return run_config_server_instance(config_id, &config);
|
|
}
|
|
|
|
let cfg = match config.gen_config() {
|
|
Ok(toml) => toml,
|
|
Err(e) => {
|
|
hilog_error!("[Rust] parse config failed {}", e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if !INSTANCE_MANAGER.list_network_instance_ids().is_empty() {
|
|
hilog_error!("[Rust] there is a running instance!");
|
|
return false;
|
|
}
|
|
|
|
if !ensure_local_socket_server_started() {
|
|
return false;
|
|
}
|
|
|
|
let inst_id = cfg.get_id();
|
|
if INSTANCE_MANAGER
|
|
.list_network_instance_ids()
|
|
.contains(&inst_id)
|
|
{
|
|
hilog_error!("[Rust] instance {} already exists", inst_id);
|
|
return false;
|
|
}
|
|
|
|
match INSTANCE_MANAGER.run_network_instance(cfg, false, ConfigFileControl::STATIC_CONFIG) {
|
|
Ok(_) => true,
|
|
Err(err) => {
|
|
hilog_error!("[Rust] start_kernel failed for {}: {}", inst_id, err);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_instance_uuid(config_id: &str) -> Option<Uuid> {
|
|
match Uuid::parse_str(config_id) {
|
|
Ok(uuid) => Some(uuid),
|
|
Err(err) => {
|
|
hilog_error!("[Rust] invalid config_id {}: {}", config_id, err);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
pub fn init_config_store(root_dir: String) -> bool {
|
|
exports::config_api::init_config_store(root_dir)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn list_configs() -> String {
|
|
exports::config_api::list_configs()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_config_display_name_by_id(config_id: String) -> Option<String> {
|
|
get_config_display_name(&config_id)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn save_config(config_id: String, display_name: String, config_json: String) -> bool {
|
|
exports::config_api::save_config(config_id, display_name, config_json)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn create_config(config_id: String, display_name: String) -> bool {
|
|
exports::config_api::create_config(config_id, display_name)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn rename_stored_config(config_id: String, display_name: String) -> bool {
|
|
config::storage::config_meta::set_config_display_name(config_id, display_name).is_some()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn delete_stored_config_meta(config_id: String) -> bool {
|
|
exports::config_api::delete_stored_config_meta(config_id)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_config(config_id: String) -> Option<String> {
|
|
exports::config_api::get_config(config_id)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_default_config() -> Option<String> {
|
|
exports::config_api::get_default_config()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_config_field(config_id: String, field: String) -> Option<String> {
|
|
exports::config_api::get_config_field(config_id, field)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn set_config_field(config_id: String, field: String, json_value: String) -> bool {
|
|
exports::config_api::set_config_field(config_id, field, json_value)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn import_toml(toml_text: String, display_name: Option<String>) -> Option<String> {
|
|
exports::config_api::import_toml(toml_text, display_name)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn export_toml(config_id: String) -> Option<String> {
|
|
exports::config_api::export_toml(config_id)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn start_kernel(config_id: String) -> bool {
|
|
exports::runtime_api::start_kernel(config_id, start_kernel_with_config_id)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn stop_kernel(config_id: String) -> bool {
|
|
exports::runtime_api::stop_kernel(
|
|
config_id,
|
|
stop_web_client,
|
|
parse_instance_uuid,
|
|
maybe_stop_local_socket_server,
|
|
)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn stop_network_instance(config_ids: Vec<String>) -> bool {
|
|
exports::runtime_api::stop_network_instance(config_ids, stop_kernel)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn easytier_version() -> String {
|
|
EASYTIER_VERSION.to_string()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn default_network_config() -> String {
|
|
get_default_config().unwrap_or_else(|| "{}".to_string())
|
|
}
|
|
|
|
#[napi]
|
|
pub fn convert_toml_to_network_config(toml_text: String) -> String {
|
|
convert_toml_to_network_config_inner(&toml_text).unwrap_or_else(|err| format!("ERROR: {err}"))
|
|
}
|
|
|
|
#[napi]
|
|
pub fn parse_network_config(cfg_json: String) -> bool {
|
|
parse_network_config_inner(&cfg_json)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn run_network_instance(cfg_json: String) -> bool {
|
|
run_network_instance_from_json(&cfg_json)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn collect_network_infos() -> Vec<KeyValuePair> {
|
|
exports::runtime_api::collect_network_infos()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn set_tun_fd(config_id: String, fd: i32) -> bool {
|
|
exports::runtime_api::set_tun_fd(config_id, fd, parse_instance_uuid)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_network_config_schema() -> NetworkConfigSchema {
|
|
build_network_config_schema()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_network_config_field_mappings() -> Vec<ConfigFieldMapping> {
|
|
build_network_config_field_mappings()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn exported_plain_object_schema_contains_core_networkconfig_metadata() {
|
|
let schema = get_network_config_schema();
|
|
assert_eq!(schema.name, "NetworkConfig");
|
|
assert_eq!(schema.node_kind, "schema");
|
|
assert!(
|
|
schema
|
|
.children
|
|
.iter()
|
|
.any(|field| field.name == "network_name")
|
|
);
|
|
let secure_mode = schema
|
|
.children
|
|
.iter()
|
|
.find(|field| field.name == "secure_mode")
|
|
.expect("secure_mode field");
|
|
assert!(
|
|
secure_mode
|
|
.children
|
|
.iter()
|
|
.any(|field| field.name == "enabled")
|
|
);
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
pub fn get_runtime_snapshot() -> RuntimeAggregateState {
|
|
exports::runtime_api::get_runtime_snapshot()
|
|
}
|
|
|
|
pub(crate) fn get_runtime_snapshot_inner() -> RuntimeAggregateState {
|
|
exports::runtime_api::get_runtime_snapshot_inner()
|
|
}
|
|
|
|
#[napi]
|
|
pub fn build_config_share_link(config_id: String, only_start: Option<bool>) -> Option<String> {
|
|
build_config_share_link_inner(&config_id, None, only_start.unwrap_or(false))
|
|
}
|
|
|
|
#[napi]
|
|
pub fn parse_config_share_link(share_link: String) -> Option<SharedConfigLinkPayload> {
|
|
parse_config_share_link_inner(&share_link)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn import_config_share_link(
|
|
share_link: String,
|
|
display_name_override: Option<String>,
|
|
) -> Option<String> {
|
|
import_config_share_link_inner(&share_link, display_name_override)
|
|
}
|