* feat(core): support AUTO detection and manual selection for BindInterface (#10519)
- Implements the 3-state design agreed in #10519:
1. Empty: do not bind interface, preserve existing behavior.
2. AUTO: automatically detect default active physical network interface and bind to it.
3. Specific interface: prioritize user-selected network interface.
- Centralizes interface resolution in NetworkUtils.GetRealBindInterfaceName().
- Supports AUTO in CoreConfigContextBuilder, Singbox, and V2ray template services.
- Adds comprehensive unit tests covering AUTO detection, context validation, and config generation.
* refactor: optimize AUTO bindInterface resolution and add mock-based tests
- Resolve AUTO interface once in CoreConfigContextBuilder and store in CoreConfigContext.ResolvedBindInterface as single-source-of-truth
- Downstream sing-box and Xray outbound/TUN services consume ResolvedBindInterface directly
- Unify TrimEx and case-insensitive check via NetworkUtils.IsAuto to tolerate user input whitespace
- Log and issue validator warning when AUTO fails to resolve any physical adapter
- Extract pure SelectDefaultInterface function with NetworkInterfaceCandidate for deterministic, mock-based unit testing
- Add comprehensive unit tests covering Ethernet vs Wi-Fi preference, no-gateway fallback, and virtual adapter filtering
* refactor(network): filter non-IP NDIS interfaces and simplify AUTO bindInterface resolution
* fix(ui,network): align BindInterface ComboBox width and refine physical NIC matching
* Update CoreConfigContextBuilder.cs
---------
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Removing "?ed=2048" from a WebSocket path also removed the '?', so
"/ws?ed=2048&key=value" was sent to sing-box as "/ws&key=value".
Match "ed" without the separator in front of it and trim the
leftover '&' or '?' instead.
NaiveFmt.ToUri percent-encodes the username and password, so a
password-only profile with the password a:b is exported as
naive+https://a%3Ab@host. Resolve decoded the user info before
looking for the ':' separator and imported it as username "a",
password "b". Split the raw user info first and decode each part.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Use generated JSON type metadata and propagate mapped SQLite types through CRUD and transactional bulk operations. Cover serialization options, affected counts, rollback and native test compatibility without changing UI models or persistence entities.
For a SIP002 link whose user info is "method:password" rather than
base64 (the 2022-blake3 form), ResolveSip002 unescapes the whole user
info and then unescapes the password a second time. A password that
still holds a valid percent sequence after the first pass decays:
p%41ss is written as p%2541ss and imported as pAss.
Same double-decoding as #10027 fixed for query values.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The AnyTLS URI scheme makes the port optional and defaults it to 443,
and two of its three examples leave it out. The naive+https:// and
naive+quic:// link format does the same: the port is omitted when it is
443, and none of its examples carries one.
System.Uri answers -1 for an unregistered scheme with no port, so these
links were imported with Port = -1 and the profile failed IsValid().
Apply the same default Hysteria2Fmt already uses (#10026). An explicit
":0" is still kept as 0.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SocksFmt.Resolve tries ResolveSocksNew first and only falls back to the
legacy socks://base64(user:pass@host:port) parser when System.Uri
rejects the link. System.Uri accepts most legacy links, though: it takes
the base64 as the host (lower-cased) and anything after a '/' as the
path. It only rejects a base64 that ends in '=' padding and has no '/',
so links without padding, or with a '/' or '_' in the base64, were
imported with the base64 as the address, port -1 and no credentials.
A legacy link has no port of its own, so let ResolveSocksNew give up
when the URI has none and leave the link to ResolveSocks.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A speed test only stored a speed above zero, so a node that was skipped
because its delay test failed, or whose download failed, kept the speed
of the previous run. The list showed the skip or error text until it was
sorted or refreshed; then the old speed came back and the node sorted as
if it had been measured. Clear the stored speed when a speed or mixed
test starts.
ConcurrentBag only makes individual operations safe. GetProfileExItem performed FirstOrDefault and AddProfileEx as separate operations, so concurrent speed tests could both miss and create duplicate entries for the same IndexId. Recheck under a dedicated lock before creating, and replace the bag under the same lock in InitData/ClearAll so creation cannot target a bag that is being swapped. Existing reads and item updates stay lock-free.
* Fix data race in ProfileExManager
Parallel speed tests over many profiles corrupt the save queue of test results: IndexIdEnqueue touches a plain Queue<string> from every test thread, so Contains plus Enqueue race - the same profile gets queued twice, and Queue.Contains can even throw ArgumentOutOfRangeException out of SetTestDelay, losing the result. SaveQueueIndexIds has the same class of bug: it snapshots Count and then dequeues in a loop, so concurrent enqueues throw or lose entries. GetProfileExItem could create duplicate rows for the same index the same way.
Guard the queue and the profile lookup with a lock and drain a snapshot instead of a stale count. No behavior change besides correctness.
* Fix data race in ProfileExManager
Parallel speed tests over many profiles corrupt the save queue of test results: IndexIdEnqueue touches a plain Queue<string> from every test thread, so Contains plus Enqueue race - the same profile gets queued twice, and Queue.Contains can even throw ArgumentOutOfRangeException out of SetTestDelay, losing the result. SaveQueueIndexIds has the same class of bug: it snapshots Count and then dequeues in a loop, so concurrent enqueues throw or lose entries.
Switch the queue to ConcurrentQueue and drain it with TryDequeue. As suggested in review, duplicates are not filtered anymore: this is a helper, and the save loop just writes the same row more than once.
* Fix overflow when speed tests have no eligible profiles
* Preserve core executable permissions in Debian packages
* Avoid requesting an icon refresh before view initialization
* Route IPv6 into the Xray TUN on NAT66 hosts and when IPv6 is enabled
#10080 dropped ::/0 from autoSystemRoutingTable whenever the host held no
address inside 2000::/3. A host behind NAT66 holds only unique local
addresses, yet it reaches the IPv6 internet through its IPv6 default
gateway. With ::/0 gone, its IPv6 follows the system default route,
bypasses the tunnel and exposes the real IPv6 address (#10327).
The check also overrode EnableIPv6Address. Before #9930, enabling it
always added ::/0; since #10080 the option only assigned the interface
address, so users who had enabled it were affected as well.
Route ::/0 when EnableIPv6Address is on, and otherwise when the host has
IPv6 connectivity: a global IPv6 address or an IPv6 default gateway.
Routes whose next hop is ::, such as the one the tunnel installs, do not
count. A host with neither, as in #10051, still skips ::/0. The
RouteExcludeAddress branch uses the same decision.
Reproduced in network namespaces with Xray 26.7.28: on a NAT66 client the
7.25.4 TUN inbound let curl -6 reach the echo server from the NAT66
address, while the inbound generated by this change sends it through the
proxy, with EnableIPv6Address on or off.
Fixes#10327
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Simplify: route ::/0 when EnableIPv6Address is on
Drop the rename and the gateway detection; keep HasGlobalIPv6Address
and let EnableIPv6Address override it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142Bna7M2GhesX7P7d4CSrt
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix subscription custom configs ignoring custom config socks port
Full Xray/sing-box configs imported from a subscription no longer got the
subscription's "Custom config socks port" (SubItem.PreSocksPort) after
eff5845 and 9638b4c. 7.24.4 and earlier copied it onto every imported
config, and the Clash, Hysteria2 and raw-file paths still do.
Without it, GetPreSocksItem builds no pre-socks core for these profiles,
so they cannot be used in TUN mode. Setting the port on the profile is
not a workaround, because every subscription update re-creates the
profiles.
* Add regression test for subscription custom config socks port
Add the Russian string for menuCheckAndUpdate, which #10282 added to
ResUI.resx, as "Проверить и обновить". Restore the full Russian text
of menuCheckOnly ("Только проверить") and menuCheckUpdate ("Проверить
обновления"), which were shortened only to fit the fixed 100 px
buttons of the update dialog; they fit once the buttons are sized by
their labels.