* 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.
* feat(credentials): manage declarative credentials through TOML
Make managed credentials part of the canonical TOML configuration and
load them before peers can authenticate.
Reuse ConfigRpc hot patches to durably replace the configured credential
set without restarting the instance. Serialize credential mutations so
base, managed, and ephemeral keys cannot race into conflicts.
Remove the managed overlay file format, digest protocol, capability
negotiation, force reconciliation, and database CAS machinery. Redact
credential secrets from debug output and management events. Write
credential-bearing files atomically with private permissions.
* fix(core): release JoinSet reapers with their owners
Pass weak task-set references into background reapers so they cannot
retain the JoinSet they are meant to collect. This lets stale smoltcp
bridge tasks terminate when an IPv4 generation is replaced.
Add ownership and TCP generation-replacement regressions covering the
production port-forward failure.
* feat(vpn): hot add/remove WireGuard portal clients without restart
WireGuard portal clients were frozen at instance construction: the
engine slot maps, host key table, and PortalModule state were all
immutable after startup, so any client change required recreating the
whole instance and dropping every established session.
Wire dynamic client management through the existing config-patch
channel (ConfigRpc.patch_config -> apply_config_patch), following the
same pattern as connectors, port forwards, and proxy networks:
- proto: InstanceConfigPatch gains repeated VpnPortalClientPatch
(Add/Remove/Clear by client name)
- engine: slot maps move under an RwLock with a free-index allocator;
add_client/remove_client recycle indices, mark removed slots retired,
and expire active sessions so Core tears down the attached peer via
the regular channel-close path (credential revocation and disconnect
events included); untouched clients keep their sessions intact. The
retired flag is re-checked under the session lock so a datagram that
races with removal cannot resurrect a session
- host: WireGuardPortalHost derives keys deterministically per name
(HKDF), keeps a mutable client table for render_client_config, and
forwards updates to the live engine; changed clients are re-added so
they re-handshake into a fresh generation with the new virtual IP or
groups
- PortalModule: client set, statuses, and session locks become shared
mutable state; run_session resolves clients from the shared map at
accept time; update_clients() validates against a caller-supplied
runtime snapshot. An empty client set is legal in every lifecycle
stage, so clearing all clients never produces a configuration that
fails instance recreation
- config_patch: apply_vpn_portal_client_patches mutates the candidate
TOML; the sub-patch runs last and is deep-validated and hot-applied
before the candidate commits, so a rejected client set leaves neither
the shared model nor the live portal changed, and validation sees the
fully patched state including routes and node IPv4 from the same
request. Rejects patches when no portal is configured or a removed
client does not exist
- cli: vpn-portal add-client/remove-client/clear-clients subcommands
Tests: engine index recycling, module update validation/state/host
notification, TOML patch application, and a three-node integration test
that adds a second WireGuard client live, removes the first while the
second stays online, and asserts rejected patches leave the shared
model unchanged.
* feat(web): reconcile WireGuard portal client edits as hot patches
The web console reconciles desired network config against the running
instance and patches it in place when possible. VPN portal changes were
not part of that: any client edit made the base configs differ, so every
save recreated the instance and dropped all established sessions.
Exclude vpn_portal_config from the base comparison and diff its clients
by name instead. Client add/remove/change now produces
VpnPortalClientPatch entries (removals first, changed clients as
remove+add) applied through the existing PatchConfig channel. Listener
identity changes (address or private key) and enabling or disabling the
portal still fall back to a full instance recreate, since those change
the listener lifecycle.
* feat(web/gui): map portal client patches to frontend RPC backends
Extend the RemoteClient seam with add/remove/clear VPN portal client
operations so frontend hosts can drive the same PatchConfig channel as
the CLI. There is deliberately no dedicated editing UI: the config form
stays the single editing surface (aligned with port forwards), and
these methods exist for programmatic and future use.
- web console: JSON proxy-rpc to ConfigRpcService.patch_config with
VpnPortalClientPatch entries (pbjson string enum actions)
- desktop GUI: patch_vpn_portal_clients tauri command forwarding the
same patch through the typed ConfigRpc client
Add a native EasyTier proof-of-concept binary with TCP and UDP
transports, TUN, UDP hole punching, AES-GCM, and a read-only RPC
portal.
Introduce a release-derived mini profile and musl linker policy so
x86_64, big-endian MIPS, and little-endian MIPS stay below the strict
5,000,000-byte target without UPX.
Create easytier-core as the portable owner of configuration,
connectivity, tunnels, peer and routing state, gateways, management,
the data plane, and instance lifecycle. Keep operating-system
integration, native protocol engines, process startup, and presentation
in easytier behind explicit Host capability adapters.
Create easytier-proto to own schemas, generated RPC types, descriptors,
and feature-scoped protocol slices. Remove runtime protobuf reflection
from core while preserving unknown route-peer fields across forwarding.
Normalize instance construction through CoreInstance, CoreHostAdapters,
CoreProcessRuntime, and InstanceManager. Make the runtime config store
the only authoritative mutable configuration after startup.
Move the portable TCP/UDP data plane into core and extract a generic
OperationBroker for completion, cancellation, disposal, and capacity
accounting. Expose the session-based FFI v2 completion API and keep the
WASI guest ABI, wire schemas, and adapters with core.
Migrate CLI, GUI, web, FFI, Android JNI, OHOS, uptime, and mobile
consumers to the shared manager and core state. Add explicit user/web
config ownership and revision-aware web reconciliation.
Preserve configuration, wire, and management behavior while fixing
regressions discovered by the full platform and integration matrix:
- inherit advertised relay capabilities in foreign networks;
- refresh OSPF peer state immediately after runtime config changes;
- restore CLI GlobalCtx event output without forcing GUI logging;
- retain legacy encryption names and standalone RPC tunnel metadata;
- restore ICMP host composition and fragmented UDP handling;
- use portable 64-bit atomics on 32-bit MIPS targets; and
- retain discarded operations until late cancellation completes.
Validate the refactor across 45 GitHub checks, including Linux, macOS,
Windows, FreeBSD, web, GUI, Android, OHOS, feature profiles, and
three-node and subnet-proxy integration tests.
BREAKING CHANGE: internal Rust module paths are not preserved. Legacy
native data-plane APIs are replaced by the session-based FFI v2 API.
The dedicated Android data-plane wrapper is removed.
handle_list_all_sessions returned client_mgr.list_sessions(), which
iterates user_clients_map across ALL users and returns every session's
StorageToken (token, client_url, machine_id, user_id). The handler is
mounted under login_required! but performed no per-user authorization:
it fetched get_group_permissions() only to println! the result, then
returned the full cross-user list. Any authenticated user could read
every other user's device token and public client_url.
Scope the result to the caller by adding
Storage::list_user_client_tokens(user_id) /
ClientManager::list_sessions_by_user_id(user_id), mirroring the existing
per-user pattern in handle_get_summary (list_machine_by_user_id). Also
drop the leftover debug println! and the unwrap() on the current user
(return 401 instead).
* distinct control / data when forward packets
* fix rpc split for udp tunnel
* feat(easytier-web): pass public ip in validate token webhook
* protect rpc port from subnet proxy
* machine-id should be scoped unbder same user-id
* feat: report device os metadata to console
* fix sync root key cause packet loss
* fix tun packet not invalid
* fix faketcp cause lat jitter
* fix some packet not decrypt
* fix peer info patch, improve performance of update self info
* fix foreign credential identity mismatch handling
- extend web controller bindings to cover full RPC service set
- update rpc_service API wiring and session/controller integration
- generate trait-level json_call_method in rpc codegen
- route restful proxy-rpc requests via scoped typed clients
- add json-call regression tests and required Sync bound fixes~
Implement end-to-end encryption for core-web connections using the
Noise protocol framework with the following changes:
Client-side (easytier/src/web_client/):
- Add security.rs module with Noise handshake implementation
- Add upgrade_client_tunnel() for client-side handshake
- Add Noise frame encryption/decryption via TunnelFilter
- Integrate GetFeature RPC for capability negotiation
- Support secure_mode option to enforce encrypted connections
- Handle graceful fallback for backward compatibility
Server-side (easytier-web/):
- Accept Noise handshake in client_manager
- Expose encryption support via GetFeature RPC
The implementation uses Noise_NN_25519_ChaChaPoly_SHA256 pattern for
encryption without authentication. Provides backward compatibility
with automatic fallback to plaintext connections.
* refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component
* feat(gui): Add network config saving and refactor RemoteManagement
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.
* add method to create NetworkConfig from TomlConfigLoader
* allow web export/import toml config file and gui edit toml config
* Extract the configuration file dialog into a separate component and allow direct editing of the configuration file on the web