Compare commits

..

9 Commits

Author SHA1 Message Date
2dust 1883178b5f up 7.25.6 2026-10-11 13:55:36 +08:00
2dust 1cde521d75 Prevent zip path traversal in app upgrade
Hardened upgrade extraction by validating each zip entry resolves under the app startup directory before writing files, and aborting with a clear failure message on suspicious paths. Added shared path utilities for directory containment checks and OS-aware path string comparison, then reused them for executable matching and `bin` prefix checks during extraction.

https://github.com/2dust/v2rayN/issues/10313
2026-10-11 13:52:28 +08:00
Ali Ahmadi 2407d44f4f Fix empty latency tests and Linux startup errors (#10335)
* Fix overflow when speed tests have no eligible profiles

* Preserve core executable permissions in Debian packages

* Avoid requesting an icon refresh before view initialization
2026-10-11 11:06:46 +08:00
dependabot[bot] fbf822d74c Bump TUnit from 1.72.16 to 1.73.19 (#10334)
---
updated-dependencies:
- dependency-name: TUnit
  dependency-version: 1.73.19
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-11 10:56:36 +08:00
liu-xindi 4869a6cbba Route IPv6 into the Xray TUN when EnableIPv6Address is on (#10329)
* 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>
2026-10-11 10:55:39 +08:00
DHR60 b501efbc45 Fix (#10326) 2026-10-10 09:14:30 +08:00
Miheichev Aleksandr Sergeevich 5ea8ae6420 fix(ui): wrap the pre-release label in the update dialog (#10303)
The per-core pre-release switch and its label sit in a horizontal
StackPanel inside a fixed-width column of the update dialog.
Translations longer than the column do not fit: in Avalonia the label
runs over the core name and the update status (Russian, French,
Hungarian, Persian), and in WPF it is cut off. Use a DockPanel so that
the label wraps inside its column, as the interval label of the
subscription window does since #10292. The dialog keeps its width.

In Avalonia the panel also had a vertical margin on top of the
margin of the switch, so the two rows with the switch were 8 px taller
than the other rows. Use a horizontal-only margin, as the WPF view
already does.
2026-10-07 11:11:27 +08:00
Miheichev Aleksandr Sergeevich fa9d4a4769 fix(ui): line up the backup dialog columns again (#10302)
#10292 made the label and button columns of the backup view Auto with
a minimum width, so that longer labels and buttons fit. Each of the two
cards sizes its own grid, so when the remote (WebDAV) buttons are wider
than the local ones, the local buttons no longer sit under the remote
ones and the separators of the two cards differ in length. This shows
in English and in every other language except Chinese.

Share the column widths of the two cards with SharedSizeGroup: both
cards line up again, and the long labels and buttons still fit. In
Avalonia the buttons stay centred in the shared column; in WPF they
fill it as before, so the local buttons get as wide as the remote ones.
2026-10-07 11:09:32 +08:00
DHR60 771ef3192a Update cert (#10308) 2026-10-07 11:04:58 +08:00
18 changed files with 231 additions and 144 deletions
+9
View File
@@ -676,6 +676,15 @@ EOF
find "$stage/opt/v2rayN" -type f -exec chmod 0644 {} + find "$stage/opt/v2rayN" -type f -exec chmod 0644 {} +
[[ -f "$stage/opt/v2rayN/v2rayN" ]] && chmod 0755 "$stage/opt/v2rayN/v2rayN" || true [[ -f "$stage/opt/v2rayN/v2rayN" ]] && chmod 0755 "$stage/opt/v2rayN/v2rayN" || true
# Core binaries must be executable before installation: ordinary users cannot
# chmod the root-owned files installed by dpkg.
local core
for core in xray/xray sing_box/sing-box mihomo/mihomo; do
if [[ -f "$stage/opt/v2rayN/bin/$core" ]]; then
chmod 0755 "$stage/opt/v2rayN/bin/$core"
fi
done
deb_out="$OUTPUT_DIR/v2rayn_${VERSION}_${deb_arch}.deb" deb_out="$OUTPUT_DIR/v2rayn_${VERSION}_${deb_arch}.deb"
dpkg-deb --root-owner-group --build "$stage" "$deb_out" dpkg-deb --root-owner-group --build "$stage" "$deb_out"
+10 -3
View File
@@ -45,6 +45,8 @@ internal class UpgradeApp
var thisAppOldFile = $"{Utils.GetExePath()}.tmp"; var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
File.Delete(thisAppOldFile); File.Delete(thisAppOldFile);
var splitKey = "/"; var splitKey = "/";
var allowedBaseDir = Utils.StartupPath();
var pathComparison = Utils.GetPathComparison();
using var archive = ZipFile.OpenRead(fileName); using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries) foreach (var entry in archive.Entries)
@@ -65,16 +67,21 @@ internal class UpgradeApp
} }
var fullName = string.Join(splitKey, lst[1..lst.Length]); var fullName = string.Join(splitKey, lst[1..lst.Length]);
var entryOutputPath = Utils.GetPath(fullName);
if (!Utils.IsPathUnderDirectory(allowedBaseDir, entryOutputPath))
{
Console.WriteLine($"{Resx.Resource.FailedUpgrade} blocked potential path traversal: {entry.FullName}");
return;
}
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase)) if (string.Equals(Utils.GetExePath(), entryOutputPath, pathComparison))
{ {
File.Move(Utils.GetExePath(), thisAppOldFile); File.Move(Utils.GetExePath(), thisAppOldFile);
} }
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped //In the bin folder, if the file already exists, it will be skipped
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath)) if (fullName.StartsWith("bin", pathComparison) && File.Exists(entryOutputPath))
{ {
continue; continue;
} }
+22
View File
@@ -24,6 +24,28 @@ internal class Utils
return Path.Combine(startupPath, fileName); return Path.Combine(startupPath, fileName);
} }
public static bool IsPathUnderDirectory(string baseDir, string targetPath)
{
if (string.IsNullOrWhiteSpace(baseDir) || string.IsNullOrWhiteSpace(targetPath))
{
return false;
}
var baseFull = Path.GetFullPath(baseDir);
var targetFull = Path.GetFullPath(targetPath);
baseFull = baseFull.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
return targetFull.StartsWith(baseFull, GetPathComparison());
}
public static StringComparison GetPathComparison()
{
return OperatingSystem.IsWindows()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
}
public static string V2rayN => "v2rayN"; public static string V2rayN => "v2rayN";
public static void StartV2RayN() public static void StartV2RayN()
+1 -1
View File
@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.25.5</Version> <Version>7.25.6</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
+1 -1
View File
@@ -27,7 +27,7 @@
<PackageVersion Include="sqlite-net-e" Version="1.11.285" /> <PackageVersion Include="sqlite-net-e" Version="1.11.285" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" /> <PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="TUnit" Version="1.72.16" /> <PackageVersion Include="TUnit" Version="1.73.19" />
<PackageVersion Include="TUnit.Assertions.Should" Version="1.72.16-beta" /> <PackageVersion Include="TUnit.Assertions.Should" Version="1.72.16-beta" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="18.1.0" /> <PackageVersion Include="YamlDotNet" Version="18.1.0" />
@@ -0,0 +1,42 @@
namespace ServiceLib.Tests.Common;
public class UnixFileModeTests
{
[Test]
public async Task AlreadyExecutableSystemBinary_ShouldNotRequireOwnership()
{
if (OperatingSystem.IsWindows())
{
return;
}
// /bin/sh is executable but owned by root on the Unix CI runners.
// A regular user must not need chmod permission just to launch it.
var mode = File.GetUnixFileMode("/bin/sh");
await Utils.SetUnixFileMode("/bin/sh").Should().BeTrue();
await File.GetUnixFileMode("/bin/sh").Should().BeEqualTo(mode);
}
[Test]
public async Task NonExecutableOwnedFile_ShouldGainExecuteBitsWithoutLosingOtherPermissions()
{
if (OperatingSystem.IsWindows())
{
return;
}
var path = Path.GetTempFileName();
try
{
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead;
File.SetUnixFileMode(path, mode);
await Utils.SetUnixFileMode(path).Should().BeTrue();
await File.GetUnixFileMode(path).Should().BeEqualTo(mode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute);
}
finally
{
File.Delete(path);
}
}
}
@@ -587,13 +587,11 @@ public class CoreConfigV2rayServiceTests
} }
[Test] [Test]
[Arguments(true)] public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6()
[Arguments(false)]
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6(bool enableIPv6Address)
{ {
// A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel, // A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel,
// while ::/0 would pull IPv6 attempts into a tunnel they cannot leave. // while ::/0 would pull IPv6 attempts into a tunnel they cannot leave.
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address); var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: false);
CoreConfigTestFactory.BindAppManagerConfig(config); CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main"); var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
@@ -611,6 +609,26 @@ public class CoreConfigV2rayServiceTests
await ipv6Routes.Should().BeEmpty(); await ipv6Routes.Should().BeEmpty();
} }
[Test]
public async Task GenerateClientConfigContent_Tun_ShouldRouteIPv6WhenEnabledWithoutGlobalIPv6()
{
// Hosts behind NAT66 hold only ULA addresses, so EnableIPv6Address must still route ::/0.
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: true);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray, hasGlobalIPv6Address: false);
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue();
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
await tunInbound.Should().NotBeNull();
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("::/0");
}
[Test] [Test]
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6() public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6()
{ {
@@ -0,0 +1,59 @@
namespace ServiceLib.Tests.Services;
public class SpeedtestServiceBatchTests
{
[Test]
[Arguments(0)]
[Arguments(1000)]
public async Task EmptySelection_ShouldReturnNoBatches(int pageSize)
{
await GetBatches([], pageSize).Count.Should().BeEqualTo(0);
}
[Test]
[Arguments(0)]
[Arguments(-1)]
public async Task NonPositivePageSize_ShouldKeepEveryEligibleProfile(int pageSize)
{
var profiles = CreateProfiles();
var batches = GetBatches(profiles, pageSize);
await batches.Count.Should().BeEqualTo(profiles.Count);
await batches.SelectMany(batch => batch).Count().Should().BeEqualTo(profiles.Count);
foreach (var profile in profiles)
{
await batches.SelectMany(batch => batch).Count(item => ReferenceEquals(item, profile)).Should().BeEqualTo(1);
}
}
[Test]
public async Task NormalBatches_ShouldRespectPageSizeAndKeepCoreTypesSeparate()
{
var profiles = CreateProfiles();
var batches = GetBatches(profiles, 2);
await batches.Count.Should().BeEqualTo(3);
await batches.SelectMany(batch => batch).Count().Should().BeEqualTo(profiles.Count);
foreach (var batch in batches)
{
await (batch.Count > 0 && batch.Count <= 2).Should().BeTrue();
await batch.Select(item => item.CoreType).Distinct().Count().Should().BeEqualTo(1);
}
}
private static List<ServerTestItem> CreateProfiles() =>
[
new() { CoreType = ECoreType.Xray },
new() { CoreType = ECoreType.Xray },
new() { CoreType = ECoreType.Xray },
new() { CoreType = ECoreType.sing_box },
new() { CoreType = ECoreType.sing_box }
];
private static List<List<ServerTestItem>> GetBatches(List<ServerTestItem> profiles, int pageSize)
{
var service = new SpeedtestService(new Config { SpeedTestItem = new() }, _ => Task.CompletedTask);
var method = typeof(SpeedtestService).GetMethod("GetTestBatchItem", BindingFlags.Instance | BindingFlags.NonPublic)!;
return (List<List<ServerTestItem>>)method.Invoke(service, [profiles, pageSize])!;
}
}
+5 -1
View File
@@ -1441,7 +1441,11 @@ public class Utils
if (File.Exists(fileName)) if (File.Exists(fileName))
{ {
var currentMode = File.GetUnixFileMode(fileName); var currentMode = File.GetUnixFileMode(fileName);
File.SetUnixFileMode(fileName, currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute); var executableMode = currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
if (currentMode != executableMode)
{
File.SetUnixFileMode(fileName, executableMode);
}
return true; return true;
} }
} }
-105
View File
@@ -300,40 +300,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
@@ -732,20 +698,6 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1 4uJEvlz36hz1
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
@@ -805,38 +757,6 @@ HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
@@ -1189,31 +1109,6 @@ AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
RA+GsCyRxj3qrg+E RA+GsCyRxj3qrg+E
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
@@ -67,12 +67,10 @@ public partial class CoreConfigV2rayService
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First(); var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
tunInbound.settings.gateway = [address]; tunInbound.settings.gateway = [address];
// Route both families into the tunnel regardless of EnableIPv6Address. That option only // Without ::/0, IPv6 bypasses the tunnel. Skip it only when the host has no global IPv6
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing // address and EnableIPv6Address is off; NAT66 hosts have only ULA addresses.
// table makes IPv6 follow the system default route and bypass the tunnel entirely. var routeIPv6 = _config.TunModeItem.EnableIPv6Address || context.HasGlobalIPv6Address;
// A host without a global IPv6 address is the exception: it has nothing to leak, tunInbound.settings.autoSystemRoutingTable = routeIPv6
// and IPv6 sent into the tunnel would have no way back out.
tunInbound.settings.autoSystemRoutingTable = context.HasGlobalIPv6Address
? ["0.0.0.0/0", "::/0"] ? ["0.0.0.0/0", "::/0"]
: ["0.0.0.0/0"]; : ["0.0.0.0/0"];
if (_config.TunModeItem.EnableIPv6Address == true) if (_config.TunModeItem.EnableIPv6Address == true)
@@ -108,7 +106,7 @@ public partial class CoreConfigV2rayService
.Where(x => x != null).ToList(); .Where(x => x != null).ToList();
var includeList = new List<IPNetwork2> { wholeInternet }; var includeList = new List<IPNetwork2> { wholeInternet };
var includeListV6 = context.HasGlobalIPv6Address var includeListV6 = routeIPv6
? new List<IPNetwork2> { wholeInternetV6 } ? new List<IPNetwork2> { wholeInternetV6 }
: new List<IPNetwork2>(); : new List<IPNetwork2>();
@@ -587,6 +587,12 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = []; List<List<ServerTestItem>> lstTest = [];
if (lstSelected.Count == 0)
{
return lstTest;
}
pageSize = Math.Max(pageSize, 1);
var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList(); var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList(); var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
@@ -527,7 +527,7 @@ public partial class MainWindowViewModel : MyReactiveObject
public async Task AddServerViaImageAsync() public async Task AddServerViaImageAsync()
{ {
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default); var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
await AddScanResultAsync(imageFileName); await ScanImageResult(imageFileName);
} }
public async Task ScanImageResult(string fileName) public async Task ScanImageResult(string fileName)
@@ -219,7 +219,8 @@ public partial class StatusBarViewModel : MyReactiveObject
await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinRouting(_config);
await RefreshRoutingsMenu(); await RefreshRoutingsMenu();
await InboundDisplayStatus(); await InboundDisplayStatus();
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true); // The view refreshes its icon on initialization; interaction handlers are not registered yet.
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, false);
BlRouting = true; BlRouting = true;
} }
@@ -24,7 +24,7 @@
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel Grid.IsSharedSizeScope="True">
<Border <Border
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -32,8 +32,14 @@
<Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto"> <Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="300" /> <ColumnDefinition
<ColumnDefinition Width="Auto" MinWidth="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
@@ -82,8 +88,14 @@
Theme="{DynamicResource CardBorder}"> Theme="{DynamicResource CardBorder}">
<Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto,Auto"> <Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="300" /> <ColumnDefinition
<ColumnDefinition Width="Auto" MinWidth="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Row="0" Grid.Row="0"
@@ -75,22 +75,23 @@
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<StackPanel <DockPanel
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin4}" Margin="{StaticResource MarginLr4}"
IsVisible="{Binding ShowCheckPreRelease}" IsVisible="{Binding ShowCheckPreRelease}">
Orientation="Horizontal">
<ToggleSwitch <ToggleSwitch
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding IsCheckPreRelease}" IsChecked="{Binding IsCheckPreRelease}"
OffContent="" OffContent=""
OnContent="" /> OnContent="" />
<TextBlock <TextBlock
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" /> Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}"
</StackPanel> TextWrapping="Wrap" />
</DockPanel>
<TextBlock <TextBlock
Grid.Column="2" Grid.Column="2"
+17 -5
View File
@@ -30,7 +30,7 @@
Style="{StaticResource ToolbarTextBlock}" /> Style="{StaticResource ToolbarTextBlock}" />
</DockPanel> </DockPanel>
<StackPanel> <StackPanel Grid.IsSharedSizeScope="True">
<materialDesign:Card Width="Auto" Margin="{StaticResource Margin8}"> <materialDesign:Card Width="Auto" Margin="{StaticResource Margin8}">
<Grid Margin="{StaticResource Margin8}"> <Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -40,8 +40,14 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="300" /> <ColumnDefinition
<ColumnDefinition Width="Auto" MinWidth="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
@@ -98,8 +104,14 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="300" /> <ColumnDefinition
<ColumnDefinition Width="Auto" MinWidth="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
+5 -4
View File
@@ -85,21 +85,22 @@
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<StackPanel <DockPanel
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource MarginLeftRight4}" Margin="{StaticResource MarginLeftRight4}"
Orientation="Horizontal"
Visibility="{Binding ShowCheckPreRelease, Converter={StaticResource BoolToVisConverter}}"> Visibility="{Binding ShowCheckPreRelease, Converter={StaticResource BoolToVisConverter}}">
<ToggleButton <ToggleButton
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding IsCheckPreRelease}" /> IsChecked="{Binding IsCheckPreRelease}" />
<TextBlock <TextBlock
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" /> Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}"
</StackPanel> TextWrapping="Wrap" />
</DockPanel>
<TextBlock <TextBlock
Grid.Column="2" Grid.Column="2"