* Always route IPv6 into Xray TUN regardless of EnableIPv6Address
EnableIPv6Address controls whether the TUN interface is assigned an IPv6
address, but it also gated whether ::/0 was added to autoSystemRoutingTable.
With the default (false), IPv6 had no route pointing at the TUN device and
followed the system default route instead, leaving the tunnel unproxied and
exposing the host's real IPv6 address.
The embedded template SampleTunInbound already declares both families; the
generated config discarded it. #9843 restored ::/0 only inside the
EnableIPv6Address == true branch, so the false branch still leaks.
Route both families unconditionally and let the option control only the
interface address. The same conditional existed a second time in the
RouteExcludeAddress branch and is fixed as well.
Fixes#9929
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add regression tests for IPv6 routing in the Xray TUN inbound
Both assertions fail on 31044f44 and pass with the fix:
Tun_ShouldRouteIPv6IntoTunnel(enableIPv6Address: False)
Expected collection {"0.0.0.0/0"} to contain "::/0".
TunRouteExcludeAddress_ShouldIncludeIPv6Ranges
Expected collection {...44 IPv4 ranges...} to have an item matching x.Contains(:).
The theory also covers enableIPv6Address: true, which passes on both revisions,
so the tests only fail while the defect is present. The gateway count assertion
pins the intended split of responsibilities: EnableIPv6Address governs the
interface address, never the routing table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: liuclare <177657698+liuclare@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* i18n(ru): add missing Russian translations
Translate the 10 strings missing from ResUI.ru.resx (proxy dial
resolution strategy, Happy Eyeballs, IPv4/IPv6 address labels, and
the custom outbound group). Russian now has full key parity with
ResUI.resx (580/580); placeholder consistency verified for all keys.
Technical terms and config values (outbound, endpoint, UseIP, Happy
Eyeballs) are kept untranslated, matching the zh-Hans locale.
* i18n(ru): remove trailing periods from translations
Align with the Russian UI convention of omitting sentence-final
periods in labels, tooltips and messages (32 values). Inner
punctuation and ellipses are kept; no wording changes.
* i18n(ru): translate TbFakeIPTips left in English
The key existed in ResUI.ru.resx but its value was the untranslated
English text. Translate it following the established glossary (FakeIP
and sing-box remain untranslated).
Replace the requirements table with a single bilingual line under
"Supported Platforms" pointing to the wiki page (Release files
introduction), per the review feedback in the PR: the README stays
minimal and the wiki remains the single source of truth.
* chore(deps): bump Avalonia.Desktop, CliWrap, Semi.Avalonia and Repobot.SQLite
- Avalonia.Desktop 12.1.0 -> 12.1.1
- CliWrap 3.10.2 -> 3.10.4
- Semi.Avalonia 12.1.0 -> 12.1.0.1
- Semi.Avalonia.DataGrid 12.1.0 -> 12.1.0.1
- Repobot.SQLite.Unofficial 3.53.3.10 -> 3.53.4
Left unchanged on purpose:
- ReactiveUI.Avalonia: nuget.org reports 14.7.1 as the highest version, but it
is unlisted and belongs to the old versioning line (netstandard2.0/net6.0/
net7.0, Avalonia >= 11.0.0, ReactiveUI >= 19.4.1). Moving to it would revert
both #9148 and #9678.
- SkiaSharp.NativeAssets.Linux: 4.151.0 is available, but Avalonia.Skia 12.1.1
resolves the managed SkiaSharp to 3.119.4. Pairing 4.x native assets with a
3.x managed binding risks native entry point failures on Linux; 3.119.4 is
the latest release on the 3.x branch.
* chore(deps): bump ReactiveUI to 24.1.0 and ReactiveUI.SourceGenerators to 3.2.0
- ReactiveUI 24.0.0 -> 24.1.0
- ReactiveUI.WPF 24.0.0 -> 24.1.0
- ReactiveUI.SourceGenerators 3.1.0 -> 3.2.0
ReactiveUI and ReactiveUI.WPF move together: ReactiveUI.WPF 24.1.0 requires
ReactiveUI >= 24.1.0. ReactiveUI.Avalonia 12.1.0 declares a ReactiveUI >= 24.0.0
minimum, so it resolves against 24.1.0 without changes; ReactiveUI.Primitives
(7.1.0) and Splat (20.2.0) are unaffected.
* chore(deps): bump Avalonia.Controls.DataGrid and ReactiveUI.Avalonia
- Avalonia.Controls.DataGrid 12.1.0 -> 12.1.2
- ReactiveUI.Avalonia 12.1.0 -> 12.1.1
ReactiveUI.Avalonia 12.1.1 requires Avalonia >= 12.1.1 and ReactiveUI >= 24.1.0,
both of which are already in place after the previous commits on this branch.
https://github.com/2dust/v2rayN/issues/9863
Add a short 1-second delay after terminating core processes on Linux, macOS, and in CoreAdminManager so ports and process resources have time to settle before the next step runs.
* Restore the silently dropped embedded TUN routing rules
tun_singbox_rules declares "network": "udp" as a bare string, but
Rule4Sbox.network is a List<string>. System.Text.Json therefore throws on
the very first rule, and JsonUtils.Deserialize swallows it and returns
default. GenRouting only checks for null before AddRange, so the whole
embedded rule set is dropped without a trace.
Both rules it carries have thus never reached a generated config: the
NetBIOS/mDNS reject and the multicast reject. The mismatch predates the
sing-box 1.12 migration - the template has used the string form since the
rules file was introduced.
- declare network as an array so the template matches Rule4Sbox
* Drop traffic addressed to the TUN's own addresses
With auto_route the TUN steals the default route, so a packet whose
destination is the TUN interface's own address is handed to sing-box instead
of being looped back by the kernel. Routing then matches ip_is_private and
sends it to `direct`, whose interface is auto-detected as the TUN again, so
the packet is written straight back into the TUN and re-enters routing. The
loop never terminates and pins a CPU core.
Seen in the wild on macOS: a WebRTC client offered the TUN's own
fc00::172:18:0:1 ULA as an ICE candidate and the resulting STUN
connectivity checks sustained ~8k packets/s out of the interface at 800%
CPU, 21 GB written to the TUN over five hours - against 3 GB read, the
asymmetry that gives the loop away.
Nothing legitimate is addressed to those addresses, so reject them before
any outbound rule can match. Use method "drop" rather than the default
ICMP unreachable, whose destination would be the looping address itself.
- reject the TUN inbound's own addresses, taken from the generated inbound
so the two cannot drift apart
Follow-up to #9830: audit of all TunModeItem.EnableTun usages found
three more launch-path reads of the live mutable config where the
behavior must agree with the context snapshot that generated the
config:
- CoreConfigClashService (mihomo custom config): the tun section was
decided from the live config while the mihomo launch elevation uses
the snapshot; a mid-reload toggle could produce a config containing
tun launched without sudo, the same failure fixed in #9830. The tun
state is now passed in as a snapshot.
- CoreManager.LoadCore: the Windows RemoveTunDevice cleanup now checks
the main/pre context snapshots.
- CoreManager.WaitForProxyPort: preContext.AppConfig is a shared live
reference; use preContext.IsTunEnabled instead.
Reads that intentionally stay live: StatusBarViewModel (UI state
source), CoreConfigContextBuilder (the snapshot capture point),
GetPreSocksItem (called during snapshot construction, self-consistent),
and AppManager.StatePort2 (transient mid-reload skew only, self-heals
after reload).
CoreManager.RunProcess decided sudo elevation from the live mutable
_config.TunModeItem.EnableTun while the launched config was generated
from the immutable CoreConfigContext snapshot. If the TUN state changed
while a reload was in flight, a core whose config contains a TUN
inbound could launch without elevation and die within the 100ms health
check ("Failed to run core"), with no stderr shown.
The elevation decision now follows context.IsTunEnabled /
preContext.IsTunEnabled, so the generated config and the launch mode
always agree. With legacy TUN protect, the sing-box pre-core hosting
TUN is elevated and the main core no longer runs as root needlessly.
* fix: sanitize system proxy exceptions to avoid macOS ExceptionsList being dropped
On macOS/Linux the exception list is comma-separated and passed to networksetup. It was only stripped of spaces, so a stray newline or a trailing comma left a malformed/empty entry. macOS configd then silently rejects the whole ExceptionsList (scutil --proxy shows no ExceptionsList), so the bypass never takes effect. Split on ',' with TrimEntries + RemoveEmptyEntries and rejoin; behavior-preserving for well-formed input. Windows path (';') is left unchanged.
Note: not built locally (no .NET SDK on hand); relying on CI.
* Update SysProxyHandler.cs
---------
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>