Commit Graph
1071 Commits
Author SHA1 Message Date
KKRainbow ab241c12c9 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.
2026-10-13 10:23:08 +08:00
KKRainbow 4ff726ead3 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.
2026-10-13 10:23:08 +08:00
KKRainbow 571d416e71 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.
2026-10-13 10:23:08 +08:00
KKRainbow 686a059b9c 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.
2026-10-13 10:23:08 +08:00
KKRainbow ad795429c6 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.
2026-10-13 10:23:08 +08:00
fanyangandKKRainbow 1c6992d6cb fix(web): bind config listeners without route probes (#2660)
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>
2026-10-13 10:04:10 +08:00
KKRainbow 9bc1a0941f feat(dhcp): accept an IPv4 CIDR for the initial address subnet (#2686)
* 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.
2026-10-13 00:34:26 +08:00
KKRainbow c01e2baafa perf(core): cap shards in per-network and per-peer maps (#2685)
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.
2026-10-13 00:18:49 +08:00
KKRainbow d31396f04c fix(hole-punch): connect UDP when STUN mapping differs (#2680)
* 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.
2026-10-12 23:19:46 +08:00
KKRainbow 4b4f0c3cb3 fix: restore thread network namespaces and stabilize tunnel tests (#2683) 2026-10-12 20:04:01 +08:00
KKRainbow 1649ec0013 ci: parallelize Linux tests and skip HAR packaging on PRs (#2678) 2026-10-12 09:09:52 +08:00
KKRainbow f8a45ccc20 ci: use lightweight PR checks and conditional nightly builds (#2677)
* 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.
2026-10-12 00:13:12 +08:00
lightfirstandKKRainbow 7592951b80 fix(dataplane): route UDP by bound local endpoints (#2652)
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>
2026-10-11 01:35:33 +08:00
fanyang 83baead059 fix(runtime): restore per-instance native thread configuration (#2658)
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.
2026-10-11 01:17:32 +08:00
Luna YaoandClaude Code 250c2ad8d6 refactor(config): preserve explicit flags with FlagsPatch and derive CLI arguments from schema (#2585)
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>
2026-10-10 22:11:08 +08:00
fanyangandCopilot Autofix powered by AI 018750a785 fix: replace deprecated atomic fetch_update calls (#2665)
* 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>
2026-10-10 21:49:43 +08:00
fanyang 24afb4ecb4 fix(cli): warn about ignored TOML logging configuration (#2661)
* 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.
2026-10-10 12:35:15 +08:00
fanyang d96eaa668b feat(cli): show direct peers using temporary credentials (#2663)
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
2026-10-10 12:33:47 +08:00
fanyang bd06395573 feat: support cargo-binstall for prebuilt CLI tools (#2659)
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
2026-10-10 10:17:08 +08:00
刚刚and225284228a-droid db289e87bb fix(web): default virtual subnet masks to /24 (#2646)
Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
2026-10-09 23:05:14 +08:00
刚刚and225284228a-droid 2583d867c3 fix(web): keep refresh buttons steady during polling (#2647)
Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
2026-10-09 23:04:22 +08:00
fanyang 7fdfe90291 feat(cli): support configurable local state directory (#2654)
Add --state-dir and ET_STATE_DIR for persisted web-client machine IDs.

Fixes #2340
2026-10-09 21:23:02 +08:00
KKRainbow 7af7bdc16a build: consolidate dependencies and select rustls ring explicitly (#2648)
* 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.
2026-10-09 10:22:32 +08:00
LighteningShadow 983afda177 fix(gui): declare pkexec dependency for deb package (#2635)
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.
2026-10-09 00:02:37 +08:00
刚刚 4837468d43 fix(gui/web): 修复丢包率和 NAT 类型显示 (#2643) 2026-10-08 00:51:06 +08:00
Luna Yao eb32d2a930 fix(utils): fix some bugs in BufPoolWriter/BufList (#2634)
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)
2026-10-07 21:58:46 +08:00
KKRainbow 728ba94b50 Link easytier-mini downloads and fix UDP admission test ordering (#2638) 2026-10-07 01:03:35 +08:00
KKRainbow 8b7f1f0196 fix(web): accept client handshakes concurrently (#2633)
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.
v2.7.0
2026-10-05 16:25:29 +08:00
KKRainbow d3a5ae684a fix(peers): keep TCP hole-punched connections alive with 1s pings (#2632)
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.
2026-10-05 16:25:08 +08:00
Luna Yao 6abc3731ae perf(wireguard): rename WG_MAX_PACKET_SIZE and optimize scratch buffers with FixedBufPool (#2627) 2026-10-05 11:57:40 +08:00
Luna Yao 39dcd0908f perf(quic-proxy): use BufPool and BufMargins in QuicSocket (#2626) 2026-10-05 11:57:32 +08:00
92eecf1e73 fix(web): retire superseded live client sessions (#2609)
Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
2026-10-05 11:19:52 +08:00
KKRainbow 651a8d9e25 feat(web): add central network management console (#2622)
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.
2026-10-05 11:06:47 +08:00
Luna Yao a017dc133c feat(buf): add BufPool, BufMargins, and BufList for zero-copy packet buffering (#2625) 2026-10-01 09:32:45 +08:00
Luna Yao 6f68ac3758 test: configure cargo-nextest slow timeout and report options (#2624) 2026-10-01 09:30:58 +08:00
Luna Yao c2f485d42a test(tunnel): add netns bench warmup (#2623) 2026-10-01 09:30:36 +08:00
Ruikang Wang ed73d318bb fix(windows): exclude loopback copies from UDP broadcast relay (#2619)
- Filter WinDivert's local outbound loopback copies before relaying.
- Preserve intentional repeated payloads and existing address constraints.
- Update the capture filter regression assertion.
2026-09-30 11:28:09 +08:00
Burning_TNT cea877ca77 fix(build): resolve Windows library paths from the crate directory (#2616)
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.
2026-09-30 00:48:08 +08:00
KKRainbow 3da349f4ee fix(relay): preserve forwarded packets in secure relay paths (#2618)
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.
2026-09-29 22:52:10 +08:00
KKRainbow ff3921ce68 docs: acknowledge Linfeng Cloud in sponsor sections (#2604)
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.
2026-09-24 12:05:36 +08:00
Luna Yao eafd46b91b chore: add ohrs as workspace member; clean Cargo.toml (#2601) 2026-09-24 12:03:13 +08:00
刚刚and225284228a-droid 0a783c8e04 fix(listener): reap completed handlers during continuous accepts (#2598)
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>
2026-09-22 23:38:59 +08:00
KKRainbow ebf0b947b9 fix(ospf): resync missing peer data after metadata expiry (#2599)
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
2026-09-22 23:38:34 +08:00
fanyang 8d200c26d7 perf: tune jemalloc RSS decay for faster memory reclamation (#2428)
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.
2026-09-19 23:44:46 +08:00
KKRainbow 0f3d8e4434 fix(core): keep peer liveness responsive under receive limits (#2590) 2026-09-19 08:22:12 +08:00
KKRainbow e313ba8efb fix(wasi): reduce startup RSS, align ABI v4 and wire v3 (#2588)
* 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.
2026-09-19 00:13:47 +08:00
KKRainbow 7853ec8685 fix(windows): reliable service auto-start on boot and start-on-boot GUI entry (#2579)
* 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.
2026-09-16 23:02:40 +08:00
Luna Yao 7223677264 cargo: upgrade (#2116)
update dependencies
2026-09-16 20:50:43 +08:00
KKRainbow 5477d4bca2 fix(proxy): isolate TCP flows and recover KCP control loss (#2569)
* 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.
2026-09-16 12:50:08 +08:00
MRK 286e0f4a0d fix(proxy): do not wrap TCP packets destined to own virtual IP (#2572)
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.
2026-09-15 14:37:47 +08:00