feat(mobile): add embedded runtime and managed network updates (#2532)

* feat(mobile): add embedded iOS runtime API

Add a thin panic-safe C ABI crate for embedding no-TUN instances
on iOS. Expose lifecycle, status, JSON-RPC, string ownership, and
error handling.

Build device and simulator XCFramework static libraries on macOS.
Add exact named-instance deletion to the iOS and Android wrappers.
Cover wrapper lifecycle and the port-forward patch flow on host
targets.

* fix(gateway): recover TCP port-forward listeners

Release an unusable TCP port-forward listener after an accept
failure. Retry binding until the forward is cancelled. Keep the old
listener released while rebinding so mobile sockets can recover.

Expose opt-in iOS diagnostics for listener and connection events.
Trace configuration removal and adapter shutdown. Add tests for
recovery, release-before-rebind, and cancellation.

* feat(web): persist incremental managed config patches

Add a revision-CAS PATCH contract for managed configs while keeping
the existing Full PUT path for compatibility and recovery.

Apply Full and Patch mutations with their revision in one SQLite
transaction. Reject ownership conflicts and invalidate revisions on
alternate web-owned writes.

Document limits, failure semantics, rollout order, and verification.
Cover delta updates, conflicts, idempotency, and transaction rollback.

* feat(web): apply managed config patches to live sessions

Carry Patch fences and touched instance IDs into live sessions.
Reconcile only those instances when the applied revision matches the
Patch base. Fall back to Full reconciliation for gaps and restarts.

Invalidate the applied revision around every direct runtime mutation.
Fence revision advancement with the runtime cache epoch so stale
reconcile rounds cannot overwrite a newer invalidation.

Require deletion responses to confirm each requested instance before
advancing the revision. Raise the managed PUT and PATCH body limit to
32 MiB and return typed conflicts for publisher recovery.

* fix(core): retry transient accepted TCP errors

Keep TCP tunnel listeners alive when an accepted socket fails during
upgrade with a retryable connection-state error.

Share the retryable I/O classifier with the socket listener. Cover a
rejected connection followed by success and propagation of permanent
errors.

* feat(core): add internal Peer Relay edge projection

Derive the local advertised OSPF row from physical adjacency and transport-authenticated credential relay coverage. Keep full local adjacency only in the temporary SPF snapshot so direct destinations retain a fallback route.

Leave Peer Relay disabled at the public configuration seam. A follow-up change can expose the preference without coupling route projection to credential reauthorization.

feat(config): expose Peer Relay routing preference

Add prefer_peer_relay to public protobuf, TOML, management patch, and
hosted runtime surfaces.

Read the preference from live peer context so runtime config updates take
effect. Refresh authenticated peer metadata when the option is enabled.

Cover dynamic enable and disable in a five-node, dual-admin credential
topology, including forwarded relay coverage and local fallback.
This commit is contained in:
KKRainbow
2026-08-28 00:43:26 +08:00
committed by GitHub
parent abf03ca521
commit 4a10d1c2b9
38 changed files with 5614 additions and 554 deletions
@@ -73,6 +73,14 @@ object EasyTierJNI {
*/
@JvmStatic external fun retainNetworkInstance(instanceNames: Array<String>?): Int
/**
* 停止指定的网络实例,其他实例不受影响
* @param instanceName 要停止的实例名称,不存在时为 no-op
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic external fun deleteNetworkInstance(instanceName: String): Int
/**
* 收集网络信息
* @param maxLength 最大返回条目数
@@ -9,6 +9,7 @@
//! - `parseConfig(config)`: validate TOML config text.
//! - `runNetworkInstance(config)`: start a local network instance.
//! - `retainNetworkInstance(instanceNames)`: retain named instances and stop the rest.
//! - `deleteNetworkInstance(instanceName)`: stop exactly one named instance.
//! - `listInstances()`: return running instance names and IDs as JSON.
//! - `collectNetworkInfos()`: return running instance info as a JSON string.
//! - `callJsonRpc(...)`: call an exposed EasyTier RPC service with JSON payload.
@@ -106,6 +107,23 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
network_api::retain_network_instance_jni(env, class, instance_names)
}
/// Stop exactly one named network instance without affecting other instances.
///
/// Java signature:
/// `EasyTierJNI.deleteNetworkInstance(instanceName: String): Int`
///
/// An unknown name is a no-op. On failure this returns `-1` and throws
/// `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_deleteNetworkInstance(
env: JNIEnv,
class: JClass,
instance_name: JString,
) -> jint {
logger::init();
network_api::delete_network_instance_jni(env, class, instance_name)
}
/// Collect running network instance information.
///
/// Java signature:
@@ -2,8 +2,8 @@ use std::{ffi::CStr, ptr};
use easytier::proto::api::manage::{NetworkInstanceRunningInfo, NetworkInstanceRunningInfoMap};
use easytier_ffi::{
KeyValuePair, collect_network_infos, free_string, list_instance, parse_config,
retain_network_instance, run_network_instance, set_tun_fd,
KeyValuePair, collect_network_infos, delete_network_instance, free_string, list_instance,
parse_config, retain_network_instance, run_network_instance, set_tun_fd,
};
use jni::JNIEnv;
use jni::objects::{JClass, JObjectArray, JString};
@@ -76,6 +76,30 @@ pub(crate) fn run_network_instance_jni(mut env: JNIEnv, _class: JClass, config:
}
}
pub(crate) fn delete_network_instance_jni(
mut env: JNIEnv,
_class: JClass,
instance_name: JString,
) -> jint {
let instance_name = match jstring_to_cstring(&mut env, &instance_name) {
Ok(name) => name,
Err(error) => {
throw_exception(&mut env, &format!("Invalid instance name: {error}"));
return -1;
}
};
let instance_names = [instance_name.as_ptr()];
unsafe {
let result = delete_network_instance(instance_names.as_ptr(), instance_names.len());
if result != 0
&& let Some(error) = get_last_error()
{
throw_exception(&mut env, &error);
}
result
}
}
pub(crate) fn retain_network_instance_jni(
mut env: JNIEnv,
_class: JClass,