39 Commits
Author SHA1 Message Date
omarandClaude Opus 5 daaa0fda41 fix(wireguard): stop holding AmneziaWG down behind a WireGuard hop
The guard refused to start an AmneziaWG endpoint whose detour chain reached a
WireGuard one, and refused silently: not an error, just started=false, after
which every dial failed with "WireGuard is not ready yet". A selector hook went
further and suspended an already-working node the moment its group switched to a
WireGuard member.

It existed because AmneziaWG inside WireGuard hung the kernel on Android. We do
not ship Android, upstream dropped the guard once the cause was gone, and the
cure landed here yesterday — the ClientBind reserved-gate plus the submodule pin
that carries its twin. So the tree held both the cure and the prohibition on
using it, and the configuration simply did not come up while looking like a node
that "just does not work".

Also takes the two fixes that belong with it. ClientBind.conn was read on a
lock-free fast path and written under a mutex; upstream found that race with the
same end-to-end test we wrote yesterday, so we had taken one half of a pair
again. And the outer WireGuard UDP socket forced DF, unlike direct, hysteria and
tuic — with encapsulation the datagram regularly exceeds the path MTU and the
kernel drops it instead of fragmenting, a symptom indistinguishable from the bug
we spent yesterday on.

The race needed its own test: the existing e2e run did not flag it under -race
even at -count=15. Eight goroutines over both connect branches reproduce it
deterministically, naming the lock-free read and the guarded write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 04:53:32 +03:00
omarandClaude Opus 5 56a276bcc1 ci: make the tests a gate instead of a decoration
The fork had a full suite and no CI that ran it. Upstream's test workflows
trigger on stable/testing/unstable; this repo only has main. And Gitea does not
read .github/workflows at all once .gitea/workflows exists, so those files were
decoration here. 115 of the 116 test files under shater/** had never executed in
CI even once, which is how TestDNSFilterRemoteBlocklistHTTPClient stayed red
across two published releases without anyone noticing.

The gate is a job inside release.yml that build-apk needs, because a separate
workflow cannot block another one. It runs the suite under the shipped tag set,
on Linux — 6 of 7 test files in transport/wireguard and 12 in shater/generate
compile only there or only under those tags, and those are exactly the files
covering AmneziaWG.

Three guards stop it from passing by running nothing, which is the failure this
whole change is about. The tag set may only ADD test files, never remove one.
Every package go list says has tests must appear as "ok <pkg>" in the output, so
a suite that collapses to "no test files" fails instead of passing. And the
panel run counts its test files first, because node --test exits 0 with "pass 0"
when the glob matches nothing.

The publish step used to exit 0 having published nothing: its assertions all
live inside a loop over artifacts, so an empty directory ran the body zero times
and reported success. It now counts what it published and fails on zero.

Verified by extracting the shipped step text and running it against stubs: empty
artifacts gives exit 0 before and exit 10 after; the rolling-release readback
still fires its own exit 14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 04:53:14 +03:00
Leadaxe 9dc758e43c Merge upstream/testing (L3-forwarding, snell, bridge) into lx-1.14
Merges 14 upstream commits including L3-forwarding support (which bumped
wireguard-go v0.0.3->v0.0.5, already re-grafted in the prior commit),
snell protocol, bridge outbound, flow-tracking/sniff improvements, and
DNS/dialer fixes.

lx conflict resolutions:
- protocol/wireguard/endpoint.go: took upstream's new flow API
  (PreMatchFlow/PortAddresses/PortMTU/AttachReturn/DetachReturn/JudgeFlow),
  dropped our old PrepareConnection/NewDirectRouteConnection. SPEC 020
  idle-suspend wake guard (resumeOnDial) moved to WritePackets — the single
  point every L3-forwarded packet transits, incl. established flows that
  bypass DialContext.
- adapter/outbound.go: kept lx IdleSuspendable/ReachabilityInvalidator,
  restored 'time' import dropped by auto-merge.
- go.mod/go.sum + test/: took upstream dependency bumps (tailscale, sing,
  sing-tun); wireguard-go stays v0.0.5 with local submodule replace.

Green: full sing-box CLI with LX_TAGS (Go 1.24.7), libbox, wireguard/
adapter/dns/daemon packages, transport+protocol/wireguard tests, AWG
config validation.
2026-07-08 15:10:38 +03:00
世界 9ec7cc8cbe Improve bridge 2026-07-08 16:30:06 +08:00
世界 f2dd4bfd75 Imrpove flow tracking & sniff action 2026-07-07 18:37:18 +08:00
世界 e6419b945f Add L3 forwarding support 2026-07-06 21:13:33 +08:00
Leadaxe c67bb9d8d4 lx(awg): keep guard-suspended AmneziaWG endpoint down (SPEC 022 #2)
SuspendAmneziaWG left idleAsleep untouched, so an endpoint idle-suspended
BEFORE the guard fired could be resurrected by the next dial (resumeOnDial
keys only on idleAsleep) — reintroducing the AmneziaWG-over-WireGuard kernel
hang the guard exists to prevent. Now clears idleAsleep under resumeMu so
the guard is ordered against a concurrent wake.
2026-07-02 18:06:58 +03:00
Leadaxe 66116ac089 fix(spec-020): idle tick must iterate the endpoint manager
The shipped idle-suspend tick (c55cf11e) iterated r.outbound.Outbounds(),
which never lists WG/AWG endpoints — they live in the endpoint manager.
outbound.Manager.Outbounds() returns only m.outbounds; the endpoint
fallback exists for Outbound(tag) lookups, not the iteration. So the tick
never reached a single IdleSuspendable and the feature was inert on a live
box (0 suspends over minutes idle), despite green unit tests that exercised
the walk and the per-endpoint decision only in isolation.

Fix: Router pulls adapter.EndpointManager from ctx (service.FromContext, no
box.go change — it is already registered there) and the tick body moves into
suspendIdleEndpoints(), which scans both r.endpoint.Endpoints() (where the
IdleSuspendables actually are) and r.outbound.Outbounds() (kept for a future
non-endpoint IdleSuspendable). Nil-guarded for the stub case.

Tests: new route/idle_tick_endpoints_lx_test.go drives the tick through a
stub endpoint manager — fails pre-fix (wg-1=0 wg-2=0, tick blind to
endpoints), passes after. Adds reachability walk tests for the production
topology this fix enables (nested selector→urltest pool, dual-path dedup,
dormant nested subtree) and the AWG-guard idle invariant. All adversarially
checked. See SPECS/020-MULTI_WG_IDLE_BUFFER_HEAT/SPEC.md §11.
2026-07-01 03:08:09 +03:00
Leadaxe 239c0515ad test(spec-020): unit tests for reachability walk, idle logic, event cache 2026-07-01 00:25:04 +03:00
Leadaxe c55cf11efa feat(spec-020): idle WG/AWG endpoint suspend via Down/Up
Selectively bring Down any WG/AWG endpoint that is idle past a threshold AND
unreachable from the active routing tree — freeing its recv-worker bufsArrs
(the dominant per-endpoint GC-scan holder), cutting the multi-WG heat. The next
dial through the endpoint wakes it (device.Up); wake pays a fresh handshake.

- option: route.lx_idle_suspend (Duration, 0/absent = off, kill-switch).
- route/reachability_lx.go: ReachableOutbounds walk — seeds = final + rule
  outbounds, descend via selector Now(), urltest active pool (ActiveTags), and
  static detour deps. Fresh walk per tick (no gen-cache: graph is tiny, tick is
  ~XX/2; a cache would need upstream-body invalidation hooks — not worth it yet).
- protocol/wireguard/endpoint.go: lastActivity/IdleSince, SuspendIfIdle (Down on
  live->asleep CAS), resumeOnDial (stamp + lazy Up on dial). idleAsleep is kept
  distinct from started so a guard-suspended endpoint is never idle-woken.
- transport/wireguard/endpoint.go: Resume() = device.Up() alongside Suspend().
- adapter: IdleSuspendable interface so the router tick iterates endpoints
  without importing protocol/wireguard.
- route/router.go: idle tick (period max(XX/2, 5s)) started in PostStart,
  stopped in Close.
- INFO log on each state transition only (edge-triggered): suspend / wake.
- group: URLTest.ActiveTags() exposes the whole active pool to the walk.

Builds clean, go vet clean. Reduced-bind urltest wake + bind-swap/keys
investigation land next.
2026-06-30 21:21:53 +03:00
Leadaxe e1a96eabe4 lx(awg): suspend AmneziaWG consumers when a selector switches to WireGuard
Start-guard covers a static detour chain but stops at a selector (its
chosen member is runtime-resolved). This adds the runtime half: in
Selector.SelectOutbound, BEFORE committing the switch, if the new member
reaches a wireguard endpoint, walk up the reverse-dependency ledger
(OutboundManager.ConsumersOf) and SuspendAmneziaWG() every AmneziaWG
consumer of the group — device down, started=false. Suspending before
s.selected.Store closes the race: by the time the group points at the WG
member, the consumer is down and a reconnect fails with "not ready"
instead of sending a junk handshake into WireGuard.

New adapter.AmneziaWGSuspendable marker + OutboundManager.ConsumersOf let
protocol/group act without importing protocol/wireguard. Plain-WG and
non-AWG consumers are left untouched. Variant B throughout.

Refs #2
2026-06-16 02:16:51 +03:00
Leadaxe 51360ece38 lx(awg): drop lazy dialer detour-guard, keep Start-guard only
The lazy DetourDialer guard (lx.8) never fired on device — the hang is in
Endpoint.Start, before any dial — and it is unverifiable in the LxBox UI
(detour targets real servers, not groups) and sync.Once-caches its verdict
so it can't catch a selector changing at runtime. Revert
common/dialer/{detour,dialer}.go to upstream and remove its test.

The Start-guard in protocol/wireguard (field-verified on lx.9) stays as the
sole guard. Selector-in-the-middle is now a known uncovered case.

Refs #2
2026-06-16 01:38:15 +03:00
Leadaxe f2488dedc4 lx(awg): reword detour-guard error as architecture limit, not platform
Field feedback: the user-facing message named Android / kernel hang, but
the restriction is architectural — amneziawg over wireguard is not
supported, period. Reword both guards (Start + dialer) to that; keep the
why (Android hang) in code comments for developers.

Refs #2
2026-06-16 01:26:40 +03:00
Leadaxe 030200877c lx(awg): guard AWG-over-WireGuard detour at Start, not just lazily
The lazy DetourDialer guard (lx.8) never fired on Android: an AWG node
whose detour reaches a wireguard endpoint hangs synchronously in
Endpoint.Start (peer-domain resolve over the detour + junk handshake),
before any dial. Proven by logcat — kernel stuck in Starting, no guard
error logged.

Add a Start-guard in protocol/wireguard.Endpoint.Start: walk the
transitive detour chain (OutboundManager + Dependencies); if it reaches a
type=wireguard endpoint, log and skip device startup (started stays false)
so the instance comes up and other outbounds keep working — variant B,
never abort start. Stops at selector/urltest groups (runtime target),
leaving that case to the lazy dialer guard, which stays as the second
echelon.

Refs #2
2026-06-16 01:05:43 +03:00
Leadaxe 9ed6946462 lx(awg): reject amneziawg detour into wireguard endpoint
An AmneziaWG node with detour into any wireguard-based endpoint (plain WG
or AWG) ends up tunnelling AWG traffic inside WireGuard, which hangs the
kernel on Android. Guard it in DetourDialer.init() like the empty-direct
check: lazy error, so the instance still starts and other outbounds keep
working while this node fails every dial (variant B).

Owner-is-AWG flows in via dialer.Options.IsAmneziaWG; the target is matched
by Type()==wireguard, expanding selector/urltest groups recursively. Detour
into a non-wireguard outbound (vless, …) and WG->AWG stay allowed.

Fixes #2
2026-06-16 00:02:23 +03:00
Leadaxe 0e8894d2ef lx(awg): AmneziaWG 2.0 client endpoint 2026-06-09 15:12:26 +03:00
世界 da0cd68115 Fix ping timeout 2026-06-03 12:56:36 +08:00
世界 b4c625543a Fix tailscale crash at start 2026-05-13 16:28:49 +08:00
世界 d2fa21d07b Deprecate Socksaddr.IsFqdn: do not reject potentially valid domain names 2026-03-16 09:37:59 +08:00
世界 1803471e02 endpoint: Fix UDP resolved destination 2026-03-02 13:55:26 +08:00
世界 8ae93a98e5 Remove overdue deprecated features 2026-03-01 12:30:43 +08:00
世界 494990f914 Update bypass action behavior for auto redirect 2026-01-17 05:48:41 +08:00
世界 78b4eac974 Add pre-match support for auto redirect 2026-01-17 05:48:39 +08:00
世界 7f3ea8dbd8 Update WireGuard and Tailscale 2026-01-17 05:46:02 +08:00
世界 f84129ca79 Add proxy support for ICMP echo request 2026-01-17 05:44:41 +08:00
世界 239e6ec701 Add preferred_by route rule item 2026-01-17 05:44:27 +08:00
世界 d4fd43cf6f Fix wireguard listen_port 2025-07-08 13:12:34 +08:00
世界 988ac62a1b refactor: Outbound domain resolver 2025-07-08 13:12:14 +08:00
世界 3016338e34 refactor: DNS 2025-07-08 13:12:14 +08:00
世界 4c9455b944 Fix wireguard endpoint 2025-04-18 08:54:40 +08:00
世界 97d41ffde8 Improve pause management 2025-04-08 14:16:22 +08:00
世界 9a1efbe54d Fix domain strategy 2025-01-13 15:14:30 +08:00
世界 83889178ed Improve timeouts 2025-01-13 15:14:30 +08:00
世界 c4b6d0eadb Make GSO adaptive 2025-01-13 15:14:30 +08:00
世界 68781387fe refactor: WireGuard endpoint 2025-01-13 15:14:30 +08:00
世界 bb46cdb2b3 Add multi network dialing 2025-01-13 15:14:30 +08:00
世界 a1be455202 refactor: Modular network manager 2025-01-13 15:14:29 +08:00
世界 19fb214226 refactor: Modular inbound/outbound manager 2025-01-13 15:14:29 +08:00
世界 e233fd4fe5 refactor: Modular inbounds/outbounds 2025-01-13 15:14:29 +08:00