Check only the DNS configuration field when deciding whether a core
needs support_dns_config. Legacy cores already understand tld_dns_zone;
including its fallback domain in the new capability comparison rejects
ordinary management of a legacy custom domain even when DNS is absent.
Normalize the new DNS field against an otherwise default configuration.
Retain rejection of non-default DNS domains and configured zones without
the capability. Leave legacy hot-patch comparisons and runtime behavior
unchanged.
Extend the existing allow/reject regressions with a legacy custom
domain. Verify omitted and default empty DNS remain accepted, while
actual new DNS requirements still fail before configuration RPCs can
be issued.
Advertise DNS configuration support in config-server heartbeats. Treat
missing support as false when reconciling a legacy core.
Allow omitted DNS and the normalized default domain with empty zones.
Reject custom domains or configured zones through the existing reconcile
error path before issuing configuration RPCs. Preserve desired state and
prevent applied revision advancement. Supported cores retain the normal
patch and readback flow.
Cover capability defaults and rejection for running or missing
instances. Reuse Web fixtures with a real v2.6.4 core for ordinary hot
updates, default empty DNS, unsupported requests, and recovery. Retain
the existing new-core DNS update and clear lifecycle regression.
Upload the current full DNS configuration and routes in every existing
Magic DNS cycle. Replace each instance contribution by UUID and
publishing connection, withdraw it on disconnect, and restore it after
election through the existing reconnect path. Keep the last valid
contribution when a new snapshot conflicts with another instance or
fails system DNS setup.
Add DNS to ConfigRpc patches and dns show/set/clear to the CLI. Validate
and save through the existing local config transaction. Successful
writes mean locally saved configuration; the next normal upload cycle
propagates it. GetConfig and dns show read only local configuration and
never wait for the elected server.
Hot-patch DNS changes from the Web reconciler without recreating the
managed instance. Reuse its existing local application and revision
semantics, without a separate DNS confirmation or observation mechanism.
Reuse the shared structured DNS compiler for configuration validation
and contribution comparison. Update aggregate system domains only when
the domain set changes.
Cover periodic two-instance updates, empty replacement, disconnect and
election, conflict protection, persistence, and Web instance identity.
Validate with real core processes, CLI and DNS queries while running,
including local management with a paused server, plus strict clippy and
fmt.
Select authority Arcs under the Catalog lock and run Hickory outside it,
so slow upstream queries do not block publication. Skip unchanged
contributions using the existing store, including TTL and connection
ownership, while preserving initialization and system-domain repair.
* feat(dns): structure static record configuration and validation
Replace zone-file record strings with name/type/TTL objects and an RDATA
value or literal TXT segments. Keep the existing 20 supported RR types
without adding per-type protocol messages or compatibility formats.
Compile file and management input through one core parser before
accepting a configuration. Validate names, type/data branches, TTL
inheritance, RRset conflicts and complete RDATA consumption with field
paths. Preserve editable input while sorting compiled RRsets by
canonical data. Complete relative SVCB/HTTPS targets against the zone
origin.
Adapt native DNS consumption, generated protocol bindings and frontend
configuration types to the same model. Automatic node records and DNS
publication behavior retain their existing runtime boundaries.
Validate with 98 core configuration tests, 10 native DNS unit/lifecycle
tests, frontend type/build/roundtrip checks and the existing 55 UI
tests. Browser/WASI core checks, workspace fmt and full Clippy pass.
Root/TUN integration is not run because the existing container cannot
load the host-built binary due to its older glibc.
* fix(dns): reject malformed SVCB input before library parsing
Hickory 0.25.2 assumes ALPN list decoding cannot fail. Empty members or
malformed character data instead trigger its expect, now reachable from
shared file and management configuration validation without enabling
Magic DNS.
Validate HTTPS/SVCB ALPN members after the same list unescaping before
calling the library. Reject a lone double-quote parameter value before
the adjacent library quote slicing can panic. Keep valid input tokens
and escaped ALPN data unchanged without catching panics or upgrading the
dependency.
Add one concentrated product regression through TOML and management JSON
configuration generation for invalid lists, escaped token errors, lone
quotes and valid ALPN lists/escapes. Preserve the structured schema, TXT
behavior and DNS runtime boundaries.
Reproduce the dependency panics first, then pass the targeted regression
and all 99 affected configuration tests. Workspace fmt and full Clippy
with -D warnings pass. Reuse prior unaffected frontend, codegen, native
lifecycle and platform checks.
* fix(dns): reject unclosed quoted service parameters
Require every quoted SVCB/HTTPS parameter to have an unescaped closing
quote before the library parser runs. Apply the check to opaque keys as
well as ALPN, preserving valid quoted values and literal backslashes.
Malformed file and management input now returns its existing value path.
Extend the concentrated product regression with unclosed opaque values,
escaped closing quotes and valid quoted parameters. Compare supported
CNAME escapes with the direct library parser and retain field errors for
semicolon and parenthesis labels that Hickory itself rejects. Keep name
tokenization and the structured configuration schema unchanged.
Reproduce the accepted malformed value through management generation,
then pass all 99 configuration tests, workspace fmt and full workspace
Clippy with -D warnings. Reuse unaffected native, frontend and platform
evidence; follow the published candidate's actual CI separately.
Static CNAME records use Hickory's in-memory lookup results, which put
target addresses in ADDITIONAL. Stub resolvers therefore fail to resolve
valid aliases, and target lookups stop at the current zone.
Wrap local authorities for A/AAAA lookups and follow CNAME targets
through the most specific local zone. Return the full chain and final
address RRset in ANSWER while keeping owners and TTLs. Share raw local
authorities with the catalog under its write lock so lookups do not
reenter that lock. Other query types and unresolved chains retain their
existing behavior; no public recursion is added.
Extend the static-record lifecycle regression for address RRsets,
chains, zone selection, refreshes, explicit CNAME queries and cycles.
Validation: 19 DNS tests pass in the rust container; strict package
Clippy and workspace fmt pass. Existing glibc and Go clients resolve
same-zone and cross-zone aliases through the actual EasyTier listener.
* feat(dns): support local static records
* fix(dns): normalize zone names with LowerName, preload static zones, and deprecate tld_dns_zone
- Normalize DNS zone names to LowerName across the server catalog and RPC boundaries to prevent uppercase/non-canonical zone suffixes from clearing node records.
- Preload all configured static zones during server initialization before registering the resolver and applying system DNS configuration.
- Deprecate tld_dns_zone in favor of [dns] domain, keeping tld_dns_zone as a hidden fallback without exposing it in help or schema defaults.
- Keep MagicDnsRecordStore using LowerName and restrict dns_route_identity to node hostname and IP.
Compact host policy disabled the packet proxy even though mini already
selects the smoltcp proxy stack. Configuration normalization
consequently removed local proxy networks and skipped proxy startup, so
peers never learned configured subnets.
Declare compact proxy support from the smoltcp feature and enable mini's
existing native ICMP adapter. Reuse the current prefix announcements,
TCP/UDP services and lifecycle without adding dependencies or changing
routing protocols. Document IPv4 subnet proxy support.
Validate prefix installation and TCP/UDP/ICMP round trips in isolated
network namespaces with both smoltcp and the default TUN proxy path.
Check remote subnet access and withdraw the local subnet on restart to
verify the unconfigured behavior. Formatting and full-feature Clippy for
the affected packages pass; mini-only strict Clippy remains blocked by
an existing unused variable outside this change.
* perf(easytier-go): keep guest linear memory off the Go heap
wazero backs linear memory with a Go []byte and grows it with append, so
each memory.grow copies the whole guest heap into a new slice and leaves
the old one for the GC. The guest heap is also the largest live object
in the process, so GOGC lets the Go heap grow to about twice its size.
With 200 open overlay connections it was 76 MB of a 100 MB live heap.
On 64-bit unix and Windows, reserve the linear memory as one
inaccessible mapping and commit pages as the guest grows. The buffer
keeps a fixed address, stays outside GC accounting, and costs RSS only
for the pages the guest touches. A failed reservation falls back to a
heap buffer; 32-bit and other platforms keep wazero's allocator.
* perf(easytier-go): share the compiled core between hosts
Each Host compiled the embedded core in its own wazero runtime: about
2.4 s of CPU, a transient peak of ~170 MB, and ~30 MB of machine code
mapped for as long as the Host lived. Embedders that run one Host per
instance, or recreate a Host to recover from a failure, paid this every
time.
Give every runtime the same in-memory compilation cache and keep the
first compiled core open, so later Hosts reuse it after the first Host
closes. Compilation runs under a lock: concurrent Hosts wait for one
compile and then hit the cache.
* fix(core): conditional check for delegated routes
Instead of checking whether `candidate.dst` is equal to `prefix`, the logic is changed to check whether `candidate.dst` is contained within `prefix`.
* fix(core): ignore host routes when detecting delegated IPv6 subnets
A /128 lease route inside a WAN source prefix is not evidence of a
delegated subnet. Accepting it made provider reconciliation disable the
WAN NDP proxy after a client received its address.
Keep exact prefix matches and accept contained routes only when they
describe a subnet. This retains /60-to-/64 delegation detection without
using lease routes from any EasyTier instance as delegation evidence.
Cover exact matches, LAN subprefixes and multiple host routes. Exercise
NDP reconciliation with kernel routes, TUN recreation and a second
interface's lease route.
---------
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
A CDN domain alias answering 301/302/303/307/308 with a plain http(s)
Location used to fail manual endpoint resolution with "no valid
connector URL found in redirect location". Return the location as a
RedirectFollow endpoint so the existing resolution loop in
resolve_manual_endpoint follows it with its cycle detection and hop
limit.
Relative locations ("/next", "//host/next") now resolve against the
request URL like in a browser, and the concatenated connector form is
only recognized when the raw remainder after the scheme explicitly
carries another "scheme://": parsing alone is not reliable because
special schemes normalize "https://ws:8443/next" into
"ws://0.0.32.251/next", and plain ported locations such as
"https://alias.example:8443/next" parse their host as a URL scheme.
CDN 域名别名以普通 http(s) Location 应答 301/302/303/307/308 时,端点
发现过去会报 "no valid connector URL found in redirect location" 而失
败。现在该 Location 会作为 RedirectFollow 端点返回,交给
resolve_manual_endpoint 既有的解析循环跟随(自带环检测与 16 跳上限)。
相对 Location("/next"、"//host/next")现在像浏览器一样相对请求
URL 解析;拼接式连接器只有在剥离 scheme 后的原始字符串显式包含另一
个 "scheme://" 时才被识别:仅凭解析结果判断并不可靠——特殊协议会把
"https://ws:8443/next" 规范化成 "ws://0.0.32.251/next",而带端口
的普通地址(如 "https://alias.example:8443/next")其主机名会被解析
成 URL scheme。
Introduces CollectionExt, RepeatedMessageModel and MessageModel in easytier-proto to simplify conversions between protobuf repeated fields and internal model vector types.
natpmp::get_default_gateway() reads /proc/net/route via netdev, which
reflects the thread-group leader's namespace. Port mapping for a
non-default netns runs on a thread that entered it with setns, so it got
the host gateway. Parse /proc/thread-self/net/route on Linux instead.
- Add powerpc64le-unknown-linux-musl target in .cargo/config.toml
- Add linux-ppc64le target to core.yml build matrix with mimalloc
- Add ppc64le support in Dockerfile and docker.yml multiarch builds
- Add binstall overrides for powerpc64le musl and gnu
- Add ppc64le platform detection in install.sh
Include the HEAD committer date as YYYYMMDD in the shared EasyTier
version string so nightly installations can be compared by date.
Read the date with Git in UTC and track HEAD, refs, and packed refs so
metadata-only commits and worktree checkouts refresh cached builds.
Preserve the package-version fallback when Git metadata is unavailable.
Refs #2796
The port-forward ACL test starts its pingpong as soon as
`init_three_node_ex` observes two peer route snapshots. The port
forward, however, resolves its destination through the outbound packet
router, which can still miss the destination at that point. In that
window `connect_tcp` fails immediately, the accepted client connection
is dropped, and the pingpong read observes `Connection reset by peer`.
Observed in PR #2793 CI run 41731902400 (case_2, dhcp=true): the same
commit passed on a rerun, confirming the race rather than a code
regression.
Wait until the outbound router resolves both forward destinations
before starting the pingpong, so the first forwarded connection is no
longer reset by a transient lookup miss. The pingpong assertions are
unchanged.
* fix(peers): route around peers without live connections
A Peer outlives its last connection until periodic cleanup. During that
window, direct sends and the least-hop shortcut still select it, even
when a usable relay route exists.
Return the original packet when connection selection finds no usable
connection, then continue through the existing relay routing path. Keep
actual send errors unchanged so packets are never retried after a send
attempt. Validate live connections in the least-hop direct shortcut.
Preserve cached connection selection and active flow bindings on normal
direct sends without an extra liveness scan or packet clone.
Add a real ring-tunnel regression covering direct and relay delivery,
with and without a flow key, while the empty Peer remains in the map.
Validation: 879 core tests, 3 native relay integration tests, cargo fmt,
and Clippy for easytier-core/easytier with full features and -D
warnings.
Domain context impact: none; peer lifecycle and protocol are unchanged.
* fix(windows): update tun for legacy Windows restart recovery
Pin tun-easytier to a8ad3eb so legacy Windows creates a fresh Wintun
adapter instead of reopening one whose requested GUID was ignored.
This fixes the GUID mismatch on subsequent starts without changing
network-profile numbering or the modern Windows fixed-GUID behavior.
The dependency keeps the official Wintun DLL and preserves the existing
reopen path for callers that do not request a GUID.
Validated the resulting core on Windows 7 SP1 and Windows 11: each OS
passed five Ctrl+C exits and five forced-termination startup rounds,
with IP assignment and an active interface/session for five seconds.
Normal exits returned zero; Windows 11 kept its fixed GUID. Cross-node
data forwarding and Windows 8/8.1 were not exercised.
Formatting and full-workspace Clippy with all targets, full features,
and warnings denied passed against the published locked Git revision.
* fix(windows): pin merged Wintun restart fix
Use rust-tun v2 commit 43621f7 after EasyTier/rust-tun#8 was squash
merged. Update both the manifest and lockfile from the feature-branch
revision to the merged revision.
The merged source tree matches tested a8ad3eb exactly. Reuse the
Windows 7/11 runtime results and full-workspace Clippy results.
Locked resolution of the merged dependency and formatting passed.
* fix(windows): reuse TUN names with incomplete registry records
A leftover Connection key must not force a new adapter identity after
EasyTier has reserved the original name. Skip that identity before
reading registry values and let rust-tun reopen or recreate the adapter.
Keep conflict checks for other GUIDs: Wintun can otherwise rename an
unrelated adapter that already uses the candidate name. Share the GUID
calculation with VirtualNic so allocation and creation stay consistent.
Verified on Windows 11 with concurrent processes, normal and forced
exit, missing Name/PnpInstanceID values, and a different-GUID collision.
The original reservation tests, Linux workspace Clippy, formatting,
and Windows core checks pass.
Co-authored-by: Daniel <2116528714@qq.com>
---------
Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
* fix(hole-punch): repair symmetric UDP candidate and handshake flow
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(hole-punch): retain candidate reuse and prediction budgets
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.
* 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.