fix bugs introduced by feat(buf): add BufPool, BufMargins, and BufList for zero-copy packet buffering #2625
cf. perf(quic-proxy): use BufPool and BufMargins in QuicSocket #2626 (comment)
Disable ping interval backoff for TCP hole-punched connections so idle
connections continue to send keepalive traffic every second. Preserve
the existing backoff and loss handling for other connections.
Keep randomized backoff above zero and cover the one-second schedule
and the existing backoff and loss retry behavior with tests.
* refactor(peers): model connection origins at admission
Record manual, direct, listener, TCP/UDP hole-punch, and attached
origins when constructing peer connections. Derive hole-punch state
from this origin instead of maintaining separate mutable flags.
Choose the one-second TCP hole-punch ping limit in PeerConn and pass
only a maximum interval to the pinger. Keep other origins on the
existing backoff schedule without inspecting tunnel type strings.
Keep origin selection internal and preserve the dedicated attached
admission paths. Update public admission callers and cover origin
propagation, relay restrictions, and ping interval limits.
Persist central network intent and compile complete per-device configs
with secure credentials, ACL policy, and dedicated Gateway runtimes.
Expose network, credential, device registry, and runtime observation
APIs.
Treat central management and external Console as alternative consumers
of the upstream ClientManager. Register central devices through a local
webhook handler and publish through existing runtime reconciliation.
Serialize public mutations with enrollment, reject direct credential
changes to managed instances, and retain REST revision invalidation.
Keep Gateway lifecycle publication in the central service and remove
obsolete incremental result bookkeeping. Restore persisted networks and
retire orphan runtimes through the same serialized publisher.
Add Core and protocol support for Gateway and WireGuard management,
including GUI bindings and serialized GUI config writes. Bootstrap IPv4
for DHCP-only networks and retain assigned addresses without peer IPv4.
Cover intent transactions, complete configuration publication, offline
recovery, authentication, revocation, Gateway lifecycle, and DHCP.
Validate central candidates before persistence using Core URL, config,
and portal-client rules. Reject unsupported peer schemes, unconvertible
proxy subnets, and invalid portal clients without changing live intent.
Expose existing pure Core validators without changing runtime behavior.
Gate API-facing credential validation on API-enabled Core builds so
minimal WASM targets do not reference omitted management types.
Preserve session-backed device views in external Console mode. Bound
Gateway admissions without cancelling transport upgrades, and cancel
pending peer handshakes before retiring runtimes. Batch registry reads
and bound runtime observation concurrency.
Randomize DHCP bootstrap and conflict retries, keep an advertised
current subnet, and select fallback subnets deterministically. Cover
concurrent startup, Gateway admission lifetime, and external Console
regressions.
Allow DHCP bootstrap with no remote routes. Count network members
through one tenant-scoped query without write transactions. Cover zero-
peer allocation and tenant/empty-network counts.
Preserve direct Web configurations and disabled states in central device
snapshots. Remove obsolete central rows atomically with membership
changes. Stop online managed instances before deleting or blocking
devices, retaining intent when shutdown or deletion fails.
Persist automatic IPv4 allocations separately from manual overrides so
subnet changes reallocate only automatic members. Normalize mapped proxy
routes to their advertised CIDRs when granting temporary credentials.
Cover publication, deletion rollback, subnet migration, and grant
updates.
Read central intent in deferred transactions so polling does not reserve
the SQLite writer lock. Test reads with an active writer and assert the
temporary-member secret constraint using a valid device fixture.
Persist patched WireGuard clients from the saved Web or GUI candidate
without a follow-up RPC. Preserve pending settings and ownership, and
cover disconnects and failed patches.
Resolve named credential and config mutations against the live Core
instance before checking central ownership. Forward authorized mutations
by UUID on the same session, including while network renames are
pending. Cover all mutation methods and preserve direct Console
behavior.
* feat(web-ui): add central network console and WireGuard management
Add network and device views with central membership, credentials, ACL
policy, temporary peers, and per-instance runtime details. Update
console navigation, styling, theme handling, and API clients.
Extend shared configuration and status components for central networks.
Add a WireGuard portal dialog for setup and running-device management,
with matching translations and network configuration types.
Use secure UUID generation on HTTP, discard stale member configuration
responses, and stop node-detail polling when the component unmounts.
Update frontend workspace dependencies and include component, dashboard,
configuration serialization, and central console end-to-end tests.
Add isolated real-Core E2E coverage for central data-plane traffic, ACL,
DHCP, recovery, device lifecycle, and native WireGuard clients. Record
all 59 functional checks with evidence and confirmed validation/UI
defects; keep runtime artifacts and test credentials out of Git.
Normalize protobuf logger levels and render translated labels correctly.
Cover all six levels across setting, reload, and language changes. Add
real configuration-rejection E2E checks for database and Core stability
and uninterrupted traffic, and record the resolved audit findings.
Gate central navigation and registry actions by console mode. Refresh
WireGuard settings on mounted status views and preserve explicit portal
listener endpoints. Explain the trusted permanent-member ACL boundary.
Cover external Console rendering, portal refresh retries and teardown,
and explicit IPv4/IPv6 listener exports.
Preserve PublicServer discovery when saving its settings, including when
its URL matches the Gateway. Cover renaming and endpoint edits in the
browser.
Display automatic member addresses without converting them to manual
overrides during edits. Derive offline address prefixes from the network
subnet. Add browser regression coverage and real-Core checks for direct
configuration preservation, temporary proxy mappings, automatic subnet
migration, and shutdown before device deletion.
Build enrollment commands from the configured API hostname, including
IPv6 and relative endpoints. Use the PublicServer connector in temporary
credential CLI and TOML exports. Add browser regression coverage.
Preserve form credentials across repeated normalization and GUI storage
reloads. Keep explicit form values authoritative over backend keys and
cover JSON persistence, idempotence, replacement, and clearing.
When another project uses easytier as a dependency on Windows, linking can fail because the build script emits relative native-library search paths such as easytier/third_party/x86_64/. These paths depend on the consuming project's directory layout instead of locating the libraries bundled with the easytier crate.
Build an absolute search path from CARGO_MANIFEST_DIR, followed by third_party and the target architecture directory (x86_64, i686, or arm64). This lets the linker locate the bundled libraries regardless of where the consuming project is built.
Forward remote packets without starting a relay session at the
intermediate hop. Encrypting forwarded Noise handshake replies with
that hop's session corrupts the end-to-end handshake and causes
credential connectivity tests to time out.
Disable automatic P2P in the admin/shared/credential test so direct
connections cannot mask broken forwarding. Cover unchanged forwarding
of handshake requests, replies, and encrypted data packets.
The current malloc_conf only sets retain:false, which prevents jemalloc from retaining virtual memory but does not actively purge dirty/muzzy pages. On long-running EasyTier nodes this can lead to inflated RSS because freed arena pages are not returned to the OS promptly.
This tunes the jemalloc decay parameters:
background_thread:true — enables background GC threads so purge happens without blocking allocation hot paths.
dirty_decay_ms:1000 / muzzy_decay_ms:1000 — return unused pages to the OS within ~1s instead of holding them indefinitely.
* fix(service): restart windows service indefinitely on failure
The windows service was installed with only the AutoStart start type
and no SCM failure actions configured. When the service failed during
boot (e.g. network not yet ready, config load error), it reported
SERVICE_STOPPED with a non-zero exit code and stayed stopped until
started manually, which is reported as no auto-start after reboot in
issue #1771.
Configure failure actions on install and update when restart is not
disabled:
- three Restart actions (1s/5s/10s delay) with reset period Never;
the SCM repeats the last action once the failure count exceeds the
actions array, so restarts are retried indefinitely
- set fFailureActionsOnNonCrashFailures so exits that report
SERVICE_STOPPED with a non-zero exit code (the error path in
win_service_event_loop) are also treated as failures; manual stops
still exit with 0 and do not trigger a restart
Also exit the service process with a non-zero code after reporting the
error status. Without it the process stayed alive after reporting
SERVICE_STOPPED and was only counted as failed after the SCM
force-killed it, adding roughly 30s of dead time to every retry cycle.
This matches the systemd path, which already generates Restart=always
with StartLimitIntervalSec=0. The --disable-restart-on-failure option
now also clears previously configured failure actions on windows.
Verified on a real windows host: crash failures and reported-error
failures both restart with the configured 1s/5s/10s cadence
indefinitely, manual stops are not restarted, and
--disable-restart-on-failure clears the actions.
* feat(gui): add start-on-boot menu entry pointing to service mode
Issue #1771 reports that users cannot find how to make EasyTier start
on boot. Auto-start is provided by service mode, but the GUI offered
no entry named after it, so the connection was hard to discover.
Add a "Start on Boot" item to the settings menu. It opens the mode
dialog with service mode preselected and shows an info message
explaining that enabling service mode registers EasyTier as a system
service that starts automatically at boot and keeps running in the
background.
When the dialog is opened with service mode preselected, the mode
watcher in ModeSwitcher can run before the default config/log dirs
have been resolved, leaving the fields empty and failing validation on
save. Fill them from the resolved defaults after mount in that case.
Add mode.autostart / mode.autostart_hint strings to the cn/en locales
in frontend-lib.
* fix(cli): stop collecting the --core-args flag into the service args
InstallArgs.core_args was declared without an explicit `long`, so
clap treated it as a trailing positional argument instead of a named
option. Passing `service install --core-args --daemon ...` therefore
collected the literal "--core-args" token into the value, and the
installed service was registered with an invalid command line that
failed on every start (observed on a real windows host: the binPath
contained `easytier-core.exe --core-args --daemon ...`).
Declare it as a real option (`long` + `num_args = 1..`) while keeping
allow_hyphen_values and the trailing semantics: --core-args must be
the last option of install and consumes everything after it.
The bare-positional spelling (`service install --daemon`), the only
correctly-working form before, now fails with a clear "unexpected
argument" error; scripts written against it need to add the
--core-args prefix.
Add unit tests covering the flag, `=` and mixed forms.
* fix(proxy): distinguish TCP flows sharing source ports
Key TCP NAT entries by source and destination so connections sharing a
source port can reach different targets independently. Allocate a unique
translated source port for each flow and restore the original addresses
and ports on the return path.
Keep connection identity during accept and cleanup so a replaced flow
cannot lose its new mapping. Give each smoltcp proxy mode its own
listener port and retain the original destination for mapped subnets.
Include regression coverage for SYN retries, concurrent insertion,
stale cleanup, translated port wraparound and exhaustion, plus the
recorded cross-platform and six-mode traffic validation.
* fix(proxy): preserve half-closed streams and pending KCP accepts
Propagate EOF to the opposite writer while keeping reverse traffic
alive until both directions finish. This allows a server to reply after
reading request EOF and a client to send after reading response EOF.
Pin kcp-sys to b37ee660 for retained pending accepts, receive-buffer
draining before EOF, early FIN handling, atomic state cleanup, and
keepalives that start only after the handshake completes.
Cover both shutdown directions, empty requests and buffered transfers
in gateway tests and the TCP/KCP/QUIC kernel/smoltcp integration matrix.
Include the half-close validation record and its remaining loss limits.
* fix(proxy): enable backward-compatible KCP control recovery
Pin kcp-sys to 26853356 and update both the root and OHOS workspace
lockfiles. The dependency negotiates reliable control per connection
while preserving legacy behavior when either endpoint lacks support.
New peers recover lost handshake and FIN control packets, acknowledge
FIN without closing the reverse writer, and retain bounded close state
for duplicate packets. Queue saturation cannot block reliable stateless
responses. Compatibility tests retain one historical baseline.
Include the protocol design and validation records for deterministic
loss, real old endpoints, three platforms, the full integration matrix,
and proxy traffic. Preserve mixed-version failures and the documented
minor scheduling issue rather than claiming legacy loss recovery.
Validate the OHOS workspace with --locked: 12 library tests and the
binding test build check pass, along with formatting.
* fix(acl): match ICMP replies in outbound allow records
Only outbound echo requests with code zero create ICMP response records.
Require inbound complete packets and first fragments to be echo replies
with code zero before using those records, so reverse echo requests
remain subject to inbound ACL rules.
Recognize ICMP fragment tails without reading their payload as an ICMP
header. Allow them to use existing address and protocol records without
creating new records. Walk IPv6 extension headers to locate ICMPv6 and
avoid inferring an unknown tail protocol from its payload.
Add IPv4 and IPv6 unit coverage for message direction, code, truncation,
and fragment parsing. Add a three-node regression for bidirectional
requests and ordinary and fragmented replies under inbound default-drop.
Fixes#2545
* fix(acl): keep IPv6 parse failures subject to rules
Retain source and destination addresses when an IPv6 extension header
is truncated, exceeds the payload, or repeats a fragment header. Treat
these packets as unspecified protocol instead of entering the global
parse-failure allow path. Apply the same handling to truncated transport
headers after an extension header.
Classify non-ICMPv6 fragment tails as unspecified so their data cannot
be parsed as TCP/UDP ports or create temporary response records. Keep
ICMPv6 tail authorization through existing response records.
Exercise the ACL entry point with malformed headers and short and long
fragment tails under default-drop and default-allow policies, including
port allow rules that must not match tail payload bytes.
* fix(web): fence managed config runtime reconciliation
Keep runtime reconciliation tied to the currently authorized session so
stale connections cannot mutate a replacement session runtime.
Accumulate only contiguous dirty IDs and load their latest SQLite state.
Require the applied revision to match the earliest Patch base and the
persisted revision to match the latest target. Otherwise, reconcile the
full desired state.
Use separate runtime-state and config-cache epochs. Managed updates can
reuse observed configs; direct mutations invalidate them. Update sync
documentation to match.
* fix(web): interrupt validation retry on state changes
Track meaningful validation state changes separately from periodic dirty signals. Applied revision changes wake a failed validation immediately, while heartbeat-driven revalidation retains the retry backoff.
Treat Notify as a wake-up hint and recheck the state-change epoch after every wake so stored permits and periodic heartbeats cannot cause retry storms.
* fix(web): retry unconfirmed connected webhooks
Retry node-connected webhook delivery on retryable errors with a
short 100ms/500ms backoff and give up immediately on non-retryable
errors. Re-check that the session still owns the connection before
every attempt and before recording the delivery, so a replaced
session can no longer record a stale connected binding.
* fix(web): fence disconnects by session ownership
Return whether session removal actually removed the current route owner, and emit disconnected only for that owner. Replaced sessions can no longer invalidate a newer connected route.
* fix(web): hot-patch managed hostnames
Include hostname changes in the hot-patch path instead of falling
back to a full restart. When a full overwrite run is required and
the desired config has no hostname, inherit the current runtime
hostname so an unmanaged value survives until it is explicitly
cleared.
Read back the runtime config after an overwrite run and verify it
converged instead of assuming the desired state was applied.
* fix(web): retry transient runtime reconciliation failures
Keep the per-session managed runtime reconciliation worker alive when a
single database round fails. Retry from the next heartbeat so persisted
managed revisions can still converge after restart-time contention.
Reserve terminal worker shutdown for destroyed session or storage state,
and cover recovery after a transient revision read failure.
* fix(web): accept omitted hostname after runtime apply
Release 2.6.4 omits hostname from config readback when it matches the device hostname. Trust a successful hostname mutation only when the returned field is absent, while continuing to verify every other field and rejecting explicit mismatches.
* fix(web): ignore unmanaged runtime device names
Windows release 2.6.4 generates a random interface name when the managed config leaves dev_name empty. Exclude that runtime-owned value from reconciliation unless the desired config explicitly sets a non-empty device name, preventing endless overwrite restarts.
* feat(web): report failed network instances to console
Expose stopped Core instances with startup errors in heartbeats.
Merge Core failures with direct managed-run RPC failures in easytier-web.
Send failed instance IDs during token validation without error text.
Prune local run failures when managed configs are deleted.
* fix(web): distinguish unknown runtime application state
Track whether the current session has observed its applied revision
separately from the optional revision value. Report this fact through
validate-token so Console can preserve application state across
receiver restarts while recognizing deliberate pending mutations.
* feat(web): configure heartbeat timing from server
Heartbeat responses now provide the interval and RPC timeout.
Legacy servers use local defaults and remote values are clamped.
Web configuration and session receive timeout follow the policy.
* fix(web): reject inactive control sessions
Route control RPCs by machine id only to sessions whose RPC manager
is still running, so a session that has been stopped or replaced
can no longer receive control traffic addressed to the device.
* fix(core): filter network info before collection
When a collect-network-info request names specific instances,
collect those instances only instead of collecting every instance
and filtering the result afterwards, so unrequested instances no
longer run per-collection work on every request.
* feat(web): enable focused runtime diagnostics
Enable easytier-web info logs by default while preserving explicit log configuration. Record startup settings, session lifecycle, failed instance changes, webhook queue and request latency, and managed runtime operation timings for production diagnosis.
* fix(web): preserve managed revision across reconnects
Keep one runtime identifier for each Core WebClient lifetime.
Reuse its managed runtime state after transport reconnects.
Retain applied revisions and reconcile hints while disconnected.
Preserve runtime epochs so stale work cannot mark a revision applied.
Reject stale sessions from reclaiming routes after reconnect.
Core or Web restarts and legacy clients still use unknown state.
Immediately revalidate a restored revision after authentication.
Document local management RPC drift as an accepted trade-off.
This lets Console converge without waiting for periodic validation.
* fix(web): satisfy clippy across managed config sync tests
Scope managed runtime guards to blocks in runtime revision tests so
no std MutexGuard is held across await points, return the applied
revision directly instead of through a let binding, and pass
WebhookValidationInput to request_heartbeat_validation instead of
expanding it into eight separate arguments.
* fix(core): stop reporting failed instances as running in heartbeats
A stopped instance with a startup error appeared in both
running_network_instances and failed_network_instances, so the
server treated it as running and never re-ran its managed config.
Exclude failed instance ids when building the running list so the
reconciler restarts them.
* fix(core): close missed-wakeup race in instance state changes
wait_for_change created the Notified future before reading the
generation but only registered it when awaited. A change landing in
between fired notify_waiters with no registered waiter and delayed
the heartbeat by a full interval. Enable the future before reading
the generation so every change wakes a waiting heartbeat.
* fix(web): address review findings
Fence webhook validation and connection transitions against stale
state, redact credentials from default-level logs, and stabilize
runtime reconciliation:
- Record connected bindings only while the session still owns the
machine route, and skip disconnect compensation once a replacement
owns the route so a stale disconnect cannot revoke it.
- Discard webhook validation results when the change epoch moved
during the HTTP round, so a stale rejection cannot invalidate the
current session.
- Drop user_token fields from info and warn logs that became
visible with info-level defaults.
- Restore a hostname omitted by the 2.6.4 readback into the cached
runtime config after a successful mutation, so later rounds stop
re-sending the same hostname patch.
- Reconcile running web configs when no revision is tracked so
legacy unrevisioned updates converge, and wake sessions for
unrevisioned full updates instead of waiting for the next
heartbeat.
* chore(go): regenerate web proto bindings for heartbeat fields
Add failed_network_instances, support_heartbeat_policy, and the
heartbeat policy response fields to the checked-in Go bindings.
Other proto packages are left as-is because their drift predates
this change.
* fix(web): redact user tokens from positional log arguments
Three runtime reconciliation info logs and the user lookup error
contexts printed user_token through format arguments, which the
earlier field-syntax redaction missed. The reconcile log now fires
every round for unrevisioned machines, so remove the token from
these messages as well.
* fix(web): fence stale validation and runtime reconcile rounds
Check webhook validation epochs while holding the session write lock,
so stale success and rejection responses cannot change session state.
Advance the runtime epoch for unrevisioned full config updates, and
exclude failed instances from heartbeat and RPC reconciliation lists
so stopped instances are restarted instead of repeatedly hot-patched.
Release test read guards before awaiting validation apply calls. Set
up the no-pending condition before asserting that an applied revision
is a no-op, and verify that its runtime epoch remains unchanged.
Validation: all 137 client_manager tests passed.
* test(credentials): cover P2P with active VPN portal
Model an admin and temporary credential peer connected as a foreign network through a public server with data relay disabled. Verify their direct connection can be replaced after a WireGuard portal client comes online.
* test(credentials): stabilize two-admins failover assertions
The two-admins non-reusable credential test could fail on slow
convergence: after dropping the winning peer it relied on a single
route sample passing a bare AND condition, then re-asserted the same
expectations through one-shot checks seconds later. A transient route
flap in that window (for example a briefly resurrected winner route
from stale conn info) turned a passing convergence into a hard assert
failure. This matches the 48.9s CI flake of
credential_non_reusable_across_two_admins_allows_only_one_peer
observed on 2026-08-12.
Changes:
- wait for bidirectional admin connectivity (AND) with a 20s budget
before issuing the credential, instead of a one-directional OR
- replace the failover wait_for_condition with
wait_stable_failover_visibility_on_admins, which requires three
consecutive samples of loser-present and winner-absent on both
admins within the same 60s budget and logs every sample
- enrich the stable-single-winner timeout message with per-admin
visibility flags and elapsed time for triage
All existing contracts are preserved; only observation windows and
diagnostics change. Validated in the rust container: three passes at
normal speed (54.1s / 53.8s / 53.1s) plus one slow-convergence round
(172.7s) that would have raced the old one-shot sampling; it now
passes with failover samples logged. cargo fmt and clippy -D warnings
clean.
* 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.
* 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.
* 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.
* 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.
* 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.
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.
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>
* 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
* 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.
* 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.
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.
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>
* 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.
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.
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.
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.
Overhaul the WASI guest data plane for throughput and add the host
capabilities it relies on. The externally driven Tokio runtime now
runs its timer pre-turn only when a tracked deadline has expired,
and all WASI-reachable timers (STUN, port mapping, WebClient, UDP
flow cleanup) go through the portable time facade so conditional
timer driving cannot starve them.
Data plane:
- Move read/write deadlines onto TCP and UDP resources with one ABI
setter per direction, reuse a single expiration timer per
resource, and drop timeout arguments from the four hot data-plane
submissions (ABI v3). Checked absolute instants treat
unrepresentable finite timeouts as unbounded instead of panicking.
- Batch host traffic: vectored TCP frame writes combine queued
slices into one host operation, and reads request a bounded 64
KiB while retaining excess bytes in the stream buffer.
- Complete TCP writes inside the guest with cancellation-safe
writes, reporting the completed prefix before honoring
cancellation or timeout so hosts never replay bytes.
- Repoll smoltcp egress immediately on zero poll delay, enlarge
virtual UDP receive queues to 128 KiB payload with 128 metadata
slots, and bound UDP session receive buffers to 8 KiB plus one
byte while keeping oversized-datagram detection.
Host integration:
- Add optional algorithm-neutral AEAD seal/open imports with the
ring backend as fallback, and pin the ring AES-128-GCM wire vector
so the Go host stays interoperable.
- Forward instance events to hosts through one best-effort,
synchronous, non-blocking import.
- Add a repository-owned build entry point for the Go host artifact:
Binaryen 131 at -O4 with cached, SHA-256-verified official
archives.
* perf(core): make data-plane idle check constant time
Avoid scanning every DashMap shard for each peer packet when no data-plane flows are active.
Publish the flow count before insertion and release it after removal so an Acquire load is a safe O(1) idle signal. Reject count overflow and underflow instead of silently saturating.
* test(perf): add repeatable two-node netns benchmark
Create isolated underlay namespaces, pin both EasyTier cores and iperf3 endpoints, and measure a single TCP flow in both directions over either UDP or TCP peer transport.
Keep every iperf3 JSON result and emit directional medians while cleaning up processes and namespaces on every exit path.
* perf(tcp): preserve native owned stream halves
Let each VirtualTcpSocket adapter consume itself into independent read and write halves. Portable adapters retain the generic shared split as a default.
Use lock-free Tokio owned halves for native TCP and Unix streams so tunnel I/O no longer takes the generic split mutex on every poll. Cover full-duplex traffic and write-half shutdown.
* perf(packet): preserve ownership across the Host seam
Introduce an opaque, move-only HostPacket that retains core packet storage while exposing only the raw IP payload. Clear private headers before handing storage back to a native TUN adapter.
Use an ownership-preserving bounded channel for native ingress and egress. Keep explicit copy adapters for Vec and WASI boundaries, and verify allocation identity, backpressure, shutdown, and end-to-end delivery.
* perf(udp): preserve packet ownership through sessions
Carry EasyTier tunnel packets through UDP session queues as owned values. Reuse the existing tunnel header for session framing instead of copying payloads into a second packet and rebuilding them on receive.
Keep completion delivery for the public datagram socket API while removing the unused completion channel from streaming tunnel sends. Avoid the unconditional receive-side clone before QUIC routing is known.
* perf(peer): publish packet filters as immutable snapshots
Replace per-packet async and synchronous registry locks with ArcSwap snapshots. Permanent filters now need no activity checks, while managed registrations retain explicit acquire/release visibility.
Closing a managed registration marks it inactive before atomically removing it. Existing snapshots keep in-flight filters alive, and registration mutations prune inactive entries while preserving newest-first order.
* perf(instance): give native hosts direct packet egress
Let the core create one bounded HostPacket channel and transfer its receiver directly to a PacketEgressHost during startup. Native TUN runtimes now consume that receiver without the intermediate PacketSink channel and forwarding task.
Keep PacketSinkEgress as the compatibility adapter for callback and test hosts, and make receiver installation one-shot across desktop, mobile, and disabled runtimes.
* perf(crypto): restore accelerated native AEAD backends
Move Ring and OpenSSL implementations behind the core Encryptor seam.
Portable builds continue selecting only supported backends.
Restore historical precedence: OpenSSL, Ring, then RustCrypto. Keep
backend availability consistent across secure transports and cover
fixed-nonce wire compatibility between implementations.
* perf(udp): receive native datagrams into owned buffers
Extend the portable UDP socket seam with an owned-datagram receive path.
Keep a compatible default for portable hosts. Native Unix sockets write
recvmsg output directly into the final BytesMut allocation.
This removes the per-packet stack-to-heap copy introduced by the portable
socket boundary without exposing native socket resources to core.
* perf(data-plane): remove portable hot-path overhead
Restore native throughput lost while generalizing the host and UDP
session layers.
Read packet policy once per send, update traffic counters through
registry guards, and preserve packet ownership while UDP dispatch
borrows stable session state.
Move UDP shutdown monitoring into a control task so forwarding avoids
a select future per packet. Bound native datagram storage to 8 KiB,
reject oversized sends, and drop truncated Unix receives.
Keep accelerated AEAD selection warning-free when portable crypto
features are also built. Cover session bounds, truncation, and idle
shutdown with regression tests.
* fix(udp): preserve portable datagram receive semantics
Keep the public portable receive capacity at the theoretical UDP
maximum instead of silently shrinking it to the native fast-path limit.
Apply the 8 KiB session boundary after a complete portable receive,
so Windows cannot turn an oversized datagram into a fatal listener
error and other adapters cannot dispatch a truncated prefix.
Cover dropping an oversized packet while the same portable socket
continues to deliver the following valid datagram.
* fix(ci): align feature gating with backend selection
Compile the Ring implementation in production only when OpenSSL is not
selected, while retaining it for cross-backend unit tests.
Remove stale test imports and assert UDP dispatch results so the strict
workspace Clippy job passes without suppressing diagnostics.
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.
* bench: add packet bytes extraction Criterion benchmark
Adds a Criterion benchmark under easytier/benches/ covering
ZCPacket::payload_bytes and tunnel_payload_bytes at 1280/4096-byte payload
sizes, using iter_batched so ZCPacket construction stays in the setup phase
and is excluded from the timed region.
- Register the [[bench]] entry in easytier/Cargo.toml.
- Document the bench and PACKET_BYTES_* env vars in benches/README.md.
* perf: reduce packet buffer slicing churn
Replace BytesMut::split_off with Buf::advance in ZCPacket bytes
extraction paths (payload_bytes, tunnel_payload_bytes, convert_type,
drop_foreign_header) and in TunZCPacketToBytes, and simplify the
copy_from_slice in new_from_payload.
When the buffer is in its unique (VEC) representation, split_off promotes
it to the shared (ARC) representation, allocating a Shared control block
and bumping the refcount on every call, and pins the buffer in shared
mode. advance only mutates the in-place ptr/len/cap fields, avoiding that
allocation/refcount churn on the TX hot path. The byte data itself is not
copied by either path.