Remove the unconditional easy-symmetric array reset so retries reuse the
existing 84-socket pool. Keep refilling consumed slots to avoid
permanent depletion after failed transport admission.
Preserve the predictable scan budget for multiple public IPs instead of
multiplying it by address count. Defer prediction freshness and coverage
changes until they can be addressed within the existing pressure limits.
Verify that two failed easy-symmetric attempts reuse all candidates,
that consumed candidates still recover, and that a 50-target prediction
budget with two public IPs retains the original 300-datagram total.
Move punched sockets out of the candidate array before publishing them,
and keep transaction registration and results in one map. This prevents
receiver cleanup from racing with socket reuse or port rebinding and
removes the unsafe reference-count assertion.
Refill hard-symmetric candidates consumed before transport admission.
Refresh easy-symmetric candidates before taking a new STUN baseline so
prediction covers their new mappings. Keep probing a selected both-easy
socket during session setup so a peer still receiving raw HolePunch
packets can switch to its listener even after one-way probe loss.
Keep predicted ports within 1..=65535, permit a single remaining port,
and cover the full prediction interval for every advertised public IP.
Preserve the existing random scan budgets, retry policy and wire format.
Add regression coverage for candidate depletion, prediction retries,
receiver handoff, continued probing, multiple IPs and port boundaries.
Validate all 877 core tests, full workspace Clippy and NAT mapping
probes that exchange payloads through the production UDP session code.
Related to #2766; its field root cause remains unconfirmed.
* fix(windows): preserve Wintun identity across restarts
Update rust-tun to wait for Wintun orphan cleanup before recreating a
requested adapter GUID. This fixes intermittent device creation failures
after forced termination while keeping the same network identity.
The dependency change also handles WOW64 without a separate device
removal program and rejects adapters with mismatched identities.
* fix(peers): don't report unmeasured connections as 1 ms to peer center
A freshly established connection has no latency sample yet, so its
WindowLatency reports 0 and direct_peer_info advertised the peer as
max(1, 0) = 1 ms. With latency_first enabled, every node could then
route through a slow or lossy link until the next report (up to ~60 s),
and a flapping link re-advertised 1 ms on every reconnect.
Skip connections that have no latency sample yet. A peer with no
measured connection is left out of the report; it is reported once its
first sample arrives, because the peer list changes then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci(mobile): use the Android command-line tools build ID
setup-android expects a download build ID, not the displayed tools
version. The value 12.0 becomes 12 and produces a URL that returns 404
when the runner cannot reuse a preinstalled SDK.
Request build 11076708, which provides the same command-line tools
12.0. Keep the runner and other tool versions unchanged.
Validate the workflow YAML and confirm the archive URL returns HTTP 200.
---------
Co-authored-by: imoulei <188999141+imoulei@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
The public IPv6 reconnect fixture kept its process runtime but created
a fresh shared-TUN registry for each client. A replacement could open
the same named device while the old registry's asynchronous cleanup
still owned it, failing with EBUSY in CI.
Keep one registry in PublicIpv6Lab and use it for initial and replacement
instances, matching NativeInstanceFactory. Existing registration IDs
and the shared NIC mutex coordinate cleanup and replacement. Preserve
the lease, route, and ping assertions without changing their timeouts.
Validate with 20 reconnect stress runs, 4 public IPv6 cases, 7 shared-TUN
integration cases, formatting, and strict workspace clippy. The baseline
also passed 30 local runs, so this is not a deterministic local red/green
reproduction. Runtime stop semantics are unchanged.
* perf(common): cache interface snapshots on the Windows bind path
Each bind with BindDev::Auto resolved the interface name via a full
NetworkInterface::show() enumeration, and on Windows setup_socket_for_win
then resolved the same name to an index through a second enumeration.
With many adapters (VMware / Hyper-V / APIPA leftovers are common on
Windows), one connect round turned into O(candidates) synchronous
enumerations on the async worker: issue #2735 measured ~4.9s of
candidate preparation against a 2s connect budget, and even on a healthy
8-adapter machine the loop cost ~124ms for 10 candidates.
Reuse a snapshot of the interface list with a 1s TTL in both places.
Measured on an 11-candidate machine: candidate bind loop drops from
~146ms to ~0.5ms; on machines with slow enumeration the win is
proportionally larger, keeping candidate preparation inside the connect
budget. Complements #2736, which offloads the remaining syscalls to the
blocking pool.
* fix(network): coalesce interface refreshes and limit caching to Windows
* fix(windows): isolate bind cache and correct lookup diagnostics
Dropping a data-plane TCP stream removed its socket without sending FIN,
leaving peer reads and exit-node forwarding tasks waiting indefinitely.
Transfer released smoltcp sockets, flow registrations, and stack leases
to the existing reactor. Preserve FIN retransmissions and TIME-WAIT
after application handles close, drain unread peer data, and reclaim
sockets on completion, an absolute close deadline, or reactor shutdown.
Reuse session resource closure to cancel pending reads and writes. Keep
the Go host on the existing resource-close ABI and refresh its embedded
WASI guest, protobuf bindings, and matching provenance together.
Cover blocked writes, expired deadlines, lost FIN and ACK packets, close
deadlines, reactor shutdown, and final flow and stack lease release.
Validate with 77 Rust tests, the Go suite, fmt, and strict workspace
clippy.
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
Periodic minimum-RTT reselection can move a busy UDP stream
to an idle TCP connection and back as their queueing delays
change. Bind active unfragmented TCP/UDP flows to their selected
connection so the five-second reselection applies to new flows and
unkeyed packets.
Keep peer listener reconstruction alive after runtime bind failures.
Withdraw failed bindings from the running registry before retrying.
Web configuration listeners retry listen after accept errors with a
fixed 100 ms delay, until their owner is stopped.
Make TCP and UDP listen replace the old binding while retaining the
assigned port. Keep accept ownership unchanged for other consumers.
Release closed UDP sessions' layer references and wake classified
accepts on receive shutdown so peer listeners can be reconstructed.
Let the existing WireGuard Portal reader replace its socket through a
shared endpoint slot, retaining the engine and client sessions. Discard
oversized Windows UDP datagrams before they reach listener recovery.
Cover repeated binding failure, cancellation, fixed ports, old socket
release, classified shutdown ordering, and successful Web recovery.
* fix(icmp): open proxy sockets only when forwarding echo requests
Keep local no-TUN echo replies independent of raw socket permissions.
Create the socket and receiver on the first outward request, so newly
added proxy networks and exit-node traffic work without eager sockets.
Serialize concurrent initialization and socket publication with stop.
Roll back partial resources on failure or cancellation, and retain an
opened socket until shutdown for in-flight replies and later requests.
Cover local replies, dynamic CIDR updates, concurrent first requests,
exit-node forwarding, failed opens, cancellation and shutdown.
* fix(proxy): start forwarding after the first dynamic subnet update
A TUN instance started without proxy networks skips the packet proxy.
Runtime updates previously published new CIDRs without starting it, so
the first dynamically added subnet could not forward packets.
Reapply the existing startup predicate after configuration publication.
Run proxy startup on the instance executor so its sockets and tasks
survive a temporary configuration caller. Roll back partial startup on
cancellation as well as errors, allowing a subsequent update to retry.
Cover first-subnet forwarding, mapped CIDRs, removal and re-addition,
system forwarding, bind failure, cancellation and executor ownership.
* feat(core): share an instance runtime with CLI worker control
Add --shared-runtime to easytier-core and reuse --multi-thread-count.
Default shared workers to available CPUs while retaining dedicated
runtime defaults. Consume shared runtime flags at process scope so
config files remain writable and keep their own thread settings.
Share executor ownership across the factory and its instances, keeping
normal instance capabilities and independent shutdown paths. Cover
worker selection, instance deletion, hot addition, and final teardown.
* docs: document shared native runtime ownership
Describe the opt-in shared executor alongside the default per-instance model, including worker selection and last-owner shutdown semantics.
Register nonblocking raw ICMP sockets with Tokio instead of creating
a blocking task for every receive. Keep the raw packet layout and
network namespace binding unchanged across native platforms.
Cancel pending sends and receives when the socket closes, without
relying on shutdown of an unconnected raw socket.
Cover receive cancellation, closing multiple waiters, and repeated
loopback echo fanout across 30 raw sockets without blocking workers.
* fix(wasi): advertise the compiled smoltcp stack and keep ring out of the guest
A WASI guest has no OS TUN device, so the TCP subnet proxy can only forward
through the user-space stack. The capability probe in the WASI runtime only
set smoltcp_available under wasm-host-tunnel-outbound, so a plain core build
left force_smoltcp false and the proxy fell back to the kernel-listener path,
which rewrites the SYN onto a device that does not exist and lets every entry
expire as "syn nat entry expired".
- Add CoreInstanceHostConfig::set_smoltcp_stack and call it from the WASI
runtime whenever the stack is compiled in. The setter only raises the flag,
so the host-tunnel mode keeps working independently of the feature.
- wasi-crypto-offload no longer pulls ring-crypto: the host performs AEAD for
the guest (easytier_host.crypto_aead_seal/open), and a software backend adds
no value inside WASI.
- build-wasi-core.sh now builds with --no-default-features and an explicit
list. endpoint-discovery was the only default that pulled rustls/ring; ring
compiles C shims for wasm32-wasip1 that surface as env.ring_core_* imports,
which the Go host cannot provide.
Covered by two unit tests for the setter.
* style(wasi): remove extra blank line from runtime module
Remove the extra blank line at the end of the WASI runtime module.
This restores the repository-wide cargo fmt check for PR #2734.
---------
Co-authored-by: 刘金发 <liujinfa9242@dingtalk.com>
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
* fix(peer_manager): route by destination rewritten in the nic packet pipeline
NIC packet filters receive &mut ZCPacket and may rewrite the IP header (e.g. a NAT filter that maps a virtual peer address to an address on the local subnet). send_msg_by_ip parsed the destination before running the filter pipeline and then routed by that stale address, so rewritten packets were looked up by the pre-filter destination and dropped as unreachable.
Re-parse the destination after run_nic_packet_process_pipeline and route by the rewritten address. Expose instance::packet_io as pub(crate) so peers can reuse the parser.
* test(peer-manager): cover NIC destination rewrite routing
Exercise IPv4 and IPv6 delivery through three peers connected by memory
tunnels. A NIC filter rewrites the destination without setting a peer
ID; verify delivery to the rewritten peer and no delivery to the
original.
Cover original destinations with and without an existing route. Wait for
route convergence before sending each packet once, without data retries.
Both regression tests fail with the PR routing fix removed and pass with
it restored. All 37 peer-manager tests and formatting checks pass.
Scoped Clippy completes with warnings in pre-existing code.
---------
Co-authored-by: ldoubil <ldoubil@users.noreply.github.com>
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
* test(limiter): stop racing the token bucket refill clock
test_initial_state drains a 1000 byte bucket that refills at 1000
bytes/s and then asserts that one more token is rejected. try_consume
refills from Instant::now() first, so the assertion only holds while
the thread runs within one millisecond of the drain; one millisecond
of scheduling delay is enough for the token to appear.
Freeze the lazy refill by moving last_refill into the future so no
token can appear between the two calls, and apply the same freeze to
test_consumption, which had the same race with a 100 ms window.
* test(limiter): pass the refill instant explicitly instead of faking the clock
Moving last_refill into the future only widened the window: once that
offset passes the bucket refills again, so the tests still depended on
the wall clock.
Split the locked consumption into try_consume_locked(state, tokens, now)
and have production take the lock first, then read the clock. Both
tests take one instant and reuse it for every call, so no refill can
happen between the assertions.
foreign_network_advertises_listeners_for_direct_connect waits on four
separate conditions, and wait_for_condition panics with a bare
"Timeout" from inside the helper. A red CI run therefore never says
which gate failed, which is what made the last recurrence on
fix/2739-platform-verifier hard to attribute.
Add wait_for_condition_named and use it for the four gates so the
panic names the condition. No behavior change: same polling
interval, same 10s budget.
* test(connector): label the convergence waits of the direct connect test
foreign_network_advertises_listeners_for_direct_connect failed in run
39903378976 with a bare "Timeout" from wait_for_condition, so the log
cannot tell which convergence gate was hit: each of the two node pairs
has four waits (remote sees client-net, client learns the route,
remote learns the client peer, direct connection established).
Add wait_for_condition_with_reason and use it for all four waits, with
the node pair and the step in the message. Timeouts and assertions are
unchanged.
* test(connector): wait for the reply route the router actually uses
The reverse-route wait added in #2738 checked the foreign network peer
list, which only holds direct connections. The packet router also
routes through the route table, so the wait could time out although a
reply would have been routed, and a direct connection often appears
only later in the flow.
Call the router lookup (get_gateway_peer_id for the client peer on the
remote foreign network) through a test accessor and wait until any
gateway is available.
CI run 39911533854 timed out on the previous condition for both tcp
and udp.
* test(connector): wait for the reply route the router actually uses
The reverse-route wait added in #2738 checked the foreign network peer
list, which only holds direct connections. The packet router routes
through the route table as well (get_gateway_peer_id), so the wait
could time out although a reply would have been routed, and a direct
connection often appears only later in the flow.
Add a test accessor that performs the router lookup on the remote
foreign network and wait until any gateway is available.
Drops the gate labels from this branch, they are covered by #2749.
CI run 39911533854 timed out on the previous condition for both tcp
and udp.
* test(connector): report remote routing state when get_ip_list times out
CI run 39921692334 failed after the gateway wait succeeded: the RPC
itself timed out with all waits green, so the gateway was available
but the reply still did not arrive.
On RPC error, print the remote client-net state next to the error:
the gateway chosen for the client, whether that gateway is a direct
peer, and the peer ids currently connected. This makes the next
failure attributable without changing any assertion or timeout.
* test(connector): also report the origin mapping of the chosen gateway
A wrapped foreign network packet is forwarded by mapping the gateway
foreign peer id to its origin (parent network) peer id and sending it
through the parent peer map, so a gateway that is not directly
connected does not by itself mean the reply is dropped.
Extend the RPC failure snapshot with get_origin_my_peer_id for the
chosen gateway and whether the parent peer map can route to that
origin id.
* test(connector): capture the foreign network forwarding decisions
The routing snapshot taken after the timeout cannot explain a lost RPC
reply: it is sampled after the timeout, re-queries the state (which may
converge later) and does not show whether the request reached the
remote at all.
Install a test-scoped tracing subscriber that keeps debug events from
easytier_core::peers, so a failing run prints the forwarding decisions
(dropped relay data, missing origin peer id, failed direct send) when
they happen. Only this test process is affected.
* test(connector): isolate foreign listener discovery
The foreign DirectConnectorRpc handler collects interface addresses in
the default network namespace. Although the three-node fixture gives
each instance its own namespace, the RPC still observes host bridges
and veth interfaces created and removed by concurrent tests.
Add both protocol cases to the existing exclusive override for host
interfaces. Preserve the reply route readiness check, RPC deadline,
listener assertion, and final direct connection assertion.
Diagnostics with 32 test threads on four CPUs showed interface
enumeration delaying the RPC handler by over two seconds. With this
override, the same mixed workload kept all observed handlers below
1.1 ms.
Remove the temporary routing snapshot accessors, failure dump, and
test tracing setup. Keep only the helpers used by the readiness check.
The relay and instance-receive rate limit tests compared TCP
application payload against the configured limit, but the limiter
charges EasyTier payload including the inner IP and transport
headers. That left the 800 KiB/s case only about 2% above its lower
bound, so ordinary variance pushed it under (717 vs 720).
Count the bytes each limiter charges and assert that rate within 5%
instead. TokenBucket now tracks metered bytes, exposed through the
limiter trait and hidden test accessors, and the benchmark samples
the counter once the initial burst is drained. Keep the payload
upper bound, which the encapsulation mismatch does not invalidate,
so a limiter whose rejection the forwarding path ignores still
fails the test.
Cargo exposes an enabled optional dependency as a rustc cfg even when
dep:smoltcp hides the implicit feature. cargo-auditable later passes
that cfg to cargo metadata, which rejects it.
Make smoltcp a core feature that selects proxy-packet, including the
dependency settings required when enabled on its own.
Fixes#2745
Reuse listeners by protocol and bind address when their destination
changes, keeping established TCP streams connected to the old target.
Prepare all new sockets before persistence or configuration publication
so bind and storage failures leave committed forwarding usable.
Commit prepared resources with shared TOML and runtime publication.
Serialize UDP target changes with flow insertion, and track response
tasks through eviction so removal waits for every socket reference.
Keep cleanup and its operation lock alive if the caller stops waiting.
Add TCP and UDP regressions for target changes, failed updates, retries,
established streams, immediate rebinds, and cancelled cleanup waiters.
* fix(udp): honor exclusive bind defaults for native sockets
Stop enabling SO_REUSEADDR implicitly for UDP listeners, proxy NAT
sockets and port forwards. Let UdpBindOptions own the reuse request so
independent receivers cannot bind the same endpoint or receive another
network instance's traffic.
Cover occupied IPv4/IPv6 endpoints, release and rebinding, and packet
isolation across concurrent ephemeral listeners, NAT and forward sockets.
The occupied-listener regression fails on the parent revision; all nine
native UDP socket tests pass with the fix.
* test(udp): reject duplicate WireGuard listener bindings
Update the WireGuard listener test to expect AddrInUse now that native
UDP sockets honor the exclusive bind default. Allocate the first port
from the kernel so the test does not depend on a fixed endpoint.
* test(udp): preserve dual-stack WireGuard port sharing
Verify IPv6-only and IPv4 wildcard listeners can use the same port
number while exact duplicate endpoints remain rejected. This preserves
the dual-stack bind scenario without enabling UDP address reuse.
Restore the CLI's default instance-manager diagnostics without enabling
unrelated module logs. Include failed instance IDs and their saved error
chains in the final exit error.
Attach the listener URL to required-listener failures and render full
error chains in startup logs, so bind failures expose their OS cause.
Extend listener regression coverage and document the default CLI targets.
The expiry test sampled time before sync_root_key established its grace
period. Scheduling delays over 3 ms could leave the asserted timestamp
inside the grace window and fail the test.
Use the actual grace deadline to check acceptance just before expiry and
rejection at expiry. Use a fresh sequence number for the rejection check
so replay detection cannot hide a broken expiry boundary.
* fix(web): stop replaying heartbeats after token validation
Wake runtime reconciliation once when authentication establishes the
session. Repeated successful validation, including a binding refresh,
must not replay a heartbeat whose instance list predates a completed
Run.
Use existing notifications as wakeups and read the latest session
heartbeat when reconciling, so delayed validation and config callbacks
cannot replace a newer runtime observation with a cached payload.
Cover one initial wakeup, 99 successful validations without replay, and
a subsequent real heartbeat that still wakes runtime reconciliation.
* fix(web): wait for authoritative config during receiver takeover
Keep the desired webhook revision on the authenticated session. Gate
runtime reconciliation, mutations and applied acknowledgements until the
receiver has that exact revision, preserving running Core instances
while its local configuration is missing or stale.
After committed Full/Patch updates, request current validation authority
without letting delayed publication callbacks or validation responses
restore an older target. After the connected ACK establishes the route,
wake existing validation once when configuration is still unavailable.
Cover missing and obsolete state, authoritative empty sets, stale
rounds, callback races, live Core handoff and an explicit
1000-connection takeover exercise with an independent mount.
* fix(gui): stop Windows processes before installing updates
Keep the NSIS installer in the current user context. Detect the target
GUI and service before updating, and elevate a temporary helper only
when stopping them requires administrator access.
Stop services through SCM and wait for their processes to exit before
closing the GUI. Retain the helper until installation completes so it
can restore a previously running service without another UAC prompt.
Restore the service on cancellation when its executable still exists.
Prepare before Tauri launches an old uninstaller, and cover passive,
silent, and direct uninstall paths. Add Windows service lifecycle tests
with isolated executable fixtures.
* fix(windows): report stopped before normal service exit
Report SERVICE_STOPPED with no accepted controls before exiting after
an explicit stop or successful completion. Reporting Running at exit
makes SCM treat a normal stop as a failure and schedule a restart.
This fixes the service lifecycle independently of installer changes.
* fix(gui): prevent old services restarting during Windows updates
Temporarily disable service startup before requesting a stop, then
restore the original startup mode when the update finishes or exits.
Released services can exit without reporting Stopped, causing SCM to
queue a recovery restart even after an explicit stop request.
Extend the Windows fixture to reproduce that exit behavior with failure
recovery enabled and check that startup settings are restored. Record
the minor directory-change and GUI-relaunch limitation from review.
* docs(windows): record installer short-path matching limitation
Record the 8.3 path alias mismatch found while testing the installer
helper without administrator privileges. Document how to run the fixture
with a full temporary-directory path and retain the issue as a minor
follow-up without expanding the installer implementation.
Restrict OHOS version baselines to v-prefixed version tags so the
rolling nightly tag cannot produce an invalid ohpm package version.
Keep the manifest version fallback and count commits from the selected
version tag.
Give each parameterized shared TUN test its own network namespace,
while keeping both member instances on the same TUN within that
namespace. Reuse TestNetwork to own and clean up the namespaces and
remove the process-local serial lock, which does not serialize nextest
processes. This prevents fixed test addresses from competing for routes
in the root namespace without increasing timeouts.
* fix(config): preserve empty manual route mode in API conversion
Treat present TOML routes as manual mode even when the list is empty.
Both Core readback and Web normalization must distinguish routes=[]
from automatic discovery; otherwise Web can skip a required overwrite
and Core can report a different mode from its running configuration.
Cover API/TOML round trips and receiver reconcile mode transitions.
Verify default-feature tests, clippy, formatting and real system routes
in three isolated Linux network namespaces.
* fix(config): patch manual route mode and verify legacy readback
Add an explicit manual-route mode to route patches and advertise typed
readback support through GetConfig. Apply the mode and route edits in the
existing route transaction without restarting the instance; keep released
callers that omit the new field on their existing route semantics.
Web gates route patches on the observed capability. For old clients with
ambiguous empty routes, recover the actual mode from GetConfig TOML or
instance-scoped ShowNodeInfo before acknowledging application. Supporting
clients use typed readback directly. Keep capability evidence in the
existing session cache and preserve its invalidation rules.
Cover mode transitions, persistence failure, stable peer identity, legacy
readback, compact clients, failed observations and cache capability gates.
Document the runtime contract and compatibility boundary.
* refactor(web): advertise route patch support in heartbeats
Declare manual-route patch and typed readback support on the WebClient
heartbeat alongside existing client capabilities. Select route updates
and legacy readback from the current authenticated Session heartbeat,
including when reconciliation reuses an observed configuration.
Remove the unreleased GetConfig capability field and per-instance
capability wrapper. Keep only observed configuration in the existing
cache, and retain strict application confirmation after full Run.
Cover the client declaration, legacy default and capability changes
against the same cached configuration. Update the runtime contract.
---------
Co-authored-by: Codex <codex@users.noreply.github.com>
The non-reusable credential test guessed an owner from three route
samples and repeatedly probed reachability with fixed stability sleeps.
Partial route synchronization could select a temporary owner, while the
extra waits pushed otherwise healthy runs past the 60-second CI limit.
Wait for both credential peers to connect, then require both admins to
route only to the lower peer ID selected by the owner election. After
stopping that peer, wait for both admins to route only to the survivor.
Verify reachability and rejection from both admins in each phase without
the redundant sampling loops. Keep the existing nextest timeout.
Keep ipv4_inet out of line to prevent LLVM load combining from
zeroing the address and prefix when inlined into async add_ip.
This works around Windows named-NIC IPv4 setup failures on the
current release toolchain.
Link the upstream LLVM fix so the workaround can be removed once
all build toolchains include it.
Fixes#2696
Publish GUI installers and Android APKs under their original names.
Use the built commit version in Core, Web and Magisk ZIP names, and
match the version-release archive layout while preserving executables.
Generate SHA256SUMS for all downloads and validate the packaged asset
manifest before publication. Cover native installers, archive layout,
checksums and invalid artifacts in the CI script tests.
Document versioned download URLs and remove the stable-URL claim.
* fix(windows): reuse stable TUN identities after complete shutdown
Wait for NIC forwarding and packet-drain tasks to release their handles
before stopping or replacing the interface. Stop the old static-IP NIC
before entering its recreation path. Store typed NIC state so shutdown
can await the owned tasks instead of only cancelling them on drop.
Use the instance UUID for the default Windows interface name and derive
a stable Wintun GUID from the device name. Keep explicit names and their
existing sharing behavior, while generated names remain dedicated. Keep
resolved names out of the input flags so recreation preserves that
choice. Pin rust-tun to the fix that joins its reader during session
drop.
Remove the global NetworkList cleanup that deleted profiles based only
on their names. Stable identities avoid creating a new profile for each
restart without guessing which existing records are safe to delete.
Add regression tests for receiver release and Windows instance restarts
with another dedicated or shared-NIC member still running.
Validation: formatting and workspace Clippy pass; 66 NIC, shared-NIC and
TUN runtime tests pass in the privileged rust container.
Fixes#2682 Related: #110
* fix(windows): allocate the first unused numbered TUN name
Use et_1, et_2 and subsequent vacant slots for default Windows NICs.
Reserve each name across threads and processes with a named kernel
object, retaining it through firewall cleanup and releasing it on exit.
Check the current device tree to skip disabled adapters without treating
removed adapters in the IP interface tables as occupied slots.
Keep GUIDs derived from names and preserve explicit NIC sharing. Cover
concurrent allocation, case-insensitive reservations and slot reuse.
* fix(windows): release idle TUN names across cached and service owners
Release a shared NIC name after its dispatcher has stopped and its
firewall rules have been removed, even when the registry caches the NIC.
Clear the old interface name so a later cache drop cannot remove rules
belonging to its replacement. Await dispatcher teardown on control
failure and timeout as well as on successful shutdown.
Grant only SYNCHRONIZE access to name reservations across accounts so
SYSTEM services and desktop processes can skip occupied slots.
Cover idle registry reuse, firewall ownership and failed-control cleanup
with regression tests.
* test(tun): use the renamed dispatcher task handle on Unix
Update the two Unix-only teardown tests to match the task field used
by explicit dispatcher shutdown.
* fix(windows): report firewall cleanup failures and verify all rules
Inspect all eight interface rules, including the ALL protocol pair.
Treat only a missing rule as absent and propagate other lookup errors.
Attempt every rule deletion and report failures with rule names instead
of silently returning success after a permission or service error.
Validate rule protocols, directions, profiles, interface binding and
allow settings on Windows. Cover repeated add/remove, unprivileged
failures, full cleanup and shared-NIC rule ownership across restarts.
Pin tun-easytier to merged v2 commit
5ba6be073e5b486b68c12f80fac5cfb50601f024 and update Cargo.lock. Its tree
matches the previously tested PR commit.
GITHUB_TOKEN-dispatched builds do not trigger downstream workflow_run
jobs. The nightly release check ran before builds finished and then
exited successfully without publishing anything.
Track dispatch run IDs and wait for all missing components to succeed.
Retry failed jobs once, account for delayed rerun state, and fail on
terminal errors. Bound the build and wait job to three hours.
Pass the built commit to publication so later main commits do not
change the release target. Retain component deduplication and manual
build completion handlers, and document the nightly lifecycle.
Add regression coverage for pending builds, retries, terminal failures,
dispatch races, and main advancing before publication. All 34 Node tests
and actionlint for the three nightly workflows pass.
* ci: publish rolling main nightly builds and retry failed jobs
Keep one nightly prerelease with stable artifact filenames. Publish
only successful Core, GUI, Mobile and Test runs from the same main
commit, and prevent older runs from replacing a newer nightly.
Generate dated changes against the latest published version release,
including manual prereleases, in newest-first order. Link the fixed
nightly download page from both READMEs.
Retry failed full-build jobs once on main and develop, excluding
cancelled runs and attempts after the first.
Validated with 28 Node tests, actionlint, archive packaging checks,
and read-only selection of real GitHub builds and release notes.
Accept a boolean or an IPv6 CIDR in dhcpv6 and --dhcpv6, using
fd6b:8c2d:91e4::/64 when no initial subnet is supplied. Keep IPv4
configuration serialization compatible and DHCPv6 disabled by default.
Share the DHCP lifecycle and host update between both address families,
with typed allocation state, peer subnet selection, conflict recovery,
and bounded IPv6 probing. Preserve static addresses of the other family
and prevent static config patches from replacing DHCP-owned leases.
Carry IPv6 settings, leases, and events through management, web config,
and mobile VPN setup. Reuse the shared NIC registry and mobile address
list for IPv6-only and dual-stack operation without extra FD owners.
Cover config round trips, allocation and recovery, mobile
reconciliation, three-node IPv6 and dual-stack connectivity, and shared
TUN member exit. Validate with workspace clippy, Docker TUN tests,
frontend tests/builds, WASI and Android Rust checks, and Android VPN
Kotlin compilation.
* refactor(tun): consolidate shared NIC configuration and tests
Separate member claims from OS deltas so mobile admission updates do
not compute unused diffs. Reuse effective configurations for desktop
address, route, and MTU updates, and share the platform claims paths.
Remove redundant runtime dispatch and test-only public accessors while
preserving registration cleanup and platform device creation ordering.
Move configuration and lifecycle tests into focused modules. Reuse
setup and table-driven cases, and verify route replacement order,
source hints, stale registrations, and failed cleanup through behavior.
Validation: shared NIC unit and Docker integration tests, mobile claim
selection, TUN and public IPv6 tests, full-workspace clippy, Android
arm64 library check, and formatting of the changed files.
* docs(tun): describe shared Ring dispatch lifecycle
Document direct member Ring polling, mobile descriptor replacement,
and cooperative scheduling in the shared dispatcher. The dispatcher
implementation is already provided by the base branch in #2689.
* test(core): remove completion race from timeout budget test
Use a pending future to exercise stage-specific timeout errors.
A delayed CI runner can observe the old 20 ms sleep and 1 ms timeout
as ready together. Tokio polls the inner future first, returning Ok
instead of the error expected by the test.
Keep the timeout assertion without a competing completion timer.
Production timeout behavior is unchanged.
Select dedicated or shared NICs through one constructor, removing
redundant construction wrappers and unreachable setup error handling.
Share desktop and mobile DHCP cancellation, publication, and permit
handling while retaining their platform-specific NIC setup.
Build mobile VPN configuration once after member selection or attach,
and use CIDR formatting that preserves host prefixes.
Cover DHCP publication locking and cancelled address updates, and
retain explicit IPv4 /32 and IPv6 /128 projection assertions.
* Add shared virtual NIC core with per-member routing
Share a named TUN across instances while keeping unnamed devices
dedicated. Track member addresses, routes, and MTU, and reject
overlapping destinations except the common Magic DNS host route.
Dispatch packets between the physical TUN and member tunnels. Keep
IPv4 and ordinary IPv6 source translation for mobile wrong-source
traffic. Update platform configuration and runtime host integration,
with unit and network namespace coverage for routing and lifecycle.
Use smoltcp for packet parsing and checksum updates. Keep core CI
matrix jobs independent.
Reserve native inbound flows before source translations allocate their
wire tuples. Keep active reservations under table pressure and leave
forwarded subnet traffic outside the local flow table.
Complete interface claim updates on the shared runtime and retain member
registrations until the transaction commits. Keep address, route, and
MTU cleanup ordered after cancelled callers, including device
initialization.
Project mobile VPN addresses, routes, and DNS from admitted native
members. Prefer unchanged attached claims and report conflicting members
individually. Apply each member's complete initial claims before opening
the shared fd, so conflicts leave healthy members usable.
Cover inbound IPv4/IPv6 collisions, cancellation during native changes
and dispatcher acknowledgments, admission priority, and atomic mobile
claim rejection with regression tests.
Use scoped TestNetwork fixtures for shared TUN bridge setup and cleanup.
Resolve public IPv6 namespace aliases through the lab's actual names so
the new tests remain isolated under parallel upstream test runs.
* Configure one mobile VPN from running TUN instances
Use admitted native members to configure VpnService and attach its
single fd to the core shared TUN. Wait for attachment completion and
record the actual attached members before refreshing VPN state. Report
rejected members in the GUI and preserve the VPN for unchanged healthy
members.
Reconcile ordinary configuration notifications without invalidating an
arbitrary member. Invalidate attachments on instance lifecycle changes,
including restarts that reuse an instance ID. Forward public IPv6
changes alongside proxy route changes to refresh the platform VPN
configuration.
Exclude confirmed IPv4-less members from readiness gating while
retaining retries for unavailable status and pending addresses. Quick
Settings stops all running TUN members and restores that group on the
next start, skipping removed configurations and no_tun networks.
Keep the FFI set_tun_fd call scoped to one instance and report
attachment errors. Cover mobile configuration, member rejection and
replacement, attachment completion, collection notifications, IPv4-less
members, and tile stop/start restoration in GUI regression tests.
Validate with 36 mobile VPN tests, vue-tsc --noEmit, Android GUI cargo
check, workspace clippy, and shared TUN network namespace tests.
* fix(tun): preserve shared NIC ownership and failure invariants
Keep mobile address and route claims in dispatcher tables without
applying desktop interface configuration to synthetic TUN names.
Limit the Magic DNS overlap exemption to route claims and remove the
shared route retry that bypassed claim accounting after invalidation.
Invalidate and stop dispatchers on failed mobile TUN replacement or I/O.
Bound TUN writes so stalled devices release pending source updates and
member cleanup instead of retaining an unusable shared dispatcher.
Treat missing Darwin and Windows addresses and routes as successful
deletions while preserving permission and other configuration errors.
Add regressions for claim conflicts, mobile configuration, failed TUN
replacement, stalled writes, and platform deletion error handling.
* fix(tun): forward single-member shared NIC traffic unchanged
Deliver TUN packets directly to the sole attached member without
destination matching or creating source translations. Keep native flow
reservations so a later member cannot collide with existing connections,
and continue applying mappings created before other members leave.
Reuse flow preparation with the original source address. Preserve the
desktop subnet forwarding bypass, including when the flow table is full.
IPv4 fragments and IPv6 extension headers remain unchanged on flows that
need no translation.
Cover unknown sources and destinations, first and later IP fragments,
native flows established before another member joins, and retained
translations after returning to one member. Document the forwarding
behavior and retain rejection coverage for multiple attached members.
* fix(tun): preserve wildcard UDP replies on shared member ports
Mark flow reservations created by incoming traffic separately from
outgoing mappings. When a wildcard UDP socket replies with another
member's source address, reuse the incoming reservation after source
correction and preserve its service port and inbound destination.
Keep exact outgoing mappings and outgoing flow collision handling ahead
of this rule. Incoming UDP sessions take priority when packet tuples
cannot distinguish a reply from a new client using identical endpoints.
Cover IPv4 and IPv6 replies, checksum updates, repeated inbound traffic,
existing outgoing mappings, TCP collisions, and single-to-shared member
transitions with regression tests.
* test(tun): wait for IPv6 activation in mobile configuration test
Assert that the configured IPv6 address is registered before forwarding
starts, then wait up to five seconds for the kernel to make it usable.
Netlink acknowledges address creation before asynchronous IPv6
activation completes, so an immediate bind can fail under CI
parallelism.
Disable automatic link-local address generation in the test namespace to
keep startup IPv6 control traffic out of member-routing assertions.
Validation: the original test failed 40 of 320 runs at 32-way
parallelism. The updated test passed all 320 runs; workspace fmt and
clippy also pass.
Construct config-server TCP and UDP listeners on IPv4 and IPv6
wildcard addresses. A default route is not needed for listening, so
remove the local-IP probes, URL-to-address wrapper, and optional
listener pairs. Keep WebSocket on its existing IPv4 address.
Propagate all native socket bind errors instead of ignoring IPv6
failures. Try address families independently and stop startup only
when no listener could bind, so one unavailable family does not hide
a working listener or create an unbound socket.
Log every successful listener with its actual bound address, including
the allocated port when the configured port is zero.
Add regressions for occupied ports and unavailable IPv6 source
addresses, failed listener registration, and valid ephemeral ports.
Fixes#2611
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
* feat(dhcp): accept an IPv4 CIDR for the initial address subnet
Accept booleans or an IPv4 CIDR in --dhcp, ET_DHCP, and TOML. Preserve
existing boolean behavior and reject invalid subnets and /31 or /32.
Carry the bootstrap subnet through management config conversions and
runtime snapshots. Keep peer-advertised subnets authoritative and use
10.126.126.0/24 when no custom bootstrap subnet is supplied.
Cover CLI overrides, config round trips, validation, address allocation,
and subnet precedence. Update English and Chinese CLI help.
* refactor(dhcp): select the bootstrap subnet during construction
Read the optional DHCP CIDR once when creating the service and pass it
to the existing allocator constructor. Restore reconciliation without
rewriting the bootstrap subnet on every round.
Initialize test configurations before constructing their DHCP services.
The PatchConfig API does not expose the DHCP policy; management changes
recreate the network instance.
Public relays create many mostly empty maps for each foreign network.
DashMap defaults to 128 shards on a 24-CPU host, allocating about
1.26 GiB of shard arrays for 2,421 networks before peer/session state.
Cap these maps at eight shards and retain the four-shard default on
single-CPU hosts. Cache the shard count once and keep global indexes
on their existing default. Apply the same cap to peer connection maps
and route synchronization version maps.
Replace nested peer/network DashSets with HashSets protected by their
existing outer map or an entry mutex. Keep cross-index updates under
the manager lock. Use a plain HashMap for the local route graph index.
Validation: 245 peer/RPC tests and eight three-node integration tests
passed, along with workspace fmt and clippy with full features.
* fix(hole-punch): connect UDP without reverse probe gating
Start cone UDP handshakes on the same punched socket without waiting
for reverse HolePunch packets. Keep outgoing probes while the existing
session layer retries SYN, allowing a reachable listener to reply to the
actual source when the STUN candidate uses a different mapping.
Register source observers on the listener's existing receive loop for
the active cone RPC. Send bounded batches to the signaled endpoint and
the first matching observed source, deduplicating equal endpoints.
Unregister observers on completion, cancellation, or listener shutdown.
Cover missing reverse probes, dropped SYN, late observed sources,
transaction isolation, and cleanup. Document socket and connectivity
ownership of observation and reply policy.
* fix(hole-punch): prepare source observation during listener selection
Carry an optional transaction ID in SelectPunchListener and echo it only
after registering the listener's existing UDP receive loop. Preserve the
first source arriving before SendPunchPacketCone, then transfer cleanup
ownership to that RPC without clearing the observed address.
Keep prepared and active registrations in the same transaction table.
Expire abandoned preparations after 30 seconds through listener cleanup;
active observers still unregister on completion or cancellation.
Preserve legacy requests without a transaction ID and tolerate older
responses without a registration acknowledgement.
Cover early packets, transaction ID zero, preparation expiry, active RPC
cleanup, legacy protobuf interoperability, and SYN retries against peers
with and without registration confirmation. Document the lifetime and
compatibility contract in the core architecture.
Validation: 820 core tests; workspace fmt and full-feature clippy
passed.
* ci: use lightweight PR checks and conditional nightly builds
Check three Core platforms, Linux GUI, and Android ARM64 on PRs and
ordinary branch pushes. Keep formatting, clippy, WASI, and all Linux
tests; reserve the per-feature sweep for full builds.
Run complete platform matrices on releases/** pushes and manual
dispatch. At 03:17 Asia/Shanghai, check existing main and develop
branches and dispatch only workflows whose last successful full build
used a different commit, retrying failed components on the next night.
Cache compilation outputs by target and build mode. Build only the GUI
frontend dependencies and reuse the dashboard artifact for Core builds.
Separate full-build concurrency so ordinary pushes cannot cancel them.
Keep the required result checks and propagate failures and
cancellations. Validate successful full-build origins and matching Core,
GUI, and Mobile commits before preparing release assets.
Validation: actionlint 1.7.12, 10 Node tests, read-only GitHub nightly
dry run, GUI frontend build, Linux GUI check, Android ARM64 check, and
Linux musl Core/Web checks including embedded dashboard assets.
* docs: describe CI behavior in English and Chinese
Document PR checks, nightly dispatch, manual builds, release validation,
artifacts, and independent workflows. Keep both versions aligned with
current workflow definitions.
* ci: decouple PR checks from Web dashboard builds
Run PR dashboard builds only when frontend sources, Core configuration
sources, protobuf schemas, or build configuration change. Keep embedded
Web builds in full mode and let ordinary Rust checks start without
frontend artifacts.
Upload dashboard artifacts only for full builds. Skip frontend setup for
ordinary lint runs and remove GUI/frontend setup from test archives.
Update both CI documents to match the new behavior.
Validation: actionlint; frontend dependency and workflow routing cases;
Linux Core cargo check and workspace clippy with frontend outputs
absent.
Register one UDP flow at bind so sockets can receive from new peers
before their first send. Allocate and reserve ports within each instance
independently of TUN and Host sockets. Release the endpoint on close.
Search the ephemeral range once and preserve concurrent cursor progress.
Route local UDP fragments by the first fragment's bound endpoint. Buffer
earlier tails in a bounded shallow cache and forward original packets to
one recipient. Preserve cached ownership across binding changes, expire
idle entries, and clear state on address changes and runtime shutdown.
This avoids orphan fragments exhausting smoltcp's reassembly slots while
keeping original packet sizes for TUN and proxy consumers.
Randomize each new stack's seed to avoid replaying IP IDs on rebuild.
Document accepted residual collision-related UDP loss or downstream
delivery instead of adding fragment completion tracking.
Allow peer filters to return a batch, resuming each packet at the next
filter without repeating earlier filters. The single-packet path does
not allocate a batch queue. Keep ZCPacket and IpReassembler unchanged.
Cover endpoint ownership, ephemeral allocation, out-of-order fragments,
cache limits and expiry, binding changes, batch continuation, and large
UDP datagrams after more than 16 complete unrelated fragmented
datagrams.
Exercise two recreated sending stacks with controlled seeds to verify
that new fragments do not inherit an old downstream route.
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
Restore per-instance Tokio executors for standard native instances,
honoring multi_thread and multi_thread_count with a two-worker minimum
in multi-thread mode. Preserve external execution for compact hosts.
Run construction, lifecycle operations, FFI data-plane tasks, and
hot-reloaded port forwards on the selected instance executor. Keep
shutdown cleanup running when its caller is cancelled, and release
owned executors without blocking their workers on runtime teardown.
Add runtime ownership, cancellation, cross-runtime connectivity, and
hot-reload regression coverage, and document the execution model.
Validation: all 45 PR checks passed, including linting, integration
tests, and cross-platform builds.
This PR refactors EasyTier's configuration flags system across protobuf definitions, TOML/API persistence, runtime reconciliation, and the CLI.
It introduces a synthesized FlagsPatch protobuf message with optionize and pbjson support to track field presence reliably, replaces manual CLI flag boilerplate with dynamic schema-driven clap argument generation, and
establishes Flags::resolve(patch) as the single source of truth for runtime defaults.
Co-authored-by: Claude Code <noreply@anthropic.com>
* fix: replace deprecated atomic fetch_update calls
Use try_update, available since the workspace's Rust 1.95 minimum, for all standard-library atomic update calls. Preserve closure behavior and memory ordering, and leave arc-swap's unrelated API unchanged.
Cover smoltcp port allocation's previous-value return and wrap boundary.
Fixes#2614
* Add unused_async allowance for tokio test
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(cli): warn about ignored TOML logging configuration
Keep logging process-wide and direct users to CLI options or environment variables when legacy logging sections are present. Emit diagnostics for files, config directories, stdin, and config validation.
Refs #984
* fix(test): use relocated core binary in nextest archives
Resolve the runtime NEXTEST_BIN_EXE path before clearing the child environment, with a compile-time fallback for cargo test. This avoids using the build runner's binary path after extracting an archive on another runner.
Add credential peers with table and JSON output while preserving credential list.
Match authenticated remote credential keys to stored fingerprints and virtual
route addresses. Keep instance results isolated and exclude unproven relay users.
Enable SHA-256 for CLI-only builds and add regression and multi-instance tests.
Accept omitted protobuf default lists in the existing multi-instance test.
Refs #2529
Map the existing release ZIP names and binary paths through package metadata, including ARM hard-float, RISC-V, Windows ARM64, and FreeBSD naming exceptions. Document repository-based installation and Windows sidecar requirements.
Fixes#365
* build(core): consolidate crypto dependencies and trim features
Align AES-GCM, ChaCha20Poly1305, HMAC, SHA2 and HKDF with the versions
already required by Snow and STUN. Align base64 with pbjson, and explain
the coordinated upgrade constraints beside the manifest entries. Adapt
AEAD and HMAC calls without changing packet or key formats, and pin the
WireGuard client key derivation with an independent HKDF vector.
Limit Snow to the Noise algorithms used by the core. Use crossbeam-utils
directly, keep the futures executor in tests, and remove UUID fast-rng
from the core while retaining existing features in native consumers.
Enable browser entropy and certificate time for the rustls backend.
Core default normal/build dependencies fall from 255 to 224 packages;
duplicated package names fall from 24 to 5 against upstream 4837468d.
Validation: Docker tests pass: 970 core, 33 proto (2 ignored), 5
WireGuard, and TCP/UDP three-node encrypted relays. Clippy passes with
warnings denied for core, proto, native and web targets. Browser
default/ChaCha20, WASI and minimal native compile checks pass. Workspace
formatting passes.
* fix(tls): select ring explicitly across application entry points
Building easytier and easytier-web together enabled both ring and aws-lc
through reqwest 0.13. HTTPS endpoint discovery used rustls automatic
provider selection and could panic before sending a ClientHello.
Use reqwest rustls-no-provider to share ring, and explicitly install the
process default before starting CLI, GUI and web services. Initialize it
for standalone webhook construction too, retaining any provider already
selected by the host.
Pass ring directly to HTTPS discovery and WebSocket TLS builders so
library use is independent of application initialization order. Keep
existing certificate verification, SNI and protocol settings.
Add a duplex-stream regression that reproduces the original panic with
both backends enabled, and run it in CI. Explain provider selection and
feature-unification constraints next to the relevant code.
Validation: the new regression fails before the fix and passes after it.
Docker tests pass: 10 discovery, 13 webhook, 2 WebSocket, WSS three-node
AES-GCM relay, and WSS credential connectivity. Clippy with warnings
denied and fmt pass. GUI, browser, WASI, minimal native, endpoint-only
and WebSocket-only compile checks pass. Linux and Windows release
dependency trees contain only the ring runtime backend.
Fixes#2607
* ci: remove the separate rustls backend regression step
Keep the HTTPS discovery regression in the existing test archive and
runner. Avoid an extra core test build solely to enable both rustls
backends; that combination was verified locally before the TLS fix.
* build(web): share reqwest 0.13 with the OIDC client
Disable the reqwest 0.12 client bundled with openidconnect/oauth2 and
use the official oauth2-reqwest adapter around the workspace reqwest
0.13 client. Pin the pre-release adapter version until its API
stabilizes.
Keep the existing redirect policy and 30-second timeout. Initialize the
ring provider when constructing OIDC configuration, including outside
the web application entry point.
Exercise discovery, JWKS fetching, token success and OAuth error
responses against a local mock provider. Verify redirects are not
followed. Refresh Cargo.lock to remove reqwest 0.12 without unrelated
upgrades.
Validation: Docker OIDC tests (2) and webhook tests (13) pass. Clippy
with warnings denied for native/core/proto/web all targets and full
features passes, as does workspace formatting. The default core/web
dependency graph has one reqwest version and 45 multi-version names
instead of 46; the TLS runtime still selects ring only.
The GUI shells out to pkexec for privilege elevation on Linux
(elevate/linux.rs). Since Debian 12 / Ubuntu 24.04, pkexec is shipped
as a standalone binary package split from policykit-1, so it is not
guaranteed to be installed. On such systems (e.g. a minimal Debian 13
desktop) installing the official deb succeeds but launching the GUI
panics with:
Failed to run elevated command: No such file or directory (os error 2)
Add 'pkexec | policykit-1' to the deb bundle dependencies. The
alternation keeps the package installable on older releases where
pkexec still lives inside policykit-1 (e.g. Ubuntu 22.04), while
tauri-bundler continues to append the auto-detected webkit2gtk/gtk
dependencies.
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)
Move secure tunnel handshakes out of the listener accept path so an idle
connection cannot delay subsequent device sessions. Bound pending
handshakes to 4096 per listener and own them in a JoinSet for
cancellation.
Preserve each in-flight accept future while handling completed
handshakes so listeners with transport upgrades do not lose accepted
connections. Keep session registration, authentication and protocol
timeouts unchanged.
Add TCP regressions for idle-client isolation, manager shutdown, bounded
admission and preserving an unfinished transport accept. The isolation
test fails before the fix and passes afterward.
Validation: all 249 easytier-web tests with --include-ignored pass.
Raise the test process file descriptor limit to 16384 for the explicit
4097-connection capacity test. The default test run skips this stress
test. Cargo fmt --all -- --check and cargo clippy -p easytier-web
--all-targets -- -D warnings also pass.
Domain context impact: none; this changes handshake scheduling only.
Architecture impact: none; ClientManager retains connection ownership.
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.
Add Linfeng Cloud to both README sponsor sections, using the
partner link from the project website. Store its logo locally so it
renders on GitHub without relying on the partner site.
Continuous incoming connections recreate the one-second sleep in run_accepted_task_runner, so completed handlers are only reaped after a one-second gap in accepts. Busy public nodes can therefore accumulate completed Tokio tasks and JoinSet entries even when their established connection count stays stable.
Keep a persistent interval outside the select loop so incoming accepts cannot reset the reaping deadline. Add a paused-clock regression test that accepts an immediately completing handler every 100 ms, checks that completed entries remain bounded, and verifies shutdown drains the set. Enable Tokio's test clock utilities only for development builds.
Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
Report missing peer metadata and connection rows in sync responses.
Resend those records through the existing per-session pending queues
without resetting the incremental cursor or rebuilding sessions.
Keep requests across lost responses, distinguish missing connection rows
from metadata, and avoid requesting non-relaying credential rows.
Deduplicate records selected by both incremental and pending paths.
Cover selective expiry, multihop recovery, retries, partial availability,
and unchanged-record suppression. All 40 route module tests pass.
Fixes#2597
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.
* feat(easytier-go): accept data plane ABI v4
The core raised DATA_PLANE_ABI_VERSION to 4 in f26c2aa1 ("feat(wasi):
run EasyTier core on Cloudflare Workers and browsers") to advertise the
new guest exports easytier_data_plane_tcp_shutdown_write_submit and
easytier_data_plane_tcp_shutdown_write_result_take, which let a host
half-close guest TCP streams.
The Go host has no caller for half-close: net.Conn exposes only Close,
which already tears down both directions, so the v3 behavior is
preserved and no new plumbing is added. Without this bump, any artifact
rebuilt from current core source is rejected at instance creation with
"unsupported EasyTier data plane ABI version 4, want 3".
* fix(easytier-go): decode socket options wire v3, rebuild core artifact
38e2a621 ("refactor(ohos): 拆分 OHRS 包并按 socket 精细保护 VPN 流量")
raised the host socket options wire format from version 2 to 3: a
need_protect byte is appended after the purpose byte in TCP connect,
UDP bind, and TCP listen options, shifting bind_device one byte later.
The Go hostABI decoders were never updated, so every socket operation
from a HEAD-built core was rejected as "invalid options" and required
listeners failed to start.
Bump the accepted wire version to 3 and skip the need_protect byte in
all three decoders. The byte requests VPN socket protection, which only
Android-style VPN hosts can honor; on every other platform sockets are
already protected, so reading and discarding it is correct.
Regenerate the embedded core artifact and protobuf bindings from HEAD
(599e4eac) so the shipped artifact matches the Go host again. The proto
regeneration also picks up schema fields added since the last embed
(e.g. prefer_peer_relay).
* perf(easytier-go): release compiler garbage after host init
wazero's optimizing compiler allocates ~100MB of throwaway state on
the Go heap while compiling the embedded core. Go's runtime does not
return that memory to the OS after the initiating GC, so the process
retained the compilation peak for its entire lifetime: RSS sat at
~164MB before any instance or network activity.
Call debug.FreeOSMemory() once after the module is instantiated.
NewHost is a one-time initialization path outside the dataplane, so
the stop-the-world pass is safe here. Measured RSS after host.New
drops from ~164MB to ~64MB; no behavioral change.
* chore(easytier-js): bump toolchain dependencies
- vitest 2.1.9 -> 3.2.7 (all three packages)
- esbuild 0.25.9 -> 0.28.2 (browser bundler)
- wrangler 4.114.0 -> 4.134.0 (cloudflare + web example)
- @cloudflare/workers-types 5.20260724.1 -> 5.20260917.1
- binaryen 131.0.0 -> 132.0.0 (JS bindings only; the wasm build
uses the standalone wasm-opt binary fetched by the build script)
Not bumped: typescript stays at 5.9.3 (latest 5.x; 7.0 is a major
jump not worth taking for this workspace), vite stays at 5.4.21
(web-example only; 5->8 spans three majors).
Verified with pnpm test (28 + 2 + 2 tests across runtime, browser,
cloudflare) and pnpm check (tsc + wrangler deploy --dry-run).
pnpm-workspace.yaml gained three minimumReleaseAgeExclude entries
recorded automatically by pnpm for freshly published versions.
* fix(easytier-js): align host wire format with core wire v3
The core socket options wire format moved to version 3 in 38e2a621
(need_protect byte after purpose). The JS websocket-host still
required version 2 and rejected every TCP bind from a HEAD-built core,
breaking browser port leases.
- websocket-host.ts: accept version 3, minimum length 49. The
need_protect byte sits after purpose (offset 43) and is a no-op
outside Android VPN hosts, so the decoder just skips it.
- websocket-host.test.ts: update the test encoder to emit v3.
- binaryen stays at 131.0.0 to match script/build-wasi-core.sh, which
intentionally pins binaryen 131 for the Go-side embedded core.
The npm binaryen package provides the wasm-opt binary used by
build-wasm.mjs, so keeping both sides on the same version avoids
divergent optimization output.
- pnpm-workspace.yaml: drop two stale minimumReleaseAgeExclude entries
for @cloudflare/workers-types versions no longer in the lockfile.
* test(easytier-go): cover socket options wire v3 decoders
Direct unit tests for decodeTCPConnectOptions, decodeUDPBindOptions, and
decodeTCPListenOptions with wire v3 documents. Covers combinations of
socket mark, netns, bind device, and local address presence, plus
rejection of wire v2.
Resolves review feedback on PR #2588.
* 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.
The wrapped TCP proxy (KCP/QUIC) claims every SYN emitted by the local host,
including packets addressed to the node's own virtual IP. Those packets were
marked for the wrapped path and self-delivered, then the wrapped destination
lookup failed ("no peer found for wrapped TCP dst"). This broke local access
to the node's own virtual IP on systems that route own-address traffic through
the tun (macOS), and, with the proxy enabled on both sides, also inbound
wrapped flows to a macOS node, because the receiving side dials its own
virtual IP locally.
Skip the wrapped path when the SYN destination equals the local virtual IPv4.
Feed the NIC filter context the instance virtual IPv4 (snapshot.virtual_ipv4)
instead of the smoltcp fake inet so the check works with smoltcp enabled too;
the net-to-net guard is unaffected because it only runs when smoltcp is off,
where both values are identical.
Add a regression test for the KCP and QUIC transports.
* 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.
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>
* 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(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.
* 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.
* 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.
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.
* 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.
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
* 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.
* 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.
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>
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>
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.
* 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.
* 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.
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.
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.
* 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.
* 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>
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.
* 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.
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.
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.
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(stats): avoid per-update clock reads
Perf profiles show quanta::get_now consuming 2.9-4.3% of data-plane
CPU because every counter update refreshes a high-resolution timestamp.
Track metric activity with the existing 60-second cleanup cadence instead.
Relaxed 32-bit epochs preserve the three-minute retention window, support
32-bit targets, and remove repeated clock reads from packet processing.
* perf(data-plane): reduce per-packet synchronization
Perf profiles showed per-packet config Arc cloning, bounded-channel
permit futures, duplicate peer lookups, and default connection UUID
lookups consuming CPU in both TCP and UDP data paths.
Borrow stable config snapshots, use nonblocking channel fast paths with
the existing backpressure fallback, reuse direct peer lookups, and cache
the selected connection while preserving close and reselection behavior.
Add focused tests for channel backpressure and cached connection
invalidation.
* fix(peer): serialize default connection cache updates
The profile-guided default connection cache could republish a connection
after the close task removed it, leaving a stale cache while another
connection remained live.
Serialize only cache-miss selection/publication and connection removal.
The per-packet cache-hit path remains lock-free, while close and selection
can no longer race to resurrect a removed connection.
* 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.
handle_list_all_sessions returned client_mgr.list_sessions(), which
iterates user_clients_map across ALL users and returns every session's
StorageToken (token, client_url, machine_id, user_id). The handler is
mounted under login_required! but performed no per-user authorization:
it fetched get_group_permissions() only to println! the result, then
returned the full cross-user list. Any authenticated user could read
every other user's device token and public client_url.
Scope the result to the caller by adding
Storage::list_user_client_tokens(user_id) /
ClientManager::list_sessions_by_user_id(user_id), mirroring the existing
per-user pattern in handle_get_summary (list_machine_by_user_id). Also
drop the leftover debug println! and the unwrap() on the current user
(return 401 instead).
* 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.
Fix web/frontend compat bugs in managed config & runtime status
- Preserve [[peer]].peer_public_key when TOML configs round-trip
through the web/managed NetworkConfig path
- Keep old peer_urls clients working while adding structured peer
metadata for new clients
- Make frontend protobuf JSON normalization preserve omitted-field
semantics instead of turning missing data into misleading defaults
- Harden runtime status rendering against omitted or string-encoded
backend fields
- Expose peer-route feature flags in the web status UI
Fixes SOCKS5/port-forward handling for peer data packets
whose source endpoint was rewritten by the KCP or QUIC proxy path.
Keep SOCKS5 entry accounting consistent by centralizing insert/remove
operations, decrementing only for actual removals, avoiding underflow,
and resetting counts when entries are retained or cleared after IPv4 changes.
This PR fixes IPv6 UDP hole punching for peers with multiple public IPv6
addresses by adding two RPC signals:
- connector_addrs: connector-side candidate public IPv6 socket addresses
that the remote peer should punch back to.
- preferred_src_ipv6: remote listener IPv6 address that the remote peer
should use as the UDP source when sending hole-punch packets back.
Together, these let the connector try all usable local IPv6 candidates
while keeping the remote punch-back
packet sourced from the same IPv6 address that the connector is dialing.
Replace std::time::Instant with quanta::Instant on per-packet, per-RPC,
and per-session paths. TSC-based, ~5ns vs ~25ns per now() call.
Reuses the existing `extern crate self as hotpath` alias so
`use hotpath::instant::Instant;` resolves to the same quanta type with
or without the hotpath feature. Leaves tokio::time::Instant and
smoltcp::time::Instant untouched.
* refactor(web): use generated proto network types
* fix(core): preserve dumped config flags
* test(web): cover config flag save paths
* fix(ci): use system protoc before frontend codegen
* fix(ci): serialize frontend-lib builds
Handle TUN receive errors by marking the fake TCP stack closed and
clearing registered sockets instead of panicking.
Refuse new sockets on closed stacks and let listeners recreate stacks
when the reader task exits.
Remove quinn-plaintext to fix connection errors caused
by different hash values across platforms.
On x64, maintain compatibility with quinn-plaintext.
* feat(ffi): add async data plane API
* feat(ffi): add async data plane examples
* test(ffi): make async Go dataplane tests self-contained
* docs(ffi): document Go async dataplane API
* docs(android): document dataplane JNI API
pnpm v11 introduces breaking changes that cause frozen installations
to fail:
1. The "pnpm" field in package.json is no longer read. Moved
`overrides` to `pnpm-workspace.yaml` to fix
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`.
2. `strictDepBuilds` is now enabled by default. Added required
dependencies (esbuild, unrs-resolver, vue-demi) to `allowBuilds` in
the workspace config to fix `ERR_PNPM_IGNORED_BUILDS`.
Add config server client support for the C FFI and Android JNI bindings.
Reuse the existing easytier::web_client::run_web_client path and
NetworkInstanceManager; OHOS is unchanged.
Report successful remote config apply/delete operations through a
callback, with one JSON event per affected instance.
Keep the config server client and FFI data plane mutually exclusive: once
either side is in use, the other side returns an error instead of sharing
lifecycle state.
Use pbjson to support string deserialization for enum fields
This allows TOML configs like:
chainType = "Inbound"
instead of:
chainType = 1
- Maintain backward compatibility with integer values
- Default serialization format is now string
TomlConfigLoader::new_from_str() always calls NetworkIdentity::new()
with unwrap_or_default() on network_secret, converting None to ''.
This creates a non-zero SHA256 digest, causing credential nodes loaded
from TOML to be misidentified as regular nodes (with network_secret),
which breaks Noise handshake authentication.
Fix: check if secure_mode is enabled AND network_secret is absent/empty,
and call NetworkIdentity::new_credential() in that case.
The same detection already exists in:
- core.rs (CLI path, via --credential flag)
- launcher.rs (GUI/web path, via gen_config)
This makes TOML config loading consistent with the other two entry points.
1. Overview
This PR adds data plane APIs to easytier-ffi:
TCP Outbound:
- data_plane_tcp_connect
- data_plane_tcp_read
- data_plane_tcp_write
- data_plane_tcp_close
TCP Listener:
- data_plane_tcp_bind
- data_plane_tcp_accept
- data_plane_tcp_listener_close
UDP:
- data_plane_udp_bind
- data_plane_udp_send_to
- data_plane_udp_recv_from
- data_plane_udp_close
2. Key Changes
The main changes are focused on:
- easytier-contrib/easytier-ffi/src/lib.rs: Added FFI interfaces;
made ERROR_MSG thread-safe.
- easytier/src/gateway/socks5.rs: Bridges the data plane to the
existing Socks5 server logic.
- Added EasyTierUdpSocket, mainly wrapping ref-counting and
critical object (e.g., Socks5EntrySet) hold & drop logic,
and exposing common fields (e.g., local_addr).
- Extended Socks5Server functionality to expose TCP and UDP
socket creation interfaces for FFI calls.
- Other files: Mostly pass-through logic.
- Added a relatively large Go usage example.
Adds a Linux-only socket_mark u32 config flag (CLI: --socket-mark, env:
ET_SOCKET_MARK, TOML/proto: flags.socket_mark, 0 = disabled) that is
applied as SO_MARK to every outbound underlay socket EasyTier creates:
TCP, UDP, QUIC, WebSocket, WireGuard connectors and listeners, plus the
FakeTCP decoy socket. Lets the host policy-route or filter EasyTier
underlay traffic with 'ip rule fwmark ...' or iptables -m mark.
Plumbing mirrors the existing bind_device pattern:
- FlagsInConfig.socket_mark (proto) + default 0 in gen_default_flags
- bind() builder gets a socket_mark arg; setup_socket2_ext calls
apply_socket_mark which is a no-op for mark=0 and on non-Linux
- TunnelConnector trait gets set_socket_mark(u32) default-no-op method
- IP-based connectors override; create_listener_by_url and the connector
factory pass mark from global_ctx flags
- QUIC threads mark through QuicEndpointManager::{server,connect}
- WebSocket/FakeTCP/TCP default-bind bypass paths apply mark via
socket2::SockRef::from(&tokio_socket)
- ForeignNetworkEntry propagates parent socket_mark into its derived ctx
Includes a Linux smoke test plus a CAP_NET_ADMIN-gated test that does a
getsockopt(SO_MARK) round-trip to confirm the kernel applied the value.
SO_MARK requires CAP_NET_ADMIN; ignored silently on non-Linux. FakeTCP's
TUN-written segments are not covered (kernel doesn't tag raw TUN
writes); operators relying on fwmark for FakeTCP must apply an iptables
rule on the FakeTCP TUN device separately.
Co-authored-by: Claude <noreply@anthropic.com>
Fix the issue where the Magisk module incorrectly matches the lookup main rule in Android 15's cellular network rules, causing data plane connectivity failure
This may helps games to find rooms in virtual network.
- add opt-in Windows UDP broadcast relay config flag and CLI/env plumbing
- capture local UDP broadcasts with Windows raw sockets, normalize packets, and inject them via PeerManager
Stop sharing PeerCenterServer state through a process-global map so local and foreign-network services cannot mix peer-center data when peer ids overlap.
When a node has public IPv6 addresses allocated by EasyTier, those addresses
are installed on the host's network interfaces. The system would then pick
them up as candidate source/destination addresses for underlay connections
(direct peer, UDP hole punch, bind addresses), causing overlay traffic to
loop back into the overlay itself.
Add a central predicate is_ip_easytier_managed_ipv6() and apply it at every
point where IPv6 addresses are selected for underlay use:
- Filter managed IPv6 from DNS-resolved connector addresses, including a
UDP socket getsockname check to detect whether the OS would route through
the overlay to reach a destination
- Skip managed IPv6 in bind address selection and STUN candidate filtering
- Strip managed IPv6 from GetIpListResponse RPC so peers never learn them
- Pass pre-resolved addresses to tunnel connectors to avoid re-resolution
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Avoid resolving Url::as_ref() to the full URL string before TunnelScheme
conversion. Add regression coverage for owned/borrowed URLs and the UDP
IPv6 hole-punch branch condition.
Co-authored-by: KKRainbow <443152178@qq.com>
* feat: support allocating public IPv6 addresses from a provider
Add a provider/leaser architecture for public IPv6 address allocation
between nodes in the same network:
- A node with `--ipv6-public-addr-provider` advertises a delegable
public IPv6 prefix (auto-detected from kernel routes or manually
configured via `--ipv6-public-addr-prefix`).
- Other nodes with `--ipv6-public-addr-auto` request a /128 lease from
the selected provider via a new RPC service (PublicIpv6AddrRpc).
- Leases have a 30s TTL, renewed every 10s by the client routine.
- The provider allocates addresses deterministically from its prefix
using instance-UUID-based hashing to prefer stable assignments.
- Routes to peer leases are installed on the TUN device, and each
client's own /128 is assigned as its IPv6 address.
Also includes netlink IPv6 route table inspection, integration tests,
and event-driven route/address reconciliation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- propagate reusable through credential storage, CLI, RPC, routing, and tests
- enforce reusable=false owner election with current topology
- preserve proof-backed groups when refreshing credential ACL groups
Fixes a CLI listener parsing regression where url crate special-casing for ws/wss could misinterpret inputs like ws:11011, and adds coverage to prevent future regressions.
Changes:
Refactors listener parsing to avoid url::Url parsing for proto:port forms and to support additional shorthand inputs (port-only / IP-only / SocketAddr).
Centralizes “expand to all IpScheme variants” logic in a helper (gen_listeners) while preserving the “port=0 is dynamic” behavior.
Adds unit tests covering valid/invalid listener inputs and expansion behavior.
* distinct control / data when forward packets
* fix rpc split for udp tunnel
* feat(easytier-web): pass public ip in validate token webhook
* protect rpc port from subnet proxy
Discard malformed fake TCP frames instead of panicking so OpenWrt
nodes can survive unexpected or truncated packets.
Also emit the correct IPv6 ethertype and cover the parser with
round-trip and truncation regression tests.
Addresses issue #2016 where nodes behind unstable networks
(e.g. campus firewalls) cause excessive traffic that can freeze
the remote node.
Two changes in peer_ospf_route.rs:
- Make do_sync_route_info only trigger reverse sync_now when
incoming data actually changed the route table or foreign
network state. The previous unconditional sync_now created
an A->B->A->B ping-pong cycle on every RPC exchange.
- Add exponential backoff (50ms..5s) to session_task retry loop.
The previous fixed 50ms retry produced ~20 RPCs/s during
sustained network instability.
Normalize composite tunnel display values before rendering peer and
debug output so IPv6 tunnel types no longer append `6` to the port.
- Preserve prefixes like `txt-` while converting tunnel schemes to
their IPv6 form.
- Recover malformed values such as `txt-tcp://...:110106` into
`txt-tcp6://...:11010`.
- Reuse the normalized remote address display in CLI debug output.
Implement the previously stubbed connector add/remove CLI commands
using PatchConfig RPC with InstanceConfigPatch.connectors, and
remove the peer add/remove stubs that had incorrect semantics.
The GUI exposed three networking modes: public server, manual, and standalone. In practice EasyTier does not have a server/client role distinction here. Those options only mapped to different peer bootstrap shapes, which made the product model misleading and pushed users toward a non-existent "public server" concept.
This change rewrites the shared configuration UX around initial nodes. Users now add or remove one or more initial node URLs directly, and the UI explains that EasyTier networking works like plugging in a cable: once a node connects to one or more existing nodes, it can join the mesh. Initial nodes may be self-hosted or shared by others.
To preserve compatibility, the frontend keeps the legacy fields and adds normalization helpers in the shared NetworkConfig layer. Old configs are read as initial_node_urls, while saves, runs, validation, config generation, and persisted GUI config sync still denormalize back into the current backend shape: zero initial nodes -> Standalone, one -> PublicServer, many -> Manual. This avoids any proto or backend API change while making old saved configs and imported TOML files load cleanly in the new UI.
Code changes:
- add initial_node_urls plus normalize/denormalize helpers in the shared frontend NetworkConfig model
- remove the mode switch and public-server/manual specific inputs from the shared Config component and replace them with a single initial-node list plus explanatory copy
- update Chinese and English locale strings for the new terminology
- normalize configs received from GUI/web backends and denormalize them before outbound API calls
- normalize GUI save-config events before storing them in localStorage so legacy payloads remain editable under the new model
- add lazy_p2p so nodes only start background P2P for peers that actually have recent business traffic
- add need_p2p so specific peers can still request eager background P2P even when other nodes enable lazy mode
- cover the new behavior with focused connector/peer-manager tests plus three-node integration tests that verify relay-to-direct route transition
* machine-id should be scoped unbder same user-id
* feat: report device os metadata to console
* fix sync root key cause packet loss
* fix tun packet not invalid
* fix faketcp cause lat jitter
* fix some packet not decrypt
* fix peer info patch, improve performance of update self info
* fix foreign credential identity mismatch handling
introduces support for custom credential ID generation, allowing users to specify their own credential IDs instead of relying solely on auto-generated UUIDs.
- extend web controller bindings to cover full RPC service set
- update rpc_service API wiring and session/controller integration
- generate trait-level json_call_method in rpc codegen
- route restful proxy-rpc requests via scoped typed clients
- add json-call regression tests and required Sync bound fixes~
Implement end-to-end encryption for core-web connections using the
Noise protocol framework with the following changes:
Client-side (easytier/src/web_client/):
- Add security.rs module with Noise handshake implementation
- Add upgrade_client_tunnel() for client-side handshake
- Add Noise frame encryption/decryption via TunnelFilter
- Integrate GetFeature RPC for capability negotiation
- Support secure_mode option to enforce encrypted connections
- Handle graceful fallback for backward compatibility
Server-side (easytier-web/):
- Accept Noise handshake in client_manager
- Expose encryption support via GetFeature RPC
The implementation uses Noise_NN_25519_ChaChaPoly_SHA256 pattern for
encryption without authentication. Provides backward compatibility
with automatic fallback to plaintext connections.
- improve credential peer filtering and related route lookup behavior
- expose credential peer information through CLI and API definitions
- add and refine tests for credential routing and peer interactions
- Explicitly shutdown tokio runtime on launcher cleanup to fix slow exit
- Add timeout to tunnel connector in tests to prevent hanging
- Reduce test wait durations from 5s to 100ms for faster test execution
- Bump num-bigint-dig from 0.8.4 to 0.8.6
- add credential manager and RPC/CLI for generate/list/revoke
- support credential-based Noise authentication and revocation handling
- propagate trusted credential metadata through OSPF route sync
- classify direct peers by auth level in session maintenance
- normalize sender credential flag for legacy non-secure compatibility
- add unit/integration tests for credential join, relay and revocation
Enable encryption for non-direct nodes requiring relay forwarding.
When secure_mode is enabled, peers perform Noise IK handshake to
establish an encrypted PeerSession. Relay packets are encrypted at
the sender and decrypted at the receiver. Intermediate forwarding
nodes cannot read plaintext data.
---------
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
PeerCenterRpc was only registered in the per-instance peer-to-peer RPC
manager (domain = network_name), but not in the management API server
(domain = ""). The CLI connects to the management API with an empty
domain, causing "Invalid service name: PeerCenterRpc" errors.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
When two EasyTier instances run on the same machine and share the same
network, the direct connector would expand a remote peer's 0.0.0.0
listener into local interface IPs and then attempt to connect to
itself, causing an infinite loop of failed connection attempts.
The existing `peer_id != my_peer_id` guard does not cover this case
because the two instances have different peer IDs despite sharing the
same physical network interfaces.
Fix by adding a self-connection check in `spawn_direct_connect_task`:
before spawning a connect task, compare the candidate (scheme, IP,
port) against the local running listeners. If a local listener matches
on all three dimensions — accounting for 0.0.0.0/:: wildcards by
checking membership in the local interface IP sets — the candidate is
silently dropped with a DEBUG log message.
The fix covers all four code paths:
- IPv4 unspecified (0.0.0.0) expansion loop
- IPv4 specific-address branch
- IPv6 unspecified (::) expansion loop
- IPv6 specific-address branch
The TESTING flag logic is untouched so existing unit tests are
unaffected.
* refactor(connector): replace is_self_connect closure with GlobalCtx::should_deny_proxy (#1954)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Addresses security vulnerabilities in axios <1.13.5. Updates the
declared specifier to ^1.13.5 in all three frontend package.json
files and regenerates both npm and pnpm lock files (resolved: 1.13.6).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Magic DNS updates are full snapshots, so appending routes keeps old IPs and returns duplicate A records. Replace each client's previous routes on update and add a regression test to ensure hostname resolution keeps only the latest IP.
Prevent crashes when users input quic://...:0 by rejecting port 0 explicitly and propagating connect setup errors. Add a regression test to ensure invalid QUIC targets fail gracefully.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: separate faketcp into a feature
* fix: no need to initialize out_len
* feat: separate zstd into a feature
* clippy: remove unnecessary cast, because for unix size_t always equals usize
Also rename stale interfaces from previous runs before creating new ones.
Works around rust-tun reusing existing tun0 instead of configured name.
Tested on FreeBSD 14.1
* use quinn-plaintext
* remove server_cert in QUICTunnelListener
* remove some customized transport config
* leave max_concurrent_bidi_streams as default
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- drop low-priority columns when tables exceed terminal width
- truncate optional columns to fit remaining width
- add --no-trunc flag to disable truncation
- compute column widths using unicode display width
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This allows HTTP redirect servers to provide network-specific node
lists based on the client's network identity. Updated unit tests
to verify the header is correctly sent.
Use noise protocol on handshake. Check peer's public key if needed. Also support rekey and replay attack prevention.
E2EE and temporary password will be implemented based on this.
support faketcp to avoid tcp-over-tcp problem.
linux/macos/windows are supported.
better to be used in internet env, the maximum
performance is majorly limited by windivert/raw socket.
This PR fundamentally restructures the EasyTier GUI, introducing support for service mode and remote mode, transforming it from a simple desktop application into a powerful network management terminal. This change allows users to persistently run the EasyTier core as a background service or remotely manage multiple EasyTier instances, greatly improving deployment flexibility and manageability.
* refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component
* feat(gui): Add network config saving and refactor RemoteManagement
This change introduces a major refactoring of the RPC service layer to improve modularity, unify the API, and simplify the overall architecture.
Key changes:
- Replaced per-network-instance RPC services with a single global RPC server, reducing resource usage and simplifying management.
- All clients (CLI, Web UI, etc.) now interact with EasyTier core through a unified RPC entrypoint, enabling consistent authentication and control.
- RPC implementation logic has been moved to `easytier/src/rpc_service/` and organized by functionality (e.g., `instance_manage.rs`, `peer_manage.rs`, `config.rs`) for better maintainability.
- Standardized Protobuf API definitions under `easytier/src/proto/` with an `api_` prefix (e.g., `cli.proto` → `api_instance.proto`) to provide a consistent interface.
- CLI commands now require explicit `--instance-id` or `--instance-name` when multiple network instances are running; the parameter is optional when only one instance exists.
BREAKING CHANGE:
RPC portal configuration (`rpc_portal` and `rpc_portal_whitelist`) has been removed from per-instance configs and the Web UI. The RPC listen address must now be specified globally via the `--rpc-portal` command-line flag or the `ET_RPC_PORTAL` environment variable, as there is only one RPC service for the entire application.
To resolve issue #1419, DNS request packets are read directly and responses are sent back internally instead of being forwarded to the listening port.
The DNS service on fake_ip (100.100.100.101) no longer supports DNS-over-TCP.
Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
- Add short column names for latency, loss rate, rx/tx bytes, tunnel protocol and NAT type
- Format loss rate as percentage with one decimal place
- Change table style from modern to markdown for better readability
* refactor: update global context STUN server initialization
Modified global context initialization to use a single StunInfoCollector
instance with properly configured IPv4 and IPv6 servers instead of
creating separate instances.
feat: add IPv6 STUN server configuration support
Added interface methods and config struct fields to support both IPv4
and IPv6 STUN server configuration. Modified getter and setter methods
to handle Option<Vec<String>> type for both server types.
feat: enhance StunInfoCollector with IPv6 support
Updated StunInfoCollector to support both IPv4 and IPv6 STUN servers.
Added new constructor that accepts both server types and methods to set
them independently.
feat: add CLI argument for IPv6 STUN servers
Added command line argument support for configuring IPv6 STUN servers.
Updated configuration setup to handle both IPv4 and IPv6 STUN server
settings.
docs: add localization for STUN server configuration
Added English and Chinese localization strings for the new STUN server
configuration options, including both IPv4 and IPv6 variants.
* feat(acl): add group-based ACL rules and related structures
* refactor(acl): optimize group handling with Arc and improve cache management
* refactor(acl): clippy
* feat(tests): add performance tests for generate_with_proof and verify methods
* feat: update group_trust_map to use HashMap for more secure group proofs
* refactor: refactor the logic of the trusted group getting and setting
* feat(acl): support kcp/quic use group acl
* feat(proxy): optimize group retrieval by IP in Kcp and Quic proxy handlers
* feat(tests): add group-based ACL tree node test
* always allow quic proxy traffic
---------
Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
Co-authored-by: sijie.sun <sijie.sun@smartx.com>
1. add FOREGROUND_SERVICE related code, connection not to be **blocked by android system** when apps running in background
2. no_tun mode not enabling vpnservice, makeing other app to use vpnservice
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1. avoid dns query hangs the thread
2. avoid deadloop when stun query failed because of no ipv4 addr.
3. make quic input error non-fatal.
4. remove ring tunnel from connection map to avoid mem leak.
5. limit listener retry count.
Large memory usage comes from:
Mimalloc hold large thread cache, causing abort 13M+ usage.
QUIC endpoint occupy 3M when GRO is enabled.
Smoltcp 64 tcp listener use 2MB.
* Add proto definition
* Implement and register the corresponding rpc service
* Parse command line parameters and call remote rpc service
---------
Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
* add method to create NetworkConfig from TomlConfigLoader
* allow web export/import toml config file and gui edit toml config
* Extract the configuration file dialog into a separate component and allow direct editing of the configuration file on the web
QUIC proxy works like kcp proxy, it can proxy TCP streams and transfer data with QUIC.
QUIC has better congestion algorithm (BBR) for network with both high loss rate and high bandwidth.
QUIC proxy can be enabled by passing `--enable-quic-proxy` to easytier in the client side. The proxy status can be viewed by `easytier-cli proxy`.
- **fix deadlock in ospf route introducd by #958 **
- **use random peer id for foreign network entry, because ospf route algo need peer id change after peer info version reset. this may interfere route propagation and cause node residual**
- **allow multiple nodes broadcast same network ranges for subnet proxy**
- **bump version to v2.3.2**
sometimes route table may not be updated in time, so some dead nodes are still showing in the peer list.
when generating ipv4-peer table, we should avoid these dead devices overrides the entry of healthy nodes.
this may fix following problem:
1. cpu 100% when large number of nodes in network.
2. high cpu usage when large number of foreign networks.
3. packet loss when new node enters/exits.
4. old routes not cleand and show as an obloleted entry.
Some devices have ipv6 but don't allow input connection, this patch add hole punching for these devices.
- **add v6 hole punch msg to udp tunnel**
- **send hole punch packet when do ipv6 direct connect**
1. dns resolver should be global unique so dns cache can work. avoid dns query influence hole punching.
2. when system dns failed, fallback to hickory dns.
also some improvements:
1. add magic dns option in gui.
2. allow icmp proxy fail on android
3. when no_tun is enabled, android do not start vpn service
Co-authored-by: Your Name <you@example.com>
This patch implements:
1. A dns server that handles .et.net. zone in local and forward all other queries to system dns server.
2. A dns server instance which is a singleton in one machine, using one specific tcp port to be exclusive with each other. this instance is responsible for config system dns and run the dns server to handle dns queries.
3. A dns client instance that all easytier instance will run one, this instance will try to connect to dns server instance, and update the dns record in the dns server instance.
this pr only implements the system config for windows. linux & mac will do later.
* optimize memory issues
1. introduce jemalloc support, which can dump current memory usage
2. reduce the GlobalEvent broadcaster memory usage.
3. reduce tcp & udp tunnel memory usage
TODO: if peer conn tunnel hangs, the unbounded channel of peer rpc
may consume lots of memory, which should be improved.
* select a port from 15888+ when port is 0
* support ipv6 stun
* show interface and public ip in cli node info
* direct conn should keep trying unless already direct connected
* peer should use conn with smallest latency
* deprecate ipv6_listener, use -l instead
Tested building natively on Windows ARM64, with Rust 1.82 and VS2022 toolchains.
Basic functionality of easytier-cli, easytier-core and easytier-web has been verified.
* Support running as a Windows service.
* Optimize startup logic contro
* When running in a Windows service environment, delegate the termination of the program to the win_service_event_loop function.
* Remove the use of std::ffi::OsString at the top.
* Support service manager
* Move the service-related features to be implemented in easytier-cli.
* Add a command line option work-dir to specify the working directory
Adjust the error handling logic
When the program is started by the Service Control Manager (SCM), it needs to call StartServiceCtrlDispatcher (which corresponds to service_dispatcher::start in the windows-service crate) to inform the SCM of the service's entry function. The SCM then calls the service entry function passed to StartServiceCtrlDispatcher. The process calling StartServiceCtrlDispatcher will block until the service's status is set to Stopped. If the current program is not run through the SCM, StartServiceCtrlDispatcher will return the error ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, and the program will run according to its original mechanism. For more details about SCM, please refer to Microsoft's documentation.
This patch implement a restful server without any auth.
usage:
```bash
# run easytier-web, which acts as an gateway and registry for all easytier-core
$> easytier-web
# run easytier-core and connect to easytier-web with a token
$> easytier-core --config-server udp://127.0.0.1:22020/fdsafdsa
# use restful api to list session
$> curl -H "Content-Type: application/json" -X GET 127.0.0.1:11211/api/v1/sessions
[{"token":"fdsafdsa","client_url":"udp://127.0.0.1:48915","machine_id":"de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f"}]%
# use restful api to run a network instance
$> curl -H "Content-Type: application/json" -X POST 127.0.0.1:11211/api/v1/network/de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f -d '{"config": "listeners = [\"udp://0.0.0.0:12344\"]"}'
# use restful api to get network instance info
$> curl -H "Content-Type: application/json" -X GET 127.0.0.1:11211/api/v1/network/de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f/65437e50-b286-4098-a624-74429f2cb839
```
* move rpc-build out of easytier dir and make it a independant project
* easytier core use launcher
* fix flags not print on launch
* allow launcher not fetch node info
* abstract out peer rpc impl
* fix arm gui ci. see https://github.com/actions/runner-images/pull/10807
* add easytier-web crate
* fix manual_connector test case
* fix peer rpc stop working because of mpsc tunnel close unexpectedly
* fix gui:
1. allow set network prefix for virtual ipv4
2. fix android crash
3. fix subnet proxy cannot be set on android
this patch optimize the udp hole punch logic:
1. allow start punch hole before stun test complete.
2. add lock to symmetric punch, avoid conflict between concurrent hole punching task.
3. support punching hole for predictable nat4-nat4.
4. make backoff of retry reasonable
introduce my peer route id and peer id is duplicated only when peer
route id is not same.
this problem occurs because update_self may increase my peer info
version and propagate to ther nodes.
in the old behavior, the flags is not set, and it will be generated as default value in the first read. so the default value for the latency_first will be set to true according to the Default settings to Flag.
so the Vue code init the latency first to true.
1. remove some unessesary copy in cli parse code of string
2. make some member function into non-member function to avoid taking the self reference.
3. use if let Some(..) instead of if xxx.is_some() to avoid copy and unwrap
This patch removes Tarpc & Tonic GRPC and implements a customized rpc framework, which can be used by peer rpc and cli interface.
web config server can also use this rpc framework.
moreover, rewrite the public server logic, use ospf route to implement public server based networking. this make public server mesh possible.
* ✨ feat: version
Add display version information, incompatible with lower versions
* 🎈 perf: unknown
Unknown when there is no version number displayed
* ✨ feat: Display local nodes
Display local nodes, incompatible with lower versions
* 🐳 chore: dependencies
* 🐞 fix: minor style issues
fixed background white patches in dark mode
fixed the line height of the status label, which resulted in a bloated appearance
* 🌈 style: lint
* ✨ feat: about
the punching task creator doesn't check if the task is already
running, and may create many punching task to same peer node.
this patch also improve hole punching by checking hole punch packet
even if punch rpc is failed.
if smoltcp bind only once on tcp socket, it can only accept exactly
one syn packet in one round. other syn packets will be dropped and
client will receive a RST packet.
icmp/udp proxy do not rely on kernel net stack, but currently not handle ip fragmentation correctly.
this patch add ip resembler to merge fragmented ip packet for udp/icmp proxy
* use correct i18n hook
* fix peer rpc panic
make sure server use correct transact id
* fix dhcp
recreate tun device after ip changed
* use upx correctly
* compile arm & armv7
* prepare to release v1.1.0
1. fix ospf route panic if no ipv4 assigned.
2. should refetch global peer latency map every 60s
3. remove regex dep because it's too large and unnecessary.
* correctly set mtu, and allow set mtu manually
* communicate between enc and non-enc should not panic
* allow loading cfg from file
* allow change file log level dynamically
1. fix wireguard / udp tunnel stack overflow on win.
2. custom panic handler to save panic stack.
3. fix iface filter on windows and linux.
4. add scheme black list to direct connector
| PR targeting `main`/`develop`, or push to either branch | Core/Web `cargo check`: Linux x64, Windows x64, macOS ARM64 | Linux frontend build and Rust check | Android ARM64 frontend build and Rust check |
| Push to `releases/**`, or manual dispatch | 16 targets and Magisk artifacts | Installers for 7 targets | APKs for 4 architectures |
Test runs CI script tests, fmt, clippy, WASI, Cargo.lock validation, and
Linux tests for `easytier`/`easytier-core`, including `three_node`. Full mode
also checks each `easytier` feature except `macos-ne`.
Linux tests run on one runner with 32 concurrent nextest processes. Network
integration tests use separate namespaces and bridges for each test. Subnet
proxy cases run at most eight at a time. Tests that use host-wide network state,
measure bandwidth, or make time-sensitive connectivity assertions run first,
exclusively, before the parallel tests.
These four workflows skip build/test jobs for draft PRs. Path and duplicate
content checks can skip ordinary builds and Rust jobs. The result jobs are
`core-result`, `gui-result`, `mobile-result`, and `test`.
PR dashboard builds run only for frontend, Core configuration, schema, or
build configuration changes. Ordinary branch pushes build the dashboard.
Core checks run independently; Web embedding is compiled only in full mode.
Test skips the frontend build in ordinary mode; its test archive builds
only Rust packages.
PR runs can upload the test archive for reuse within CI. The dashboard is
uploaded only in full mode.
Core binaries, GUI installers, APKs, and Magisk artifacts require full mode.
Compilation outputs are cached. Ordinary pushes do not cancel full builds.
## Nightly
Scheduled at 03:17 Asia/Shanghai, or started manually on `main`. Check Core,
GUI, Mobile, and Test separately. Compare main HEAD with that workflow's latest
1. Please search [existing feature requests](https://github.com/EasyTier/EasyTier/issues?q=is%3Aissue+label%3A%22type%3A+feature+request%22) to ensure your idea hasn't been suggested already.
2. 请确保这个功能确实适合 EasyTier 项目的目标和范围。
2. Please ensure this feature fits within EasyTier's goals and scope.
3. 考虑这个功能是否能让更多用户受益,而不只是解决个人需求。
3. Consider whether this feature would benefit many users, not just personal needs.
- type:dropdown
id:feature-category
attributes:
label:功能类别 / Feature Category
description:请选择这个功能请求属于哪个类别 / Please select which category this feature request belongs to
options:
- 网络连接 / Network Connectivity
- 安全和加密 / Security & Encryption
- 性能优化 / Performance Optimization
- 用户界面 / User Interface
- 配置管理 / Configuration Management
- 监控和日志 / Monitoring & Logging
- 平台支持 / Platform Support
- API 和集成 / API & Integration
- 其他 / Other
validations:
required:true
- type:textarea
id:use-case
attributes:
label:使用场景 / Use Case
description:描述您希望这个功能解决的具体使用场景或问题 / Describe the specific use case or problem you want this feature to solve
placeholder:|
例如:
- 作为企业用户,我需要在多个分支机构之间建立安全的网络连接
- 作为开发者,我希望能够通过 API 监控网络状态
- 作为系统管理员,我需要更详细的连接日志来排查问题
Example:
- As an enterprise user, I need to establish secure network connections between multiple branch offices
- As a developer, I want to monitor network status through APIs
- As a system administrator, I need more detailed connection logs for troubleshooting
validations:
required:true
- type:textarea
id:current-limitations
attributes:
label:当前限制 / Current Limitations
description:描述当前 EasyTier 的哪些限制阻止了您实现这个使用场景 / Describe what current limitations in EasyTier prevent you from achieving this use case
placeholder:|
例如:
- 目前不支持基于用户角色的访问控制
- 缺少对 IPv6 的完整支持
- 没有提供 REST API 来获取网络状态
Example:
- Currently lacks role-based access control
- Missing complete IPv6 support
- No REST API available for network status
validations:
required:true
- type:textarea
id:proposed-solution
attributes:
label:建议的解决方案 / Proposed Solution
description:详细描述您希望添加的功能以及它应该如何工作 / Describe in detail the feature you'd like to add and how it should work
placeholder:|
请描述:
- 功能的具体实现方式
- 用户界面或 API 设计
- 配置选项和参数
- 与现有功能的集成方式
Please describe:
- Specific implementation approach
- User interface or API design
- Configuration options and parameters
- Integration with existing features
validations:
required:true
- type:textarea
id:benefits
attributes:
label:预期收益 / Expected Benefits
description:说明这个功能会带来什么好处,会影响哪些用户群体 / Explain what benefits this feature would bring and which user groups it would affect
placeholder:|
例如:
- 提高网络连接的稳定性和性能
- 简化大规模部署的管理复杂度
- 增强企业用户的安全性需求
- 降低新用户的学习成本
Example:
- Improve network connection stability and performance
- Simplify management complexity for large-scale deployments
- Enhance security requirements for enterprise users
- Reduce learning curve for new users
- type:textarea
id:technical-considerations
attributes:
label:技术考虑 / Technical Considerations
description:如果您了解技术细节,请分享相关的技术考虑或约束 / If you have technical knowledge, please share relevant technical considerations or constraints
placeholder:|
例如:
- 可能需要修改网络协议栈
- 需要考虑跨平台兼容性
- 可能影响现有性能
- 依赖第三方库或协议
Example:
- May require modifications to network protocol stack
Security updates are provided for the latest EasyTier release. Before reporting a vulnerability, please verify it against the latest release or the current `main` branch when practical.
| Version | Supported |
| --- | --- |
| Latest release | Yes |
| Older releases | No |
## Reporting a Vulnerability
Please report suspected vulnerabilities through GitHub's [private vulnerability reporting form](https://github.com/EasyTier/EasyTier/security/advisories/new).
Do not disclose the vulnerability in a public issue, discussion, or pull request. A useful report should include:
- the affected EasyTier version or commit;
- the affected platform and relevant configuration, with secrets removed;
- a description of the vulnerability and its potential impact;
- reproducible steps or a minimal proof of concept; and
- any known mitigations or workarounds.
The maintainers will assess the report and coordinate remediation and disclosure through the private advisory. Please allow reasonable time for a fix before publishing details.
When investigating, avoid accessing or modifying other users' data, disrupting services, or violating applicable laws.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
0. Additional Definitions.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
1. Exception to Section 3 of the GNU GPL.
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
2. Conveying Modified Versions.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
3. Object Code Incorporating Material from Library Header Files.
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
4. Combined Works.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
END OF TERMS AND CONDITIONS
d) Do one of the following:
APPENDIX: How to apply the Apache License to your work.
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
Copyright 2023 sunsijie
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
5. Combined Libraries.
http://www.apache.org/licenses/LICENSE-2.0
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
[](https://github.com/EasyTier/EasyTier/actions/workflows/test.yml)
EasyTier is a simple, plug-and-play, decentralized VPN networking solution implemented with the Rust language and Tokio framework.
## Features
- **Decentralized**: No need to rely on centralized services, nodes are equal and independent.
- **Cross-platform**: Supports MacOS/Linux/Windows, will support IOS and Android in the future. The executable file is statically linked, making deployment simple.
- **Networking without public IP**: Supports networking using shared public nodes, refer to [Configuration Guide](#Networking-without-public-IP)
- **NAT traversal**: Supports UDP-based NAT traversal, able to establish stable connections even in complex network environments.
- **Subnet Proxy (Point-to-Network)**: Nodes can expose accessible network segments as proxies to the VPN subnet, allowing other nodes to access these subnets through the node.
- **Smart Routing**: Selects links based on traffic to reduce latency and increase throughput.
- **TCP Support**: Provides reliable data transmission through concurrent TCP links when UDP is limited, optimizing performance.
- **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected.
> ✨ A simple, secure, decentralized virtual private network solution powered by Rust and Tokio
## Installation
1.**Download the precompiled binary file**
Visit the [GitHub Release page](https://github.com/KKRainbow/EasyTier/releases) to download the binary file suitable for your operating system.
Subnet proxy information will automatically sync to each node in the virtual network, and each node will automatically configure the corresponding route. Node A can check whether the subnet proxy is effective through the following command.
1. Check whether the routing information has been synchronized, the proxy_cidrs column shows the proxied subnets.
```sh
easytier-cli route
```

2. Test whether Node A can access nodes under the proxied subnet
```sh
ping 10.1.1.2
```
---
### Networking without Public IP
EasyTier supports networking using shared public nodes. The currently deployed shared public node is ``tcp://easytier.public.kkrainbow.top:11010``.
When using shared nodes, each node entering the network needs to provide the same ``--network-name`` and ``--network-secret`` parameters as the unique identifier of the network.
After the command is successfully executed, Node A can access Node B through the virtual IP 10.144.144.2.
### Configurations
You can use ``easytier-core --help`` to view all configuration items
# Roadmap
- [ ] Improve documentation and user guides.
- [ ] Support features such as encryption, TCP hole punching, etc.
- [ ] Support Android, IOS and other mobile platforms.
- [ ] Support Web configuration management.
# Community and Contribution
We welcome and encourage community contributions! If you want to get involved, please submit a [GitHub PR](https://github.com/KKRainbow/EasyTier/pulls). Detailed contribution guidelines can be found in [CONTRIBUTING.md](https://github.com/KKRainbow/EasyTier/blob/main/CONTRIBUTING.md).
# Related Projects and Resources
- [ZeroTier](https://www.zerotier.com/): A global virtual network for connecting devices.
- [TailScale](https://tailscale.com/): A VPN solution aimed at simplifying network configuration.
- [vpncloud](https://github.com/dswd/vpncloud): A P2P Mesh VPN
# License
EasyTier is released under the [Apache License 2.0](https://github.com/KKRainbow/EasyTier/blob/main/LICENSE).
# Contact
- Ask questions or report problems: [GitHub Issues](https://github.com/KKRainbow/EasyTier/issues)
- Discussion and exchange: [GitHub Discussions](https://github.com/KKRainbow/EasyTier/discussions)
This installs `easytier-core` and `easytier-cli` using the repository's metadata, independently of crates.io publication. On Windows, also copy the DLL and driver files from the matching release ZIP beside the installed binaries; binstall only installs executables.
[Install pre-built binary](https://github.com/EasyTier/EasyTier/releases) (Recommended, All platforms supported)
For a smaller executable, try [easytier-mini](https://github.com/EasyTier/easytier-mini), which supports basic TCP/UDP networking. See its documentation for supported features and usage.
[Install via Docker](https://easytier.cn/en/guide/installation.html#installation-methods)
[One-Click Register Service](https://easytier.cn/en/guide/network/oneclick-install-as-service.html) (Automatically start when the system boots and run in the background)
### 🚀 Basic Usage
#### Quick Networking with Shared Nodes
EasyTier supports quick networking using shared public nodes. When you don't have a public IP, you can use the free shared nodes provided by the EasyTier community. Nodes will automatically attempt NAT traversal and establish P2P connections. When P2P fails, data will be relayed through shared nodes.
When using shared nodes, each node entering the network needs to provide the same `--network-name` and `--network-secret` parameters as the unique identifier of the network.
Taking two nodes as an example (Please use more complex network name to avoid conflicts):
Once your network is set up successfully, you can easily configure it to start automatically on system boot. Refer to the [One-Click Register Service guide](https://easytier.cn/en/guide/network/oneclick-install-as-service.html) for step-by-step instructions on registering EasyTier as a system service.
#### Decentralized Networking
EasyTier is fundamentally decentralized, with no distinction between server and client. As long as one device can communicate with any node in the virtual network, it can join the virtual network. Here's how to set up a decentralized network:
1. Start First Node (Node A):
```bash
# Start the first node
sudo easytier-core -i 10.144.144.1
```
After startup, this node will listen on the following ports by default:
Assuming the network topology is as follows, Node B wants to share its accessible subnet 10.1.1.0/24 with other nodes:
```mermaid
flowchart LR
subgraph Node A Public IP 22.1.1.1
nodea[EasyTier<br/>10.144.144.1]
end
subgraph Node B
nodeb[EasyTier<br/>10.144.144.2]
end
id1[[10.1.1.0/24]]
nodea <--> nodeb <-.-> id1
```
To share a subnet, add the `-n` parameter when starting EasyTier:
```bash
# Share subnet 10.1.1.0/24 with other nodes
sudo easytier-core -i 10.144.144.2 -n 10.1.1.0/24
```
Subnet proxy information will automatically sync to each node in the virtual network, and each node will automatically configure the corresponding route. You can verify the subnet proxy setup:
1. Check if the routing information has been synchronized (the proxy_cidrs column shows the proxied subnets):
```bash
# View routing information
easytier-cli route
```

2. Test if you can access nodes in the proxied subnet:
```bash
# Test connectivity to proxied subnet
ping 10.1.1.2
```
#### WireGuard Integration
EasyTier can act as a WireGuard server, allowing any device with a WireGuard client (including iOS and Android) to access the EasyTier network. Here's an example setup:
```mermaid
flowchart LR
ios[[iPhone<br/>WireGuard Installed]]
subgraph Node A Public IP 22.1.1.1
nodea[EasyTier<br/>10.144.144.1]
end
subgraph Node B
nodeb[EasyTier<br/>10.144.144.2]
end
id1[[10.1.1.0/24]]
ios <-.-> nodea <--> nodeb <-.-> id1
```
1. Start EasyTier with WireGuard portal enabled:
```bash
# Register one WireGuard client as virtual peer 10.144.144.3
sudo easytier-core -i 10.144.144.1 \
--network-secret portal-secret \
--vpn-portal wg://0.0.0.0:11013 \
--vpn-portal-private-key "$(wg genkey)"\
--vpn-portal-client phone=10.144.144.3
```
2. Get WireGuard client configuration:
```bash
# Get WireGuard client configuration
easytier-cli vpn-portal
```
3. In the output configuration, replace a wildcard `Peer.Endpoint` with the
public IP/domain of your EasyTier node, then import it. `Interface.Address`
is local to that WireGuard client and may be changed to any IPv4 address;
EasyTier translates it to the registered virtual-peer address.
#### Self-Hosted Public Shared Node
You can run your own public shared node to help other nodes discover each other. A public shared node is just a regular EasyTier network (with same network name and secret) that other networks can connect to.
To run a public shared node:
```bash
# No need to specify IPv4 address for public shared nodes
Special thanks to [Langlang Cloud](https://langlangy.cn/?i26c5a5) and [RainCloud](https://www.rainyun.com/NjM0NzQ1_) for sponsoring our public servers.
We also thank [Linfeng Cloud (林枫云)](https://www.dkdun.cn/aff/RZGBFYSW) for supporting EasyTier.
If you find EasyTier helpful, please consider sponsoring us. Software development and maintenance require a lot of time and effort, and your sponsorship will help us better maintain and improve EasyTier.
[](https://github.com/EasyTier/EasyTier/actions/workflows/test.yml)
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.