mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-02 17:15:43 +00:00
feat(ohos): add nearby console integration and improve UDP path selection (#2486)
* feat(ohos): complete nearby console integration Use the core management RPC surface for ephemeral nearby deployments, harden session lifecycle and packet validation, support tunnel-to-NIC packet conversion, and timestamp HarmonyOS traffic samples. * fix(core): prefer verified UDP hole-punch paths Treat zero latency as unmeasured so a newly admitted UDP path cannot replace a working relay before liveness is confirmed. --------- Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local>
This commit is contained in:
+8
@@ -1244,6 +1244,8 @@ dependencies = [
|
|||||||
"derive_builder",
|
"derive_builder",
|
||||||
"easytier-proto",
|
"easytier-proto",
|
||||||
"futures",
|
"futures",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"getrandom 0.3.3",
|
||||||
"guarden",
|
"guarden",
|
||||||
"hmac",
|
"hmac",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@@ -1277,6 +1279,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"wasm-bindgen",
|
||||||
"webpki-roots 0.26.11",
|
"webpki-roots 0.26.11",
|
||||||
"wildmatch",
|
"wildmatch",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
@@ -1288,10 +1291,15 @@ dependencies = [
|
|||||||
name = "easytier-ohrs"
|
name = "easytier-ohrs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
"bytes",
|
||||||
"easytier",
|
"easytier",
|
||||||
|
"easytier-core",
|
||||||
|
"easytier-proto",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"futures",
|
||||||
"gethostname 1.1.0",
|
"gethostname 1.1.0",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"napi-build-ohos",
|
"napi-build-ohos",
|
||||||
|
|||||||
@@ -7,9 +7,18 @@ edition = "2024"
|
|||||||
crate-type=["cdylib"]
|
crate-type=["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
bytes = "1.5"
|
||||||
|
easytier-core = { path = "../../easytier-core", default-features = false }
|
||||||
|
easytier-proto = { path = "../../easytier-proto", default-features = false, features = [
|
||||||
|
"api",
|
||||||
|
"core",
|
||||||
|
"json-rpc",
|
||||||
|
] }
|
||||||
flate2 = "1.1"
|
flate2 = "1.1"
|
||||||
|
futures = "0.3"
|
||||||
gethostname = "1.1"
|
gethostname = "1.1"
|
||||||
easytier = { path = "../../easytier" }
|
easytier = { path = "../../easytier" }
|
||||||
napi-derive-ohos = "1.1"
|
napi-derive-ohos = "1.1"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
struct LocalSocketState {
|
struct LocalSocketState {
|
||||||
stop_flag: std::sync::Arc<AtomicBool>,
|
stop_flag: std::sync::Arc<AtomicBool>,
|
||||||
@@ -38,6 +38,7 @@ const EVENT_RECEIVER_SYNC_INTERVAL: Duration = Duration::from_secs(1);
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct TrafficStatsPayload {
|
struct TrafficStatsPayload {
|
||||||
|
sampled_at_ms: i64,
|
||||||
instances: Vec<InstanceTrafficStats>,
|
instances: Vec<InstanceTrafficStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ fn tun_candidate_ids(snapshot: &RuntimeAggregateState) -> HashSet<String> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_traffic_stats() -> TrafficStatsPayload {
|
fn collect_traffic_stats(sampled_at_ms: i64) -> TrafficStatsPayload {
|
||||||
let running_instances = INSTANCE_MANAGER
|
let running_instances = INSTANCE_MANAGER
|
||||||
.instances()
|
.instances()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -285,7 +286,17 @@ fn collect_traffic_stats() -> TrafficStatsPayload {
|
|||||||
instances
|
instances
|
||||||
});
|
});
|
||||||
|
|
||||||
TrafficStatsPayload { instances }
|
TrafficStatsPayload {
|
||||||
|
sampled_at_ms,
|
||||||
|
instances,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unix_time_millis() -> i64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|duration| duration.as_millis().min(i64::MAX as u128) as i64)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_local_socket_server() -> bool {
|
pub fn start_local_socket_server() -> bool {
|
||||||
@@ -409,7 +420,7 @@ pub fn start_local_socket_server() -> bool {
|
|||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
if should_collect_traffic_stats {
|
if should_collect_traffic_stats {
|
||||||
last_traffic_stats_at = Some(now);
|
last_traffic_stats_at = Some(now);
|
||||||
match serde_json::to_string(&collect_traffic_stats()) {
|
match serde_json::to_string(&collect_traffic_stats(unix_time_millis())) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
let _ = broadcast_local_socket_json_payload_message(
|
let _ = broadcast_local_socket_json_payload_message(
|
||||||
&mut clients,
|
&mut clients,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ macro_rules! ohrs_log_debug {
|
|||||||
mod config;
|
mod config;
|
||||||
mod exports;
|
mod exports;
|
||||||
mod kernel_bridge;
|
mod kernel_bridge;
|
||||||
|
mod nearby_management;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ use easytier::common::config::NetworkConfigExt;
|
|||||||
use easytier::common::constants::EASYTIER_VERSION;
|
use easytier::common::constants::EASYTIER_VERSION;
|
||||||
use easytier::common::{
|
use easytier::common::{
|
||||||
MachineIdOptions,
|
MachineIdOptions,
|
||||||
config::{ConfigFileControl, ConfigLoader, TomlConfigLoader},
|
config::{ConfigLoader, TomlConfigLoader},
|
||||||
};
|
};
|
||||||
use easytier::instance::factory::{NativeInstanceManager, native_instance_manager_with_runtime};
|
use easytier::instance::factory::{NativeInstanceManager, native_instance_manager_with_runtime};
|
||||||
use easytier::proto::api::manage::NetworkConfig;
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
@@ -68,6 +69,7 @@ use kernel_bridge::{
|
|||||||
stop_local_socket_server as stop_local_socket_server_inner,
|
stop_local_socket_server as stop_local_socket_server_inner,
|
||||||
};
|
};
|
||||||
use napi_derive_ohos::napi;
|
use napi_derive_ohos::napi;
|
||||||
|
use napi_ohos::bindgen_prelude::Uint8Array;
|
||||||
use runtime::state::runtime_state::{RuntimeAggregateState, RuntimeInstanceState};
|
use runtime::state::runtime_state::{RuntimeAggregateState, RuntimeInstanceState};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::format;
|
use std::format;
|
||||||
@@ -380,6 +382,7 @@ fn stop_runtime_inner() -> bool {
|
|||||||
&& ok;
|
&& ok;
|
||||||
}
|
}
|
||||||
maybe_stop_local_socket_server();
|
maybe_stop_local_socket_server();
|
||||||
|
let _ = nearby_management::stop_runtime_management_server();
|
||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,7 +733,12 @@ pub(crate) fn run_network_instance_from_json(cfg_json: &str) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match INSTANCE_MANAGER.run_network_instance(cfg, ConfigFileControl::STATIC_CONFIG) {
|
let config_control = nearby_management::runtime_management_config_control(inst_id);
|
||||||
|
if !nearby_management::ensure_runtime_management_server_started() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match INSTANCE_MANAGER.run_network_instance(cfg, config_control) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
cache_runtime_config_snapshot(inst_id.to_string(), inst_id.to_string(), config);
|
cache_runtime_config_snapshot(inst_id.to_string(), inst_id.to_string(), config);
|
||||||
true
|
true
|
||||||
@@ -959,6 +967,92 @@ pub fn run_network_instance(cfg_json: String) -> bool {
|
|||||||
run_network_instance_from_json(&cfg_json)
|
run_network_instance_from_json(&cfg_json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Starts the management server in the VPN Extension process even when no
|
||||||
|
/// network instance is active, allowing a nearby controller to deploy a
|
||||||
|
/// one-shot config through the canonical Core RPC surface.
|
||||||
|
#[napi]
|
||||||
|
pub fn start_nearby_management_host() -> bool {
|
||||||
|
nearby_management::ensure_runtime_management_server_started()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn stop_nearby_management_host() -> bool {
|
||||||
|
nearby_management::stop_runtime_management_server()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn drain_nearby_host_commands() -> Vec<nearby_management::NearbyHostCommand> {
|
||||||
|
nearby_management::drain_nearby_host_commands()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn complete_nearby_host_command(
|
||||||
|
request_id: String,
|
||||||
|
success: bool,
|
||||||
|
error: Option<String>,
|
||||||
|
) -> bool {
|
||||||
|
nearby_management::complete_nearby_host_command(request_id, success, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns 1 for canonical Core RPC packets, 2 for the OHOS-private settings
|
||||||
|
/// envelope, and 0 for malformed or unsupported data.
|
||||||
|
#[napi]
|
||||||
|
pub fn nearby_management_packet_kind(packet: Uint8Array) -> i32 {
|
||||||
|
nearby_management::nearby_management_packet_kind(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn encode_nearby_ohos_packet(envelope_json: String) -> Option<Uint8Array> {
|
||||||
|
nearby_management::encode_nearby_ohos_packet(envelope_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn decode_nearby_ohos_packet(packet: Uint8Array) -> Option<String> {
|
||||||
|
nearby_management::decode_nearby_ohos_packet(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens one Core RPC endpoint for a HarmonyOS collaboration session.
|
||||||
|
///
|
||||||
|
/// The Harmony layer transports the returned native packets verbatim with
|
||||||
|
/// `abilityConnectionManager.sendData`; all RPC framing stays inside Core.
|
||||||
|
#[napi]
|
||||||
|
pub fn open_nearby_management_session(session_key: String, host: bool) -> bool {
|
||||||
|
nearby_management::open_nearby_management_session(session_key, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn close_nearby_management_session(session_key: String) -> bool {
|
||||||
|
nearby_management::close_nearby_management_session(session_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn push_nearby_management_packet(session_key: String, packet: Uint8Array) -> bool {
|
||||||
|
nearby_management::push_nearby_management_packet(session_key, packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn drain_nearby_management_packets(session_key: String) -> Vec<Uint8Array> {
|
||||||
|
nearby_management::drain_nearby_management_packets(session_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub async fn call_nearby_management_json_rpc(
|
||||||
|
session_key: String,
|
||||||
|
service_name: String,
|
||||||
|
method_name: String,
|
||||||
|
domain_name: Option<String>,
|
||||||
|
payload_json: String,
|
||||||
|
) -> String {
|
||||||
|
nearby_management::call_nearby_management_json_rpc(
|
||||||
|
session_key,
|
||||||
|
service_name,
|
||||||
|
method_name,
|
||||||
|
domain_name,
|
||||||
|
payload_json,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn collect_network_infos() -> Vec<KeyValuePair> {
|
pub fn collect_network_infos() -> Vec<KeyValuePair> {
|
||||||
exports::runtime_api::collect_network_infos()
|
exports::runtime_api::collect_network_infos()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -206,7 +206,9 @@ where
|
|||||||
op(true);
|
op(true);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("hole punching transport admitted successfully");
|
tracing::info!(
|
||||||
|
"hole punching transport admitted; awaiting liveness measurement"
|
||||||
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ use tokio_util::task::AbortOnDropHandle;
|
|||||||
type ArcPeerConn = Arc<PeerConn>;
|
type ArcPeerConn = Arc<PeerConn>;
|
||||||
type ConnMap = Arc<DashMap<PeerConnId, ArcPeerConn>>;
|
type ConnMap = Arc<DashMap<PeerConnId, ArcPeerConn>>;
|
||||||
|
|
||||||
|
fn conn_latency_sort_key(latency_us: u64, is_hole_punched: bool) -> (bool, u64) {
|
||||||
|
(is_hole_punched && latency_us == 0, latency_us)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Peer {
|
pub struct Peer {
|
||||||
pub peer_node_id: PeerId,
|
pub peer_node_id: PeerId,
|
||||||
conns: ConnMap,
|
conns: ConnMap,
|
||||||
@@ -212,16 +216,19 @@ impl Peer {
|
|||||||
return Some(conn);
|
return Some(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// find a conn with the smallest latency
|
// A zero latency on a hole-punched connection means the ping loop has not
|
||||||
let mut min_latency = u64::MAX;
|
// confirmed liveness yet. Prefer any other connection, so a freshly admitted
|
||||||
let mut selected = None;
|
// hole-punched path cannot steal traffic before its first successful ping.
|
||||||
for conn in self.conns.iter() {
|
let selected = self
|
||||||
let latency = conn.value().get_stats().latency_us;
|
.conns
|
||||||
if latency < min_latency {
|
.iter()
|
||||||
min_latency = latency;
|
.min_by_key(|conn| {
|
||||||
selected = Some(conn.value().clone());
|
conn_latency_sort_key(
|
||||||
}
|
conn.value().get_stats().latency_us,
|
||||||
}
|
conn.value().is_hole_punched(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|conn| conn.value().clone());
|
||||||
|
|
||||||
if let Some(conn) = selected.as_ref() {
|
if let Some(conn) = selected.as_ref() {
|
||||||
self.default_conn.store(Some(conn.clone()));
|
self.default_conn.store(Some(conn.clone()));
|
||||||
@@ -321,3 +328,23 @@ impl Drop for Peer {
|
|||||||
tracing::info!("peer {} drop", self.peer_node_id);
|
tracing::info!("peer {} drop", self.peer_node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::conn_latency_sort_key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn measured_relay_precedes_unverified_hole_punch_path() {
|
||||||
|
assert!(conn_latency_sort_key(20_000, false) < conn_latency_sort_key(0, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unmeasured_regular_connection_keeps_existing_priority() {
|
||||||
|
assert!(conn_latency_sort_key(0, false) < conn_latency_sort_key(20_000, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verified_lower_latency_path_can_be_preferred() {
|
||||||
|
assert!(conn_latency_sort_key(5_000, true) < conn_latency_sort_key(20_000, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user