Commit Graph
1019 Commits
Author SHA1 Message Date
KKRainbow e0bdb516b6 fix(quic): bind proxy packet checksum to packet number via ETQ1 version (#2565)
* fix(quic): bind proxy packet checksum to packet number via ETQ1 version

QUIC proxy connections die with quinn PROTOCOL_VIOLATION "unsent
packet acked" under bursty traffic with reordering, and the affected
peer pair keeps failing for every new connection until the source node
restarts.

Root cause: the custom crypto checksums the packet bytes but not the
packet number, while quinn decodes truncated packet numbers by
proximity to the largest received (RFC 9000 Appendix A). A 1-byte
encoded packet delayed beyond the +/-128 decode window is decoded as
a future packet number, still passes the checksum, gets ACKed, and
the peer aborts because it never sent that number. Real QUIC survives
this because the AEAD nonce is derived from the packet number, so a
misdecode fails authentication.

Fix: negotiate a custom QUIC version ETQ1 (0x45545131) for the proxy.
Connections on ETQ1 mix the packet number into the SeaHash checksum,
so an out-of-window misdecode fails authentication and the packet is
handled as ordinary loss. The mode is derived statelessly from the
negotiated version in QuicSession and ServerConfig::initial_keys.

Compatibility: the proxy endpoint accepts both ETQ1 and version 1.
NatDstQuicConnector dials ETQ1 first; on ConnectionError::VersionMismatch
from a legacy peer it retries with version 1 and remembers the peer in
legacy_version_peers to skip the rejected version afterwards. The
quic:// tunnel keeps version 1 only.

Verified with 13 docker nodes under netem jitter and bursty iperf
load: the previously-poisoned pair survived 16 minutes on ETQ1 with
zero violations while all legacy-version pairs kept dying; mixed
new-to-old and old-to-new connections work.

* fix(quic): extend the ETQ1 checksum fix to the quic:// tunnel

The quic:// tunnel shares CryptoKey with the proxy, so after the proxy
moved to ETQ1 the tunnel still carried the unsent-packet-acked exposure.
Make endpoint_config() dual-version so tunnel listeners accept both
ETQ1 and legacy peers, and dial ETQ1 first in upgrade_connected with a
transparent fallback to version 1 on VersionMismatch, via a shared
connect_with_etq1 helper. The proxy keeps its hedged dialer with the
per-peer legacy memory; tunnel connections are established once per
session, so the fallback there costs a single extra round trip.
2026-09-11 23:33:46 +08:00
a7383114d0 fix(core): do not allocate DHCP IP without an assigned peer IPv4 (#2551)
Fixes the DHCP allocator treating its hard-coded fallback
 subnet as an interface lease when no peer IPv4 has been 
observed. With DHCP enabled and no devices carrying a 
virtual IPv4, the allocator now waits instead of pulling 
10.126.126.0/24 onto the TUN interface.

Behavior preserved when an explicit allocator subnet is 
supplied or when peers with assigned IPv4 exist.

---------

Co-authored-by: 225284228a-droid <225284228a-droid@users.noreply.github.com>
Co-authored-by: Codex <codex@users.noreply.github.com>
2026-09-11 23:06:18 +08:00
KKRainbow 19e5c49ba3 chore: bump version to 2.7.0 (#2566)
* chore: bump version to 2.7.0

Update version strings across the workspace:
- crate versions and internal dependency requirements for
  easytier, easytier-core, easytier-proto, easytier-web,
  easytier-gui, and easytier-mini, plus Cargo.lock
- GUI package.json and tauri.conf.json
- Magisk module.prop
- default release/docker workflow tags (v2.7.0)

* fix(ohos): sync easytier-ohrs Cargo.lock with bumped workspace versions

The ohos workflow builds easytier-ohrs with --locked, so its
lockfile must record the new 2.7.0 versions of the easytier,
easytier-core, and easytier-proto path dependencies.
2026-09-10 22:38:24 +08:00
KKRainbow 974c3270e8 feat(magisk): add module WebUI configuration (#2563)
* feat(magisk): add module WebUI configuration

Reuse the existing config generator in KernelSU-compatible module
managers. Validate and atomically persist TOML through a module helper
before restarting EasyTier, and package the generated assets in CI.

Closes #1915

* fix(magisk): preserve existing WebUI configuration

Merge form-managed fields into the original TOML so advanced module settings remain intact. Resolve the running core by its exact executable path before restart.
2026-09-10 20:13:21 +08:00
KKRainbow 3d0c9c3ca5 chore(go): use lowercase module import path (#2560)
* chore(go): use lowercase module import path
* ci: scope checks for Go module changes
2026-09-10 15:13:55 +08:00
KKRainbow 86d942ec8c docs: add security reporting policy (#2561)
* docs(security): add private reporting policy

Document supported versions and route vulnerability reports through GitHub's private advisory workflow.

Add English and Chinese responsible-use notices to the READMEs.

Closes #2544

* ci: skip unrelated pull request builds

Use pull-request-aware path filtering for required Core, GUI, Mobile, and Test workflows so they still publish required check contexts without launching expensive jobs for documentation changes.

Limit the optional OHOS pull request workflow to relevant paths.
2026-09-10 12:30:49 +08:00
Zhengqi Zhang 44a0a17f68 fix(android): avoid duplicate foreground notifications (#2559) 2026-09-10 11:39:34 +08:00
KKRainbow 993640b1dd fix(config): normalize [secure_mode] when loading TOML config files (#2562) 2026-09-10 08:56:57 +08:00
Mg Pig 0fba5769a3 feat(web): add URL path support for listener addresses (#1967) 2026-09-09 22:55:44 +08:00
38e2a621bb refactor(ohos): 拆分 OHRS 包并按 socket 精细保护 VPN 流量 (#2543)
* refactor(ohos): split facade feature and kernel crates
* feat(ohos): protect transport sockets individually
* fix(ohos): keep local proxy subnets off tun
* fix(ohos): expose valid config enum values
* refactor(ohos): finalize reusable core boundary
* test(ohos): verify split package contracts

* fix(port-forward): support wildcard userspace listeners
Keep the existing Host listener intact while adding a DataPlane listener for force-smoltcp IPv4 wildcard rules. Keep literal loopback destinations on the local Host path instead of exporting them through an exit node.

* chore(ohos): refresh split workspace lockfile
* fix(socket): normalize Windows raw socket handles
* refactor(socket): carry VPN protection through host bind options
* refactor(socket): simplify protection defaults and TUN ingress
* refactor(socket): consolidate native protection and socket creation
* fix(socket): protect outbound UDP paths
* fix(socket): preserve VPN routing for RPC listeners

---------

Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local>
Co-authored-by: KKRainbow <443152178@qq.com>
2026-09-09 22:12:47 +08:00
MiaM1ku b1f87f025b feat(easytier-go): expose CreateInstanceTOML and ShowNodeInfo (#2557) 2026-09-08 20:27:52 +08:00
KKRainbow e972307510 feat(upnp): inline the IGD client (#2555)
* feat(upnp): inline the IGD client

Replace igd-next with the subset EasyTier uses for SSDP discovery,
device description parsing, and SOAP port-mapping requests.

Route SSDP and HTTP connections through EasyTier's existing socket
factories so platform-specific socket policy remains centralized.
Preserve dynamic service types, XML escaping, response limits, and
request timeouts from the upstream implementation.

Keep the upstream MIT license and retain mapping inspection only for
integration tests.

* chore(ohos): refresh lockfile after IGD migration

Update the independent OHOS workspace lockfile for the changed EasyTier dependency set so Cargo --locked accepts it. Remove the stale igd-next package and its now-unused transitive dependencies.
2026-09-08 00:32:03 +08:00
nako-ruruandbright 57afa290a4 feat(acl): enhance ACL handling to support JSON and TOML formats (#2540)
* feat(acl): enhance ACL handling to support JSON and TOML formats

---------

Co-authored-by: bright <nako_ruru@sina.com>
2026-09-06 22:33:44 +08:00
KKRainbow abb9fd7e1a fix(core): reset foreign networks when the event handler lags (#2550)
A lag used to terminate the event handler and detach the network entry
without closing its live peer connections. Those peers stayed connected
to an untracked entry, so the network could no longer recover.

On Lagged, serialize teardown with peer admission using the entry lock,
atomically detach the current entry, remove its reverse-index records,
and close every peer. A secret-verified peer can then reconnect and
create a fresh entry.

Admission now revalidates the entry before and after adding a
connection. Credential-authenticated peers may only reuse an existing
entry, and that check shares the manager lock with entry insertion.

Each entry tracks its indexed peer IDs. Teardown can therefore clear
skipped PeerRemoved records without scanning every network or leaving
stale reverse mappings.
2026-09-06 22:32:24 +08:00
KKRainbowandYour Name fe764ce45c ci: add SignPath test signing workflow (#2549)
* ci: add SignPath test signing workflow

* ci: preserve existing workflow permissions

---------

Co-authored-by: Your Name <you@example.com>
2026-09-06 15:15:05 +08:00
KKRainbow f26c2aa147 feat(wasi): run EasyTier core on Cloudflare Workers and browsers (#2548)
* fix(core): normalize secure keys for TOML instances

* feat(wasi): run core behind Cloudflare WebSockets

Introduce the Cloudflare Worker WASI host that runs the EasyTier core
behind host-upgraded WebSockets.

- Worker package scaffold (wrangler Durable Object, build-wasm script,
  vitest config) and core-runtime/websocket-host/data-plane runtime.
- WASI host WebSocket tunnel ABI (imports, adapter, runtime exports)
  with bounded receive memory and bounded admission queue.
- Route host sockets through the portable listener plan
  (HostListenerRegistration, listener queue, admission handler split).
- Build the WASM guest with the aes-gcm feature so secure peer
  sessions have their cipher available.

* feat(wasi): add outbound browser client runtime

Add the outbound-only WASI runtime and browser connector host so
browser pages can dial EasyTier peers through WebSocket relays.

- CoreConnectivityMode::{OutboundOnly, InboundOnly} gating for
  listeners, discovery, and direct connectivity modules.
- ExternalTunnelConnector plumbing through composite/connector_host/
  manual for browser WebSocket dials.
- Browser/Node smoke entries with shared helpers
  (smoke-shared.ts).

* feat(wasi): extend browser data plane with TCP half-close

Add the data-plane pieces the browser runtime needs for full-duplex
TCP streams behind host WebSockets:

- Guest TCP shutdown_write operation with submit/take ABI pair
  (DATA_PLANE_ABI_VERSION 3 -> 4) and smoltcp half-close support.
- Worker data-plane TCP listener/stream plumbing and core-runtime
  listener registration.
- Unit coverage for the new session ops and listener wiring.

* refactor(wasi): make host tunnel ABI transport-neutral

Replace WebSocket-specific core and WASI boundaries with a
message-oriented Host Tunnel interface. Keep WebSocket framing and text
rejection in the Cloudflare host while preserving payload boundaries,
ownership, cancellation, backpressure, and EOF behavior.

Rename feature flags and guest imports and exports to the Host Tunnel
ABI. Update both Worker profiles, tests, and architecture documentation.

* feat(web): split WASI hosts into publishable npm packages

Extract the shared JSPI, WASI, Host Tunnel, and data-plane runtime
into @easytier/runtime. Keep ABI handles, guest memory, TOML, and
operation broker details behind its adapter entry point.

Add typed, auto-starting @easytier/browser and factory-based
@easytier/cloudflare packages. Ship a matching Wasm profile with
each platform package and validate its capabilities before packing.

Persist Cloudflare instance identity in Durable Object storage,
centralize WebSocket admission ownership, and add package-level
coverage for the public interfaces.

* fix(web): make public packages portable

Embed the browser Wasm artifact in the published JavaScript entry
point. This lets esbuild consumers bundle the package without an asset
loader or a copied file.

Return Cloudflare's nominal Durable Object base type and document the
named subclass export required by generated Wrangler bindings.

* docs(web): add public package walkthrough

Expand both package READMEs with installation, configuration, local
validation, health checks, and deployment instructions.

Add a standalone Vite and Wrangler example that imports only the
public Browser and Cloudflare entries. Generate Worker bindings from
configuration and keep local secrets outside version control.

* chore(go): import EasyTier Go host

Add the standalone Go host runtime as a monorepo subtree without
carrying its development branch ancestry.

Preserve its API, tests, examples, generated protobuf bindings, and
embedded WASI artifacts.

* refactor(hosts): colocate Go and JavaScript runtimes

Move the browser, Cloudflare, shared runtime, and web example into
the easytier-js subtree. Update workspace metadata, build paths, and
documentation for the new layout.

Adopt github.com/EasyTier/EasyTier/easytier-go as the Go module path.
Resolve artifact and protobuf generation from the enclosing monorepo.

* build(web): isolate JavaScript host workspace

Keep public browser and Cloudflare packages outside the legacy frontend
workspace so root installs and cross-platform builds do not pull workerd.

Make each package build generate its required WASI artifact from a clean
checkout. Add a dedicated workflow that runs the same install and check
commands documented for contributors.

Move JavaScript dependencies into a scoped lockfile and restore the root
workspace lockfile to its pre-host state.
2026-09-06 13:35:02 +08:00
Zhengqi Zhang 56be71c7f9 fix(android): keep VPN running when pressing back (#2546) 2026-09-06 11:21:38 +08:00
Zhengqi Zhang f19bcfb400 feat(android): add VPN quick settings tile (#2511)
add an Android Quick Settings tile for starting and stopping EasyTier VPN networks
persist tile actions until the Tauri frontend is ready, so cold-start clicks are not lost
add the standard Quick Settings preferences activity alias so long-pressing the tile opens EasyTier
keep network and VPN lifecycle ownership in the existing frontend reconciliation flow
2026-09-05 18:08:20 +08:00
KKRainbow 164e2db6ae feat(core): expose WireGuard client traffic metrics (#2537)
* feat(core): expose WireGuard client traffic metrics

Count successfully accepted upload and delivered download packets
per VPN portal client. Keep counters stable across session reconnects
and export them through the existing Prometheus statistics endpoint.

If a client is removed by a concurrent config update while one of its
sessions is still starting, drop that session instead of panicking:
the release profile aborts the whole process on panic.

* ci(core): pin cargo-zigbuild to 0.23.2

cargo-zigbuild 0.23.3 (released today) passes
-mcpu=generic+v6+strict_align to zig cc when building jemalloc for
arm-unknown-linux-musleabi; zig 0.16.0 rejects that mcpu value, so the
linux-arm job fails and fail-fast cancels the rest of the build matrix.
Pin the tool to the last working version until zig is bumped.
2026-08-29 20:52:54 +08:00
KKRainbow 25f6e2dc5e feat: stabilize mobile runtime and VPN portal (#2536) 2026-08-29 13:40:56 +08:00
KKRainbow 4a10d1c2b9 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.
2026-08-28 00:43:26 +08:00
Xingjian Du abf03ca521 dns: timeout system resolver before hickory fallback (#2310)
Bound libc DNS before falling back to Hickory so the manual
connector's two-second resolution budget cannot expire first.

Keep at most one system lookup in flight because Tokio cannot cancel
a blocking getaddrinfo call. This prevents reconnects from exhausting
the blocking pool while allowing the system resolver to recover.

Apply the policy to process-default and namespace-aware resolution,
and cover timeout, retry, and success behavior with deterministic
tests.
2026-08-27 16:22:50 +08:00
KKRainbow 67f2270ee7 fix(credential): trust pinned admin keys on first connect (#2528) 2026-08-26 23:59:13 +08:00
KKRainbow 81487c363a fix(web): fence routes to the current session (#2522)
* fix(web): publish routable sessions before connect events
* fix(web): fence routes to the current session
2026-08-24 09:49:27 +08:00
nako-ruruandbright 3b2aadde8e chore: enforce LF for shell scripts and ignore Android .gradle cache (#2520)
Add a root .gitattributes pinning *.sh to LF so shell scripts stay
executable after a checkout on Windows with autocrlf enabled; without
it every .sh lands in the working tree as CRLF and fails when run
under WSL/bash.

Also ignore tauri-plugin-vpnservice/android/.gradle, which Gradle
regenerates on every sync.

Co-authored-by: bright <nako_ruru@sina.com>
2026-08-23 13:50:36 +08:00
nako-ruruandbright b93e45a215 feat(acl): add runtime ACL replacement via TOML (easytier-cli acl set) (#2513)
Adds a new CLI subcommand to replace the entire ACL at runtime
from a TOML input (either inline or from a file). The command is 
`easytier-cli acl set <TOML|@path>`. 

Co-authored-by: bright <nako_ruru@sina.com>
2026-08-23 13:35:37 +08:00
kittyandhezz e130e9e4c7 fix(gui): set HOME for macOS launchd service (#2509)
Co-authored-by: hezz <hezz@example.com>
2026-08-23 10:42:36 +08:00
KKRainbow 4304b065cb core: allocate smoltcp listener sockets per SYN (#2519)
A half-open handshake consumed the only smoltcp listener socket, so
other clients were rejected until its timeout. Closed listener sockets
could also remain unusable after a network interruption.

Register logical TCP listeners with the reactor without preallocating
socket slots. Allocate one temporary smoltcp socket for each new SYN;
repeated SYNs reuse the existing connection. Limit each listener to
sixteen pending handshakes or completed connections.

Batch ordinary ingress packets through smoltcp and scan listener state
once per batch. Before admitting a SYN, advance timers, reclaim stale
pending sockets, and flush queued packets. Process only a newly
allocated socket's SYN separately to preserve packet order.

Keep the global tuple lookup off unmatched and full-listener rejection
paths. Promote established connections to the normal timeout, reclaim
closed connections, and transfer accepted sockets to streams.

Keep one long-lived logical listener in SmolTcpStack and cover
concurrency, retransmission, timeout recovery, capacity, and cleanup in
tests.
2026-08-23 10:41:49 +08:00
KKRainbow 9f231ee76c refactor(credentials): centralize grant policy (#2517)
* refactor(credentials): centralize grant policy

Represent ACL groups, relay permission, proxy CIDRs, and reuse as one
internal credential grant shared by generated, imported, managed, and
attached credentials. Normalize proxy CIDRs at construction while
preserving the flat credential storage schema.

Reuse one managed credential adapter for protobuf/TOML projection and
patching so defaults and future fields have a single mapping authority.

* fix(credentials): normalize grants loaded from storage

Run persisted grants through the same CIDR normalization used by new and
managed credentials. Reject invalid stored CIDRs through the existing
storage-unavailable path and include the credential ID in the error.

Cover whitespace migration and invalid legacy data with regression tests.
2026-08-22 23:55:29 +08:00
KKRainbow 3fe427bc99 feat(credentials): manage declarative credentials through TOML (#2515)
* 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.
2026-08-22 16:30:51 +08:00
KKRainbow 8794e12a26 feat(vpn): hot add/remove WireGuard portal clients without restart (#2514)
* 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
2026-08-22 01:18:42 +08:00
KKRainbow 62e4fd15e9 feat(vpn): multi-client WireGuard portal with attached peers (#2502)
* feat(peer): support protocol-agnostic attached peers

Add locally attached peers backed by independent, peer-level portable
managers and authenticated in-process ring connections. Carry trusted
connection provenance through packet admission so attached relay
privileges cannot be forged through packet headers.

Let every peer manager own ACL loading, sanitized policy updates, route
refresh, and runtime cleanup. In Secure Mode, grant attached identities
ephemeral credentials instead of sharing administrator and group secrets.

* feat(vpn): add reusable attached-peer portal runtime

Add a protocol-neutral portal runtime that converts authenticated client
sessions into attached EasyTier peers. Own per-client generations,
status, packet forwarding, address translation, and peer cleanup without
knowing the transport protocol.

Add transactional IPv4 source and destination rewriting with correct
IPv4, TCP, UDP, ICMP, and quoted-packet checksum updates. Keep the old
production portal path temporarily active until the WireGuard adapter is
migrated in the next change.

* feat(wireguard): attach named clients through peer portal

Replace the monolithic WireGuard portal with a native adapter that owns
key derivation, UDP demultiplexing, reauthentication, roaming, and
bounded per-client packet queues. Hand authenticated sessions to the
generic portal runtime for peer lifecycle and IPv4 translation.

Move portal configuration into the core instance model, require a
dedicated server key, and preserve existing listener, CLI, and runtime
configuration behavior. Reject runtime address conflicts before
publishing shared configuration.

* feat(vpn): expose per-client portal status

Project configured clients and their runtime state through the portal
RPC, including generated client configuration, listener, peer identity,
endpoint, tunnel address, ACL groups, and errors. Keep private client
configuration out of the broad instance-info response and expose the
explicit RPC through the CLI and Tauri bridge.

* feat(vpn): add portal configuration to web clients

Expose WireGuard portal listener, key, client, ACL group, and runtime
status fields in the shared frontend library, Web dashboard, and Tauri
client. Preserve UUID and uint64 values across protobuf JSON
boundaries, keep dynamic client editor rows stable, and document the
portal workflow.

* test(vpn): cover multi-client and roaming WireGuard portals

Add two three-node integration tests for the WireGuard VPN portal.

The multi-client test connects two kernel WireGuard clients from
separate network namespaces, verifies per-client connectivity to mesh
nodes, and exercises cross-client traffic that runs the IPv4 source
and destination translation in both directions. A TCP echo exchange
through the portal additionally covers the TCP pseudo-header checksum
rewrite path that ICMP-only ping tests miss, and portal status
snapshots must report both clients online with distinct peer ids and
correctly learned tunnel addresses.

The roaming test swaps the client namespace address (delete the old
address, then add the new one) so the kernel WireGuard source cache is
invalidated and the client keeps sending under the same session from
the new source, exactly like a real network change. The portal must
update the client endpoint on the same peer id via the data path
(same generation, no re-handshake, no detach/reconnect) while
connectivity to mesh nodes is preserved.

Supporting changes: run_wireguard_client now takes an interface name,
and the shared namespace topology gains net_f (10.1.2.5) on the portal
bridge for the second client.
2026-08-21 10:59:05 +08:00
fanyang 57eb6908f4 perf(recv): add try_recv fast path in recv_packet_from_chan (#2426)
Add a try_recv() fast path to recv_packet_from_chan(): if a packet is
immediately available, return it without parking the task. Only when
the channel is empty do we fall back to recv().await.

This benefits all callers of recv_packet_from_chan() including
start_peer_recv, virtual_nic, foreign_network_manager, and instance.
2026-08-19 00:16:38 +08:00
fanyang f64af13424 fix(cli): preserve proxy CIDRs in route output (#2503) 2026-08-18 20:37:57 +08:00
Zhengqi Zhang 35845c14e6 fix(android): reconcile VPN service after startup (#2491)
Summary
Reconcile the Android system VPN after the GUI has finished initializing the EasyTier core.
Retry reconciliation while network information or the virtual IPv4 address is not ready yet.
Serialize reconciliation work and de-duplicate concurrent VPN permission requests.
Root cause
On Android, the network instance can report that it has started before collectNetworkInfo exposes the instance state and virtual IPv4 address. The previous startup path treated that temporary state as a terminal failure, stopped VPN setup, and relied on another event to retry it. If no later event arrived, peers could connect successfully while the Android VpnService remained inactive until the user stopped and started the network again.

PR #1628 added polling for the DHCP-specific empty-IP case. The same race can occur earlier, while network information is still unavailable, and can also affect static-IP configurations.
2026-08-17 22:12:01 +08:00
ParkGarden 282cd92f2c fix(Windows): 移除 wait_interface_show 的 10 秒硬超时,避免开机无网络时 TUN 接口未就绪导致实例停止 (#2500) 2026-08-17 19:13:14 +08:00
KKRainbow 636390ec38 feat(peer): echo liveness probes on data traffic (#2497)
* feat(peer): echo liveness probes on data traffic

Advertise a liveness-echo capability during classic and Noise
handshakes. After a ping failure, tag outgoing peer packets with a
short probe token and accept only the matching echoed token as
round-trip proof.

Keep one ping request outstanding and coalesce scheduler triggers so
high traffic cannot reorder timeout results. Preserve one-way failure
detection because unrelated ingress never clears the loss counter.

* test(three_node): relax disconnect wait for sequential pingpong

proxy_three_node_disconnect_test assumed the old pingpong timing,
where overlapping pings failed fast and the connection closed well
inside the 11s wait (see the old [4, 9)s comment).

The liveness-echo change keeps one ping outstanding: each failure
now takes a full 2s timeout, so the fifth consecutive failure and
the connection close land at ~11s. Both proto variants timed out at
the 11s bound in CI. Widen the wait to 15s and update the timing
comment.
2026-08-15 00:21:07 +08:00
fanyang 8c15941c44 Support configurable TCP STUN servers (#2314)
tcp_stun_servers explicitly controls TCP STUN servers.
If tcp_stun_servers is not configured, TCP STUN falls back to configured stun_servers.
If neither is configured, TCP STUN uses the built-in default TCP STUN list.
Empty lists explicitly disable the corresponding STUN server list.
Empty CLI/env overrides now clear existing configured STUN servers instead of appending nothing.
2026-08-13 09:56:44 +08:00
52bb26680a fix(fake-tcp): normalize Linux packet sockets to layer 3 (#2255)
Use AF_PACKET SOCK_DGRAM so Ethernet, TUN, and point-to-point
interfaces expose the same IP payload to the BPF filter. Rebuild the
synthetic Ethernet envelope expected by fake TCP on receive and strip
it before transmitting through the cooked socket.

Bind sockets to the selected IP protocol and reject non-initial IPv4
fragments before reading TCP ports. Preserve peer MAC addresses on
Ethernet links.

Add privileged TUN and veth tests for IPv4/IPv6 receive, send, tuple
filtering, and fragment rejection, and enable them in Linux CI.

Co-authored-by: KKRainbow <443152178@qq.com>
Co-authored-by: Max Sum <4883681+Max-Sum@users.noreply.github.com>
2026-08-12 20:21:42 +08:00
KKRainbow 0b27ac2885 feat(credentials): support managed credential synchronization (#2490)
* feat(credentials): support managed credential synchronization

Allow managed callers to upsert credentials with an exact ID, secret,
permissions, reuse policy, and expiry.

Return non-secret attributes plus a public-key fingerprint so callers can
verify relay credential consistency.

Persist imported credentials atomically and preserve identity and expiry
across restarts.

* fix(credentials): make managed upserts durable

Write the candidate credential snapshot before committing it to memory.
Propagate storage failures so controllers can retry instead of observing
false convergence.

Cover a transient storage failure to verify that memory stays unchanged
and the retry persists the credential.

* fix(credentials): atomically replace stored snapshots

Define CredentialStorage::store as an atomic replacement boundary and
use atomic-write-file in the management adapter. This keeps the last
committed credential JSON readable when a replacement fails.

Cover replacement of an existing credential snapshot and keep the
dependency scoped to the management feature.
2026-08-10 23:20:36 +08:00
韩嘉乐andFrankHan 23d55373a4 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>
2026-08-10 14:15:09 +08:00
KKRainbow d375d7e455 feat(mini): add compact native EasyTier client (#2479)
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.
2026-08-09 19:43:30 +08:00
KKRainbow 1e40350c89 feat(wasi): expose protobuf RPC request ABI (#2477)
* feat(wasi): expose protobuf RPC request ABI

Add an instance-scoped asynchronous RPC session backed by the shared
operation broker. Reuse the existing dispatcher and management handlers.
WASI hosts can call PeerManageRpc and ConnectorManageRpc with the same
protobuf payloads as easytier-cli.

Export ABI version, submit, take, and free functions. Bind selectors to
the WASM instance handle and keep method errors in RpcResponse. Enable
management RPC explicitly in the Go-host WASM build.

* fix(gateway): serialize UDP client eviction

Serialize UDP client admission across forwarding rules so only one
eviction can claim and wait for a released semaphore permit. Retry
when cleanup concurrently removes the selected client.

Add a multithreaded regression test for the permit handoff while the
evicted client is still referenced.

* fix(gateway): publish UDP client admission atomically

Hold the admission guard through client and response-task publication
so a concurrent eviction cannot leave an orphan task holding the slot
permit.

Open the data-plane flow before entering the critical section and extend
the multithreaded regression test across the publication window.
2026-08-07 18:34:53 +08:00
KKRainbow e31bde1836 feat(web): add standalone WASM config generator (#2480)
Add a small Vite workspace that builds and publishes independently
of the dashboard. Reuse frontend-lib for the form and expose the
existing NetworkConfig conversions through wasm-bindgen.

Initialize the Aura theme in the standalone entry, detect the browser
language, and provide a persistent selector in the form header. Present
Generate Config and Copy Config as the page actions.

Keep the shared network secret field fluid so both form columns align.
Bundle the generator under dist/config-generator in the dashboard
artifact while preserving its standalone build output.

Build the optimized WASM module with the project and remove the
API-backed generator route from the dashboard.
2026-08-07 16:25:33 +08:00
KKRainbow 86222771c5 fix(peer): recover from asymmetric direct connections (#2476)
Require matching pong responses before resetting consecutive liveness
failures so half-open direct connections leave the peer map.

Carry latency-first policy on relay handshakes and route replies around
stale direct peers, including handshakes started during decryption.

Store peer-center reports as atomic per-peer snapshots and include
topology costs in the digest so removals and latency updates propagate.

Drop data packets at a saturated host egress boundary instead of
blocking the shared peer packet router and shutdown path.

Add regressions for asymmetric traffic, relay ACK routing, peer-center
invalidation, and bounded host egress.
2026-08-07 10:51:15 +08:00
刚刚and225284228a-droid d114cdd20f fix(web-client): time out stalled config-server dials (#2461)
Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
2026-08-04 10:08:57 +08:00
Chenx Dust df874b85be refactor(core): use linearizable lazy token bucket (#2421)
Replace periodic refill tasks with on-demand accounting to avoid waking
idle token buckets.

Keep balance, refill time, and fractional credit in one locked state so
concurrent consumers cannot observe partially published refills or
exceed the configured burst capacity. Track credit in nanoseconds and
discard excess credit at capacity to preserve precise limiter behavior.

Use a one-second default burst capacity to preserve the existing
limiter behavior while supporting explicit capacity configuration. Keep
limiter capacity and fill rate in a local config instead of an unused
protobuf message.

Charge only logical EasyTier data payload, unwrap foreign network
packets before accounting, and leave control traffic outside the
limiter. Reject forged payload lengths by accounting from actual packet
boundaries.

Split oversized blocking consumes into capacity-sized chunks and cover
concurrency, refill precision, burst caps, payload accounting, and
bandwidth integration behavior.
2026-08-04 10:08:37 +08:00
韩嘉乐andFrankHan 8d475dc3fc fix(tunnel): preserve websocket cleanup after send errors (#2468)
Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local>
2026-08-03 10:15:42 +08:00
韩嘉乐 40c857748f feat(ohos): 接入 Pro 运行时并自动化 HAR 交付 (#2462) 2026-08-01 16:35:14 +08:00
KKRainbow afbba5d928 refactor(core): migrate packet processing from pnet to smoltcp (#2456)
Replace pnet_packet parsing and mutation across gateway packet paths with the existing smoltcp wire APIs. Preserve length validation, fragmentation classification, TCP flags, and checksum behavior while removing the core pnet_packet feature dependency.
Reject stale non-initiator OSPF sync sessions: only initiator requests may create missing sessions, and a rejection clears the old initiator role only when the remote session generation is unchanged. This fixes an unbounded RPC storm caused by a delayed route sync recreating a session after both peers relinquished the initiator role, with regression tests for session creation and response reordering.
2026-08-01 10:57:23 +08:00