Compare commits

...

27 Commits

Author SHA1 Message Date
2dust
92c3df45ae up 7.24.3 2026-07-26 21:25:31 +08:00
hyjhyj111
ee7e21268a Fix TUN core elevation: decide sudo from the config context snapshot (#9830)
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.
2026-07-26 21:24:11 +08:00
2dust
dc216c2b02 Rename TunIp to TunIP 2026-07-26 21:07:36 +08:00
2dust
beffa919d3 Use /126 prefixes for TUN IPv6 addresses
https://github.com/2dust/v2rayN/pull/9830
2026-07-26 20:46:32 +08:00
2dust
a654826231 Enable legacy TUN protect by default 2026-07-26 20:46:01 +08:00
2dust
b01476d147 Fix tun inbound dns setting 2026-07-26 10:29:35 +08:00
2dust
cf8dd45abe up 7.24.2 2026-07-24 16:57:53 +08:00
2dust
9bba6f53f8 Update Directory.Packages.props 2026-07-24 16:57:22 +08:00
2dust
cd77f1d882 Code clean 2026-07-24 16:57:19 +08:00
mymyme
0427037638 fix: sanitize system proxy exceptions to avoid macOS ExceptionsList b… (#9815)
* 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>
2026-07-24 14:12:10 +08:00
2dust
e749b81ecf Add configurable TUN IPv4/IPv6 addresses
https://github.com/2dust/v2rayN/issues/9810
2026-07-24 10:13:08 +08:00
JieXu
fd2c942231 Update package-rhel-loong.sh (#9792)
* Update package-rhel-loong.sh

* Update package-debian-loong.sh

* Update Directory.Packages.props
2026-07-20 09:30:52 +08:00
tt2563
fa42eb670f Update ResUI.zh-Hant.resx (#9783)
Update Traditional Chinese translation
2026-07-19 09:54:45 +08:00
DHR60
0d42996573 Fix ui (#9789) 2026-07-19 09:54:26 +08:00
DHR60
a37772f12b Fakeip (#9786)
* Add fakeip range

* Xray fakedns
2026-07-18 17:47:14 +08:00
DHR60
531e0e9443 Fix (#9787) 2026-07-18 17:45:42 +08:00
2dust
984c77f0c4 up 7.24.1 2026-07-17 09:26:10 +08:00
2dust
b5e0be47a5 Code clean 2026-07-17 09:25:13 +08:00
dependabot[bot]
6df0e6de1b Bump actions/setup-dotnet from 5.4.0 to 6.0.0 (#9778)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.4.0...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-17 09:13:28 +08:00
autorepobot
2b5328665f Update package-rhel.sh (#9780)
* Update package-rhel.sh

* Update package-rhel-riscv.sh

* Update package-rhel-loong.sh

* Update package-debian.sh

* Update package-debian-riscv.sh

* Update package-debian-loong.sh

* Update package-debian-riscv.sh

* Update package-rhel-riscv.sh
2026-07-17 09:13:19 +08:00
DHR60
4584ecc5c9 Add HappyEyeballs (#9772)
* Add HappyEyeballs

* Clean up the code

* Fix
2026-07-17 09:12:50 +08:00
DHR60
d9dc2d7cf5 Fix (#9776) 2026-07-17 09:11:28 +08:00
DHR60
36f8565b7a Update cert (#9773) 2026-07-17 08:59:30 +08:00
DHR60
f55d8b2565 Sync xray (#9708)
* Sync xray fragment

* Sync xray freedom domainStrategy

* Add Strategy4ProxyDial
2026-07-16 18:55:06 +08:00
DHR60
09ea4890a7 Fix (#9771) 2026-07-16 17:37:17 +08:00
DHR60
5cc2aaba13 Fix (#9769) 2026-07-16 17:37:04 +08:00
DHR60
b8889bad86 Fix (#9768) 2026-07-16 17:36:51 +08:00
70 changed files with 3343 additions and 3986 deletions

View File

@@ -1,3 +1,49 @@
/*
==============================================================================
THIRD-PARTY CA CERTIFICATE DATA ATTRIBUTION
==============================================================================
This component includes CA certificate data obtained from third-party sources.
1. Common CA Database (CCADB)
------------------------------------------------------------------------------
Website:
https://www.ccadb.org/
Data source:
https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites
License:
Community Data License Agreement Permissive, Version 2.0 (CDLA-2.0 Permissive)
The names and trademarks of CCADB contributors may not be used to endorse or
promote products derived from this data without prior written permission.
License text:
https://cdla.dev/permissive-2-0/
2. Chromium Root Store
------------------------------------------------------------------------------
Source:
Chromium Project
Data source:
https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT
License:
BSD 3-Clause License
Copyright:
Copyright (c) The Chromium Authors
The Chromium Root Store data is distributed under the BSD 3-Clause License.
License text:
https://opensource.org/licenses/BSD-3-Clause
==============================================================================
*/
using System;
using System.Net.Http;
using System.Text;
@@ -27,68 +73,23 @@ Console.WriteLine("\nAll done!");
async Task ProcessMozillaAsync(string outputDir)
{
const string certdataUrl = "https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt";
const string certdataUrl = "https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites";
var outputFile = Path.Combine(outputDir, "mozilla_roots_pem");
Console.WriteLine("Downloading Mozilla certdata.txt...");
var content = await client.GetStringAsync(certdataUrl);
Console.WriteLine("Parsing MULTILINE_OCTAL to PEM...");
var pems = ParseMozillaCertData(content);
var pemMatches = Regex.Matches(content, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
var pems = new List<string>();
foreach (Match m in pemMatches)
{
pems.Add(m.Groups[1].Value.Trim());
}
await File.WriteAllTextAsync(outputFile, string.Join("\n\n", pems));
Console.WriteLine($"Mozilla roots saved to: {outputFile} ({pems.Count} certificates)");
}
List<string> ParseMozillaCertData(string text)
{
var pems = new List<string>();
var matches = Regex.Matches(text,
@"CKA_VALUE MULTILINE_OCTAL\s*([\s\S]*?)\s*END",
RegexOptions.Multiline);
foreach (Match m in matches)
{
var octalBlock = m.Groups[1].Value.Trim();
var der = OctalToBytes(octalBlock);
if (der.Length > 0)
{
var pem = ConvertToPem(der);
pems.Add(pem);
}
}
return pems;
}
byte[] OctalToBytes(string octalBlock)
{
var bytes = new List<byte>();
var matches = Regex.Matches(octalBlock, @"\\(\d{1,3})");
foreach (Match m in matches)
{
bytes.Add((byte)Convert.ToInt32(m.Groups[1].Value, 8));
}
return bytes.ToArray();
}
string ConvertToPem(byte[] der)
{
var base64 = Convert.ToBase64String(der);
var sb = new StringBuilder();
sb.Append("-----BEGIN CERTIFICATE-----\n");
for (int i = 0; i < base64.Length; i += 64)
{
var length = Math.Min(64, base64.Length - i);
sb.Append(base64.Substring(i, length));
sb.Append("\n");
}
sb.Append("-----END CERTIFICATE-----\n");
return sb.ToString().TrimEnd();
}
async Task ProcessChromeAsync(string outputDir)
{
const string chromeCertsUrl = "https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT";
@@ -99,7 +100,7 @@ async Task ProcessChromeAsync(string outputDir)
var decoded = Convert.FromBase64String(base64Content.Replace("\n", ""));
var text = Encoding.UTF8.GetString(decoded);
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
var pems = new List<string>();
foreach (Match m in pemMatches)

View File

@@ -52,7 +52,7 @@ jobs:
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
- name: Setup .NET 10.0.1xx
uses: actions/setup-dotnet@v5.4.0
uses: actions/setup-dotnet@v6.0.0
with:
dotnet-version: 10.0.1xx

View File

@@ -69,7 +69,7 @@ jobs:
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
- name: Setup .NET
uses: actions/setup-dotnet@v5.4.0
uses: actions/setup-dotnet@v6.0.0
with:
dotnet-version: '10.0.1xx'

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.4.0
uses: actions/setup-dotnet@v6.0.0
with:
dotnet-version: '8.0.x'

View File

@@ -13,8 +13,8 @@ PKGROOT="v2rayN-publish"
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
OUTPUT_DIR="${HOME}/debbuild"
DOTNET_TFM="net10.0"
DOTNET_LOONGARCH_VERSION="10.0.109"
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
DOTNET_LOONGARCH_VERSION="10.0.110"
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
@@ -597,8 +597,8 @@ package_binary() {
write_desktop_file "$stage"
write_maintainer_scripts "$debian_dir"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
mkdir -p "$workdir/debian"
cat > "$workdir/debian/control" <<EOF
Source: v2rayn

View File

@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
PKGROOT="v2rayN-publish"
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
OUTPUT_DIR="${HOME}/debbuild"
DOTNET_RISCV_VERSION="10.0.109"
DOTNET_RISCV_VERSION="10.0.110"
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
@@ -595,8 +595,8 @@ package_binary() {
write_desktop_file "$stage"
write_maintainer_scripts "$debian_dir"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
mkdir -p "$workdir/debian"
cat > "$workdir/debian/control" <<EOF
Source: v2rayn

View File

@@ -609,7 +609,7 @@ package_binary() {
write_desktop_file "$stage"
write_maintainer_scripts "$debian_dir"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
mkdir -p "$workdir/debian"
cat > "$workdir/debian/control" <<EOF

View File

@@ -12,8 +12,8 @@ MIN_KERNEL="6.12"
PKGROOT="v2rayN-publish"
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
RPM_TOPDIR="${HOME}/rpmbuild"
DOTNET_LOONGARCH_VERSION="10.0.109"
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
DOTNET_LOONGARCH_VERSION="10.0.110"
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
@@ -513,9 +513,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.39
Requires: fontconfig >= 2.15.0
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.2.0
Requires: coreutils >= 9.5
Requires: bash >= 5.2.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 9.4
Requires: bash >= 5.2.21
Requires: freetype >= 2.13
%description

View File

@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
PKGROOT="v2rayN-publish"
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
RPM_TOPDIR="${HOME}/rpmbuild"
DOTNET_RISCV_VERSION="10.0.109"
DOTNET_RISCV_VERSION="10.0.110"
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
@@ -512,9 +512,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.39
Requires: fontconfig >= 2.15.0
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.2.0
Requires: coreutils >= 9.5
Requires: bash >= 5.2.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 9.4
Requires: bash >= 5.2.21
Requires: freetype >= 2.13
%description

View File

@@ -501,9 +501,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.39
Requires: fontconfig >= 2.15.0
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.2.0
Requires: coreutils >= 9.5
Requires: bash >= 5.2.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 9.4
Requires: bash >= 5.2.21
Requires: freetype >= 2.13
%description

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.24.0</Version>
<Version>7.24.3</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -9,13 +9,13 @@
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.0" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.0" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.2" />
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.0.3" />
<PackageVersion Include="CliWrap" Version="3.10.2" />
<PackageVersion Include="Downloader" Version="5.9.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageVersion Include="Downloader" Version="5.9.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
<PackageVersion Include="QRCoder" Version="1.8.0" />
@@ -27,7 +27,7 @@
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
<PackageVersion Include="NLog" Version="6.1.4" />
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3.10" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
@@ -36,4 +36,4 @@
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
</ItemGroup>
</Project>
</Project>

View File

@@ -59,7 +59,7 @@ internal static class CoreConfigTestFactory
},
WebDavItem = new WebDavItem(),
CheckUpdateItem = new CheckUpdateItem(),
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Length = "100-200", Interval = "10-20" },
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Lengths = ["100-200"], Delays = ["10-20"] },
Inbound =
[
new InItem
@@ -84,6 +84,7 @@ internal static class CoreConfigTestFactory
ParallelQuery = false,
Strategy4Freedom = Global.AsIs,
Strategy4Proxy = Global.AsIs,
Strategy4ProxyDial = Global.AsIs,
},
IndexId = string.Empty,
SubIndexId = string.Empty,

View File

@@ -592,4 +592,38 @@ public class CoreConfigSingboxServiceTests
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
proxy.server.Should().BeNull();
}
[Fact]
public void GenerateClientConfigContent_TunSystemStackWithIpv6_ShouldUsePrefixWithPeerAddress()
{
// Regression test for #9820: sing-box fails with "need one more IPv6 address in
// first prefix for system stack" when the TUN inbound uses a /128 IPv6 prefix.
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
config.TunModeItem.EnableTun = true;
config.TunModeItem.Stack = "system";
config.TunModeItem.EnableIPv6Address = true;
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
{
IsTunEnabled = true,
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
var tun = cfg.inbounds.First(i => i.type == "tun");
tun.address.Should().NotBeNullOrEmpty();
foreach (var address in tun.address!)
{
var prefixLength = int.Parse(address[(address.LastIndexOf('/') + 1)..]);
var isIpv6 = address.Contains(':');
prefixLength.Should().BeLessThanOrEqualTo(isIpv6 ? 126 : 30,
$"'{address}' must leave room for the peer address the system stack derives");
}
}
}

View File

@@ -567,7 +567,7 @@ public class CoreConfigV2rayServiceTests
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
directOutbound.Should().NotBeNull();
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
}
[Fact]

View File

@@ -0,0 +1,44 @@
using AwesomeAssertions;
using ServiceLib.Enums;
using ServiceLib.Manager;
using Xunit;
namespace ServiceLib.Tests.Manager;
public class CoreManagerTests
{
[Theory]
[InlineData(ECoreType.sing_box)]
[InlineData(ECoreType.mihomo)]
[InlineData(ECoreType.Xray)]
public void ShouldRunAsSudo_TunLaunchOnNonWindows_RequiresElevation(ECoreType coreType)
{
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeTrue();
}
[Fact]
public void ShouldRunAsSudo_NonTunLaunch_ShouldNotElevate()
{
// Regression guard for the macOS TUN failure: the elevation decision must follow
// the context snapshot that generated the config. A launch whose snapshot has TUN
// disabled must never elevate, and a launch whose snapshot has TUN enabled must
// elevate regardless of later changes to the live config.
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.sing_box, isNonWindows: true).Should().BeFalse();
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.Xray, isNonWindows: true).Should().BeFalse();
}
[Fact]
public void ShouldRunAsSudo_OnWindows_ShouldNotElevate()
{
CoreManager.ShouldRunAsSudo(isTunLaunch: true, ECoreType.sing_box, isNonWindows: false).Should().BeFalse();
}
[Theory]
[InlineData(ECoreType.v2fly)]
[InlineData(ECoreType.hysteria)]
[InlineData(null)]
public void ShouldRunAsSudo_UnsupportedCoreType_ShouldNotElevate(ECoreType? coreType)
{
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeFalse();
}
}

View File

@@ -485,12 +485,17 @@ public class Utils
public static string? DomainStrategy4Sbox(string? strategy)
{
if (strategy is null)
{
return null;
}
return strategy switch
{
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
_ when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
_ when strategy.StartsWith("UseIP") => "prefer_ipv4",
_ when strategy.StartsWith("ForceIPv6") => "ipv6_only",
_ when strategy.StartsWith("ForceIP") => "ipv4_only",
_ => null
};
}

View File

@@ -2,8 +2,6 @@ namespace ServiceLib;
public class Global
{
#region const
public const string AppName = "v2rayN";
public const string GithubUrl = "https://github.com";
public const string GithubApiUrl = "https://api.github.com/repos";
@@ -106,9 +104,7 @@ public class Global
public const string SingboxFakeDNSTag = "fake_dns";
public const int Hysteria2DefaultHopInt = 30;
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
public static readonly List<string> PolicyGroupDefaultFilterList =
@@ -557,7 +553,6 @@ public class Global
"http",
"tls",
"quic",
"fakedns",
];
public static readonly List<int> TunMtus =
@@ -737,6 +732,12 @@ public class Global
"reply",
];
public static readonly List<string> FakeIPRanges =
[
"198.18.0.0/15",
"11.0.0.0/8",
];
public static readonly List<string> RootCertProviders =
[
"system",
@@ -744,5 +745,29 @@ public class Global
MozillaRootProvider,
];
#endregion const
public static readonly IReadOnlyList<string> TunIPv4Address =
[
"172.18.0.1/30",
"172.31.0.1/30",
"172.20.0.1/30",
"172.16.0.1/30",
"192.168.100.1/30",
"10.10.14.1/30",
"10.1.0.1/30",
"10.0.0.1/30",
];
// Prefixes must leave room for a peer address (max /126); the sing-box system
// stack derives a gateway from the first prefix and rejects single-address prefixes.
public static readonly IReadOnlyList<string> TunIPv6Address =
[
"fc00::172:18:0:1/126",
"fc00::172:31:0:1/126",
"fc00::172:20:0:1/126",
"fc00::172:16:0:1/126",
"fc00::192:168:100:1/126",
"fc00::10:10:14:1/126",
"fc00::10:1:0:1/126",
"fc00::10:0:0:1/126",
];
}

View File

@@ -30,8 +30,8 @@ global using ServiceLib.Handler.Fmt;
global using ServiceLib.Handler.SysProxy;
global using ServiceLib.Helper;
global using ServiceLib.Manager;
global using ServiceLib.Models.CoreConfigs;
global using ServiceLib.Models.Configs;
global using ServiceLib.Models.CoreConfigs;
global using ServiceLib.Models.Dto;
global using ServiceLib.Models.Entities;
global using ServiceLib.Resx;

View File

@@ -92,7 +92,7 @@ public static class ConfigHandler
EnableTun = false,
Mtu = 9000,
IcmpRouting = Global.TunIcmpRoutingPolicies.First(),
EnableLegacyProtect = false,
EnableLegacyProtect = true,
};
config.GuiItem ??= new();
if (!Global.RootCertProviders.Contains(config.GuiItem.RootCertProvider))
@@ -115,10 +115,12 @@ public static class ConfigHandler
config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
config.SimpleDNSItem.GlobalFakeIp ??= true;
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
config.SimpleDNSItem.ServeStale ??= false;
config.SimpleDNSItem.ParallelQuery ??= false;
config.SimpleDNSItem.EnableHappyEyeballs ??= false;
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
@@ -168,10 +170,24 @@ public static class ConfigHandler
config.Fragment4RayItem ??= new()
{
Packets = "tlshello",
Length = "50-100",
Interval = "10-20",
MaxSplit = "0"
};
config.Fragment4RayItem.MaxSplit ??= "0";
config.HappyEyeballs4RayItem ??= new()
{
TryDelayMs = 250,
PrioritizeIPv6 = false,
Interleave = 1,
MaxConcurrentTry = 4,
};
if ((config.Fragment4RayItem.Lengths ?? []).Count == 0)
{
config.Fragment4RayItem.Lengths = [config.Fragment4RayItem.Length ?? "50-100"];
}
if ((config.Fragment4RayItem.Delays ?? []).Count == 0)
{
config.Fragment4RayItem.Delays = [config.Fragment4RayItem.Interval ?? "10-20"];
}
config.GlobalHotkeys ??= [];
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())

View File

@@ -16,7 +16,6 @@ public static class SysProxyHandler
try
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
if (port <= 0)
{
return false;
@@ -24,17 +23,18 @@ public static class SysProxyHandler
switch (type)
{
case ESysProxyType.ForcedChange when Utils.IsWindows():
{
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
break;
}
var (strProxy, strExceptions) = GetWindowsProxyString(config, port);
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
break;
case ESysProxyType.ForcedChange when Utils.IsLinux():
var exceptions = SanitizeExceptions(config);
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
break;
case ESysProxyType.ForcedChange when Utils.IsMacOS():
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
var exceptions2 = SanitizeExceptions(config);
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions2);
break;
case ESysProxyType.ForcedClear when Utils.IsWindows():
@@ -66,15 +66,31 @@ public static class SysProxyHandler
return true;
}
private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions)
private static string SanitizeExceptions(Config config)
{
strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
var exceptions = config.SystemProxyItem.SystemProxyExceptions;
if (exceptions.IsNullOrEmpty())
{
return string.Empty;
}
var items = exceptions
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
.Select(item => item.Replace(" ", string.Empty))
.Where(item => item.Length > 0);
return string.Join(',', items);
}
private static (string strProxy, string strExceptions) GetWindowsProxyString(Config config, int port)
{
var strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
if (config.SystemProxyItem.NotProxyLocalAddress)
{
strExceptions = $"<local>;{strExceptions}";
}
strProxy = string.Empty;
var strProxy = string.Empty;
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
{
strProxy = $"{Global.Loopback}:{port}";
@@ -86,6 +102,8 @@ public static class SysProxyHandler
.Replace("{http_port}", port.ToString())
.Replace("{socks_port}", port.ToString());
}
return (strProxy, strExceptions);
}
[SupportedOSPlatform("windows")]

View File

@@ -183,7 +183,7 @@ public class CoreManager
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true, context.IsTunEnabled);
if (proc is null)
{
return;
@@ -201,7 +201,7 @@ public class CoreManager
if (result.Success)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true, preContext.IsTunEnabled);
if (proc is null)
{
return;
@@ -289,7 +289,20 @@ public class CoreManager
#region Process
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
/// <summary>
/// Decides whether a core launch must be elevated on non-Windows platforms.
/// The TUN state comes from the immutable <see cref="CoreConfigContext" /> snapshot that
/// generated the config, never from the live mutable config: the generated config and the
/// launch mode must always agree, even if TUN is toggled while a reload is in flight.
/// </summary>
public static bool ShouldRunAsSudo(bool isTunLaunch, ECoreType? coreType, bool isNonWindows)
{
return isTunLaunch
&& coreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray
&& isNonWindows;
}
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo, bool isTunLaunch = false)
{
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty())
@@ -301,9 +314,7 @@ public class CoreManager
try
{
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray)
&& Utils.IsNonWindows())
&& ShouldRunAsSudo(isTunLaunch, coreInfo.CoreType, Utils.IsNonWindows()))
{
_linuxSudo = true;
await CoreAdminManager.Instance.Init(_config, _updateFunc);

View File

@@ -34,6 +34,7 @@ public class Config
public List<KeyEventItem> GlobalHotkeys { get; set; }
public List<CoreTypeItem> CoreTypeItem { get; set; }
public SimpleDNSItem SimpleDNSItem { get; set; }
public HappyEyeballs4RayItem HappyEyeballs4RayItem { get; set; }
#endregion other entities
}

View File

@@ -147,8 +147,10 @@ public class TunModeItem
public int Mtu { get; set; }
public bool EnableIPv6Address { get; set; }
public string IcmpRouting { get; set; }
public bool EnableLegacyProtect { get; set; }
public bool EnableLegacyProtect { get; set; } = true;
public List<string>? RouteExcludeAddress { get; set; }
public string IPv4Address { get; set; }
public string IPv6Address { get; set; }
}
[Serializable]
@@ -249,9 +251,15 @@ public class CheckUpdateItem
public class Fragment4RayItem
{
public string? Packets { get; set; }
public string? Length { get; set; }
public string? Interval { get; set; }
public List<string>? Lengths { get; set; }
public List<string>? Delays { get; set; }
public string? MaxSplit { get; set; }
// For migration from old version, remove those properties in the future
public string? Length { get; set; }
public string? Interval { get; set; }
// migration end
}
[Serializable]
@@ -269,14 +277,26 @@ public class SimpleDNSItem
public bool? AddCommonHosts { get; set; }
public bool? FakeIP { get; set; }
public bool? GlobalFakeIp { get; set; }
public string? FakeIPRange { get; set; }
public bool? BlockBindingQuery { get; set; }
public string? DirectDNS { get; set; }
public string? RemoteDNS { get; set; }
public string? BootstrapDNS { get; set; }
public string? Strategy4Freedom { get; set; }
public string? Strategy4Proxy { get; set; }
public string? Strategy4ProxyDial { get; set; }
public bool? ServeStale { get; set; }
public bool? ParallelQuery { get; set; }
public string? Hosts { get; set; }
public string? DirectExpectedIPs { get; set; }
public bool? EnableHappyEyeballs { get; set; }
}
[Serializable]
public class HappyEyeballs4RayItem
{
public int? TryDelayMs { get; set; }
public bool? PrioritizeIPv6 { get; set; }
public int? Interleave { get; set; }
public int? MaxConcurrentTry { get; set; }
}

View File

@@ -4,6 +4,7 @@ public class V2rayConfig
{
public Log4Ray log { get; set; }
public object dns { get; set; }
public FakeDns4Ray? fakedns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; }
public Routing4Ray routing { get; set; }
@@ -43,6 +44,12 @@ public class Log4Ray
public string? loglevel { get; set; }
}
public class FakeDns4Ray
{
public string? ipPool { get; set; }
public long? poolSize { get; set; }
}
public class Inbounds4Ray
{
public string tag { get; set; }
@@ -136,8 +143,6 @@ public class Outboundsettings4Ray
public Response4Ray? response { get; set; }
public string? domainStrategy { get; set; }
public int? userLevel { get; set; }
public string? secretKey { get; set; }
@@ -516,6 +521,8 @@ public class MaskSettings4Ray
public string? length { get; set; }
public string? delay { get; set; }
public List<string>? lengths { get; set; }
public List<string>? delays { get; set; }
public int? maxSplit { get; set; }
// noise
@@ -547,15 +554,20 @@ public class AccountsItem4Ray
public class Sockopt4Ray
{
public string? domainStrategy { get; set; }
public string? dialerProxy { get; set; }
[JsonPropertyName("interface")]
public string? Interface { get; set; }
public HappyEyeballs4Ray? happyEyeballs { get; set; }
}
public class FragmentItem4Ray
public class HappyEyeballs4Ray
{
public string? packets { get; set; }
public string? length { get; set; }
public string? interval { get; set; }
public int? tryDelayMs { get; set; }
public bool? prioritizeIPv6 { get; set; }
public int? interleave { get; set; }
public int? maxConcurrentTry { get; set; }
}

View File

@@ -3060,6 +3060,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable Happy Eyeballs 的本地化字符串。
/// </summary>
public static string TbEnableHappyEyeballs {
get {
return ResourceManager.GetString("TbEnableHappyEyeballs", resourceCulture);
}
}
/// <summary>
/// 查找类似 Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path. 的本地化字符串。
/// </summary>
public static string TbEnableHappyEyeballsTip {
get {
return ResourceManager.GetString("TbEnableHappyEyeballsTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 DNS via Bridge 的本地化字符串。
/// </summary>
@@ -3088,7 +3106,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
/// 查找类似 Applies globally by default, and built-in FakeIP filtering is only built into sing-box. 的本地化字符串。
/// </summary>
public static string TbFakeIPTips {
get {
@@ -3330,6 +3348,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Ipv4 Address 的本地化字符串。
/// </summary>
public static string TbIpv4Address {
get {
return ResourceManager.GetString("TbIpv4Address", resourceCulture);
}
}
/// <summary>
/// 查找类似 Ipv6 Address 的本地化字符串。
/// </summary>
public static string TbIpv6Address {
get {
return ResourceManager.GetString("TbIpv6Address", resourceCulture);
}
}
/// <summary>
/// 查找类似 Most Stable 的本地化字符串。
/// </summary>
@@ -3528,6 +3564,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Proxy Dial Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbProxyDialResolveStrategy {
get {
return ResourceManager.GetString("TbProxyDialResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 Not recommended; may cause routing loops. 的本地化字符串。
/// </summary>
public static string TbProxyDialResolveStrategyTip {
get {
return ResourceManager.GetString("TbProxyDialResolveStrategyTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Public Key 的本地化字符串。
/// </summary>

View File

@@ -1495,7 +1495,7 @@
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>

View File

@@ -1492,7 +1492,7 @@
<value>Choisir une config.</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Actif globalement par défaut, avec filtre FakeIP intégré ; ne fonctionne que dans sing-box</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Veuillez ajouter au moins une configuration</value>

View File

@@ -1495,7 +1495,7 @@
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>

View File

@@ -1495,7 +1495,7 @@
<value>Pilih profil</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Berlaku secara global secara default, dengan filter FakeIP bawaan (hanya sing-box).</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>

View File

@@ -1501,7 +1501,7 @@
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
@@ -1830,4 +1830,22 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRootCertificateProviderTip" xml:space="preserve">
<value>Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation.</value>
</data>
</root>
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
<value>Proxy Dial Resolution Strategy</value>
</data>
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
<value>Not recommended; may cause routing loops.</value>
</data>
<data name="TbEnableHappyEyeballs" xml:space="preserve">
<value>Enable Happy Eyeballs</value>
</data>
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
<value>Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path.</value>
</data>
<data name="TbIpv6Address" xml:space="preserve">
<value>Ipv6 Address</value>
</data>
<data name="TbIpv4Address" xml:space="preserve">
<value>Ipv4 Address</value>
</data>
</root>

View File

@@ -1501,7 +1501,7 @@
<value>Выбрать профиль</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Добавьте хотя бы одну конфигурацию</value>

View File

@@ -1498,7 +1498,7 @@
<value>选择配置</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
<value>默认全局生效,仅在 sing-box 中内置 FakeIP 过滤。</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>请至少添加一个配置</value>
@@ -1827,4 +1827,22 @@
<data name="TbRootCertificateProviderTip" xml:space="preserve">
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
</data>
</root>
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
<value>连接代理解析策略</value>
</data>
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
<value>不建议开启,特殊情况可能回环。</value>
</data>
<data name="TbEnableHappyEyeballs" xml:space="preserve">
<value>启用 Happy Eyeballs</value>
</data>
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
<value>需配合 UseIP 策略使用。启用后将同时尝试 IPv4 和 IPv6 连接,并自动选择更快可用的路径。</value>
</data>
<data name="TbIpv6Address" xml:space="preserve">
<value>Ipv6 地址</value>
</data>
<data name="TbIpv4Address" xml:space="preserve">
<value>Ipv4 地址</value>
</data>
</root>

View File

@@ -1498,7 +1498,7 @@
<value>選擇節點</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默認全生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
<value>默認全生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>請至少添加一個節點</value>
@@ -1827,4 +1827,22 @@
<data name="TbRootCertificateProviderTip" xml:space="preserve">
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
</data>
</root>
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
<value>連線代理解析策略</value>
</data>
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
<value>不建議啟用,特殊情況可能造成連線循環</value>
</data>
<data name="TbEnableHappyEyeballs" xml:space="preserve">
<value>啟用 Happy Eyeballs</value>
</data>
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
<value>需搭配 UseIP 策略,啟用後會同時嘗試 IPv4 與 IPv6 連線,並自動選擇速度較快的路徑</value>
</data>
<data name="TbIpv6Address" xml:space="preserve">
<value>Ipv6 位址</value>
</data>
<data name="TbIpv4Address" xml:space="preserve">
<value>Ipv4 位址</value>
</data>
</root>

View File

@@ -1,27 +1,28 @@
{
"tag": "tun",
"protocol": "tun",
"settings": {
"name": "xray_tun",
"MTU": 9000,
"gateway": [
"172.18.0.1/30",
"fdfe:dcba:9876::1/126"
],
"dns": [
"172.18.0.1"
],
"autoSystemRoutingTable": [
"0.0.0.0/0",
"::/0"
],
"autoOutboundsInterface": "auto"
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}
"tag": "tun",
"protocol": "tun",
"settings": {
"name": "xray_tun",
"MTU": 9000,
"gateway": [
"172.18.0.1/30",
"fdfe:dcba:9876::1/126"
],
"dns": [
"1.1.1.1",
"8.8.8.8"
],
"autoSystemRoutingTable": [
"0.0.0.0/0",
"::/0"
],
"autoOutboundsInterface": "auto"
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -137,12 +137,12 @@ public partial class CoreConfigSingboxService
// fake ip
if (simpleDnsItem.FakeIP == true)
{
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
var fakeip = new Server4Sbox
{
tag = Global.SingboxFakeDNSTag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
inet4_range = fakeipRange,
};
_coreConfig.dns.servers.Add(fakeip);
}
@@ -171,7 +171,7 @@ public partial class CoreConfigSingboxService
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
domain = context.ProtectDomainList.ToList(),
});
}
@@ -266,7 +266,7 @@ public partial class CoreConfigSingboxService
});
}
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp != false)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;

View File

@@ -67,9 +67,13 @@ public partial class CoreConfigSingboxService
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack;
if (_config.TunModeItem.EnableIPv6Address == false)
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
tunInbound.address = [address];
if (_config.TunModeItem.EnableIPv6Address == true)
{
tunInbound.address = ["172.18.0.1/30"];
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
tunInbound.address.Add(address6);
}
tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress;

View File

@@ -417,11 +417,15 @@ public partial class CoreConfigSingboxService
var tls = new Tls4Sbox()
{
enabled = true,
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
server_name = serverName,
insecure = _node.GetAllowInsecure(),
alpn = _node.GetAlpn(),
};
if (_config.CoreBasicItem.EnableFragment == true)
{
tls.fragment = true;
tls.record_fragment = true;
}
if (_node.Fingerprint.IsNotEmpty())
{
tls.utls = new Utls4Sbox()

View File

@@ -10,18 +10,27 @@ public partial class CoreConfigSingboxService
var simpleDnsItem = context.SimpleDnsItem;
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
var dialDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial);
var rawDNSItem = context.RawDnsItem;
if (rawDNSItem is { Enabled: true })
{
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
dialDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
}
else if (!simpleDnsItem.Strategy4Freedom.IsNullOrEmpty())
{
var directOutbound = _coreConfig.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag);
directOutbound?.domain_resolver = new()
{
server = defaultDomainResolverTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
};
}
_coreConfig.route.default_domain_resolver = new()
{
server = defaultDomainResolverTag,
strategy = directDnsStrategy
strategy = dialDnsStrategy
};
if (context.IsTunEnabled)
@@ -35,18 +44,21 @@ public partial class CoreConfigSingboxService
}
var lstDirectExe = BuildRoutingDirectExe();
_coreConfig.route.rules.Add(new()
if (lstDirectExe.Count > 0)
{
port = [53],
action = "hijack-dns",
process_path = lstDirectExe
});
_coreConfig.route.rules.Add(new()
{
port = [53],
action = "hijack-dns",
process_path = lstDirectExe,
});
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
process_path = lstDirectExe
});
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
process_path = lstDirectExe,
});
}
// ICMP Routing
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";

View File

@@ -28,7 +28,7 @@ public partial class CoreConfigV2rayService
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
inboundTag = [Global.DnsTag],
outboundTag = Global.ProxyTag,
});
return;
@@ -43,11 +43,7 @@ public partial class CoreConfigV2rayService
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new()
{
domainStrategy = strategy4Freedom,
userLevel = 0
};
FillSockoptDomainStrategy(outbound, strategy4Freedom);
}
}
@@ -64,6 +60,22 @@ public partial class CoreConfigV2rayService
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
}
var strategy4DialProxy = simpleDnsItem?.Strategy4ProxyDial ?? Global.AsIs;
//Outbound DialProxy domainStrategy
if (strategy4DialProxy.IsNotEmpty() && strategy4DialProxy != Global.AsIs)
{
var xraySupportConfigTypeNames = Global.XraySupportConfigType
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
.ToHashSet();
_coreConfig.outbounds
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
.ToList()
.ForEach(outbound =>
{
FillSockoptDomainStrategy(outbound, strategy4DialProxy);
});
}
FillDnsServers(dnsItem);
FillDnsHosts(dnsItem);
@@ -108,6 +120,33 @@ public partial class CoreConfigV2rayService
}
}
private void GenFakeDns()
{
var fakeipRange = _config.SimpleDNSItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : _config.SimpleDNSItem.FakeIPRange;
var poolSize = 65535L;
try
{
var fakeipNetwork = IPNetwork2.Parse(fakeipRange);
var totalIPs = fakeipNetwork.Total;
// see https://github.com/XTLS/Xray-core/blob/6e3322d219140a025285ded1114fe17a5edb74d8/app/dns/fakedns/fake.go#L88
// if math.Log2(float64(lruSize)) >= float64(rooms) { return errors.New("LRU size is bigger than subnet size").AtError() }
totalIPs -= 1;
if (totalIPs > 0)
{
poolSize = (totalIPs >= long.MaxValue) ? long.MaxValue : (long)totalIPs;
}
}
catch
{
// Ignore
}
_coreConfig.fakedns = new()
{
ipPool = fakeipRange,
poolSize = poolSize,
};
}
private void FillDnsServers(Dns4Ray dnsItem)
{
var simpleDNSItem = context.SimpleDnsItem;
@@ -234,6 +273,23 @@ public partial class CoreConfigV2rayService
var directDnsTagIndex = 1;
if (simpleDNSItem.FakeIP == true)
{
var fakeIPMatchDomain = new HashSet<string>(proxyDomainList);
fakeIPMatchDomain.UnionWith(proxyGeositeList);
if (simpleDNSItem.GlobalFakeIp != false)
{
fakeIPMatchDomain.UnionWith(directDomainList);
fakeIPMatchDomain.UnionWith(directGeositeList);
fakeIPMatchDomain.UnionWith(expectedDomainList);
}
if (fakeIPMatchDomain.Count > 0)
{
GenFakeDns();
AddDnsServers(["fakedns"], fakeIPMatchDomain.ToList());
}
}
AddDnsServers(remoteDNSAddress, proxyDomainList);
AddDnsServers(directDNSAddress, directDomainList, true);
AddDnsServers(remoteDNSAddress, proxyGeositeList);
@@ -384,11 +440,9 @@ public partial class CoreConfigV2rayService
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new()
{
domainStrategy = domainStrategy4Freedom,
userLevel = 0,
};
outbound.streamSettings ??= new();
outbound.streamSettings.sockopt ??= new();
outbound.streamSettings.sockopt.domainStrategy = domainStrategy4Freedom;
}
}
@@ -471,4 +525,28 @@ public partial class CoreConfigV2rayService
};
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
private void FillSockoptDomainStrategy(Outbounds4Ray outbound, string? domainStrategy, bool skipHappyEyeballs = false)
{
if (domainStrategy.IsNullOrEmpty())
{
return;
}
outbound.streamSettings ??= new();
outbound.streamSettings.sockopt ??= new();
var sockopt = outbound.streamSettings.sockopt;
sockopt.domainStrategy = domainStrategy;
if (skipHappyEyeballs || _config.SimpleDNSItem.EnableHappyEyeballs != true)
{
return;
}
var happyEyeballsItem = _config.HappyEyeballs4RayItem;
sockopt.happyEyeballs ??= new();
var happyEyeballs = sockopt.happyEyeballs;
happyEyeballs.tryDelayMs = happyEyeballsItem.TryDelayMs;
happyEyeballs.prioritizeIPv6 = happyEyeballsItem.PrioritizeIPv6;
happyEyeballs.interleave = happyEyeballsItem.Interleave;
happyEyeballs.maxConcurrentTry = happyEyeballsItem.MaxConcurrentTry;
}
}

View File

@@ -63,11 +63,16 @@ public partial class CoreConfigV2rayService
new Inbounds4Ray();
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
if (!_config.TunModeItem.EnableIPv6Address)
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
tunInbound.settings.gateway = [address];
if (_config.TunModeItem.EnableIPv6Address == true)
{
tunInbound.settings.gateway = ["172.18.0.1/30"];
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
tunInbound.settings.gateway.Add(address6);
}
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
if (!bindInterface.IsNullOrEmpty())
{
@@ -152,6 +157,16 @@ public partial class CoreConfigV2rayService
inbound.sniffing.destOverride = inItem.DestOverride;
inbound.sniffing.routeOnly = inItem.RouteOnly;
if (_config.SimpleDNSItem.FakeIP == true)
{
// Ensure destOverride contains "fakedns" if FakeIP is enabled
inbound.sniffing.destOverride ??= [];
if (!inbound.sniffing.destOverride.Contains("fakedns"))
{
inbound.sniffing.destOverride.Add("fakedns");
}
}
return inbound;
}
}

View File

@@ -865,21 +865,10 @@ public partial class CoreConfigV2rayService
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
.ToList();
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var fragmentMask = BuildFragmentsMasks();
foreach (var outbound in actOutboundWithTlsList)
{
//var packets = configPackets;
//if (outbound.streamSettings.security == Global.StreamSecurityReality
// && packets == "tlshello")
//{
// packets = "1-3";
//}
//else if (outbound.streamSettings.security == Global.StreamSecurity
// && packets != "tlshello")
//{
// packets = "tlshello";
//}
var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject();
// tcp fragment
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
@@ -888,13 +877,6 @@ public partial class CoreConfigV2rayService
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
finalMaskJsonObj["tcp"] = tcpFinalmaskList;
}
// udp noise
var udpFinalmaskList = finalMaskJsonObj["udp"] as JsonArray ?? [];
if (udpFinalmaskList.Count == 0)
{
udpFinalmaskList.Add(JsonUtils.SerializeToNode(noiseMask));
finalMaskJsonObj["udp"] = udpFinalmaskList;
}
// write back
outbound.streamSettings.finalmask = finalMaskJsonObj;
}
@@ -902,7 +884,7 @@ public partial class CoreConfigV2rayService
private void ApplyFinalFragment()
{
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var fragmentMask = BuildFragmentsMasks();
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
var fragmentFreedom = new Outbounds4Ray()
@@ -914,9 +896,8 @@ public partial class CoreConfigV2rayService
finalmask = new Finalmask4Ray
{
tcp = [fragmentMask],
udp = [noiseMask],
}
}
},
},
};
foreach (var outbound in actOutboundList)
@@ -934,13 +915,22 @@ public partial class CoreConfigV2rayService
}
}
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
private Mask4Ray BuildFragmentsMasks()
{
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
var configLength = _config.Fragment4RayItem?.Length.NullIfEmpty() ?? "50-100";
var configDelay = _config.Fragment4RayItem?.Interval.NullIfEmpty() ?? "10-20";
var configLengths = _config.Fragment4RayItem?.Lengths ?? [];
var configDelays = _config.Fragment4RayItem?.Delays ?? [];
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
if (configLengths.Count == 0)
{
configLengths = ["50-100"];
}
if (configDelays.Count == 0)
{
configDelays = ["10-20"];
}
var maxSplit = 0;
var parts = configMaxSplit.Split('-');
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
@@ -954,21 +944,15 @@ public partial class CoreConfigV2rayService
settings = new MaskSettings4Ray
{
packets = configPackets,
length = configLength,
delay = configDelay,
lengths = configLengths,
delays = configDelays,
maxSplit = maxSplit,
}
};
var noiseMask = new Mask4Ray
{
type = "noise",
settings = new MaskSettings4Ray
{
length = "10-20",
delay = "10-16",
}
// For legacy xray compatibility, remove this in the future
length = configLengths.FirstOrDefault(),
delay = configDelays.FirstOrDefault(),
},
};
return (fragmentMask, noiseMask);
return fragmentMask;
}
}

View File

@@ -14,17 +14,20 @@ public partial class CoreConfigV2rayService
_coreConfig.routing.rules.AddRange(tunRules);
}
var lstDirectExe = BuildRoutingDirectExe();
_coreConfig.routing.rules.Add(new()
if (lstDirectExe.Count > 0)
{
port = "53",
process = lstDirectExe,
outboundTag = Global.DnsOutboundTag,
});
_coreConfig.routing.rules.Add(new()
{
process = lstDirectExe,
outboundTag = Global.DirectTag,
});
_coreConfig.routing.rules.Add(new()
{
port = "53",
process = lstDirectExe,
outboundTag = Global.DnsOutboundTag,
});
_coreConfig.routing.rules.Add(new()
{
process = lstDirectExe,
outboundTag = Global.DirectTag,
});
}
_coreConfig.routing.rules.Add(new()
{
inboundTag = ["tun"],

View File

@@ -3,6 +3,7 @@ namespace ServiceLib.ViewModels;
public class AddServer2ViewModel : MyReactiveObject, ICloseable
{
public event EventHandler? RequestClose;
public Interaction<Unit, string?> BrowseConfigFileInteraction { get; } = new();
[Reactive]

View File

@@ -4,19 +4,22 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
{
public event EventHandler? RequestClose;
[Reactive] public bool? UseSystemHosts { get; set; }
[Reactive] public bool? AddCommonHosts { get; set; }
[Reactive] public bool? FakeIP { get; set; }
[Reactive] public bool? BlockBindingQuery { get; set; }
[Reactive] public string? DirectDNS { get; set; }
[Reactive] public string? RemoteDNS { get; set; }
[Reactive] public string? BootstrapDNS { get; set; }
[Reactive] public string? Strategy4Freedom { get; set; }
[Reactive] public string? Strategy4Proxy { get; set; }
[Reactive] public string? Hosts { get; set; }
[Reactive] public string? DirectExpectedIPs { get; set; }
[Reactive] public bool? ParallelQuery { get; set; }
[Reactive] public bool? ServeStale { get; set; }
[Reactive] public bool UseSystemHosts { get; set; }
[Reactive] public bool AddCommonHosts { get; set; }
[Reactive] public bool FakeIP { get; set; }
[Reactive] public string FakeIPRange { get; set; }
[Reactive] public bool BlockBindingQuery { get; set; }
[Reactive] public string DirectDNS { get; set; }
[Reactive] public string RemoteDNS { get; set; }
[Reactive] public string BootstrapDNS { get; set; }
[Reactive] public string Strategy4Freedom { get; set; }
[Reactive] public string Strategy4Proxy { get; set; }
[Reactive] public string Strategy4ProxyDial { get; set; }
[Reactive] public string Hosts { get; set; }
[Reactive] public string DirectExpectedIPs { get; set; }
[Reactive] public bool ParallelQuery { get; set; }
[Reactive] public bool ServeStale { get; set; }
[Reactive] public bool EnableHappyEyeballs { get; set; }
[Reactive] public bool UseSystemHostsCompatible { get; set; }
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
@@ -67,20 +70,22 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
{
_config = AppManager.Instance.Config;
var item = _config.SimpleDNSItem;
UseSystemHosts = item.UseSystemHosts;
AddCommonHosts = item.AddCommonHosts;
FakeIP = item.FakeIP;
BlockBindingQuery = item.BlockBindingQuery;
DirectDNS = item.DirectDNS;
RemoteDNS = item.RemoteDNS;
BootstrapDNS = item.BootstrapDNS;
Strategy4Freedom = item.Strategy4Freedom;
Strategy4Proxy = item.Strategy4Proxy;
Hosts = item.Hosts;
DirectExpectedIPs = item.DirectExpectedIPs;
ParallelQuery = item.ParallelQuery;
ServeStale = item.ServeStale;
UseSystemHosts = item.UseSystemHosts ?? false;
AddCommonHosts = item.AddCommonHosts ?? false;
FakeIP = item.FakeIP ?? false;
FakeIPRange = item.FakeIPRange ?? string.Empty;
BlockBindingQuery = item.BlockBindingQuery ?? false;
DirectDNS = item.DirectDNS ?? string.Empty;
RemoteDNS = item.RemoteDNS ?? string.Empty;
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
Strategy4Freedom = item.Strategy4Freedom ?? string.Empty;
Strategy4Proxy = item.Strategy4Proxy ?? string.Empty;
Strategy4ProxyDial = item.Strategy4ProxyDial ?? string.Empty;
Hosts = item.Hosts ?? string.Empty;
DirectExpectedIPs = item.DirectExpectedIPs ?? string.Empty;
ParallelQuery = item.ParallelQuery ?? false;
ServeStale = item.ServeStale ?? false;
EnableHappyEyeballs = item.EnableHappyEyeballs ?? false;
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
RayCustomDNSEnableCompatible = item1.Enabled;
UseSystemHostsCompatible = item1.UseSystemHosts;
@@ -102,17 +107,19 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
_config.SimpleDNSItem.FakeIP = FakeIP;
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
_config.SimpleDNSItem.DirectDNS = DirectDNS;
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
_config.SimpleDNSItem.Strategy4ProxyDial = Strategy4ProxyDial;
_config.SimpleDNSItem.Hosts = Hosts;
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
_config.SimpleDNSItem.ServeStale = ServeStale;
_config.SimpleDNSItem.EnableHappyEyeballs = EnableHappyEyeballs;
if (NormalDNSCompatible.IsNotEmpty())
{
var obj = JsonUtils.ParseJson(NormalDNSCompatible);

View File

@@ -263,6 +263,11 @@ public class MainWindowViewModel : MyReactiveObject
#endregion AppEvents
ProfilesViewModel.RefreshServersRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await RefreshServers());
var vmReloadRequestedList = new List<IObservable<Unit>>
{
ProfilesViewModel.ReloadRequested.AsObservable(),
@@ -394,8 +399,8 @@ public class MainWindowViewModel : MyReactiveObject
private async Task RefreshServers()
{
await ProfilesViewModel.RefreshServers();
await StatusBarViewModel.RefreshServers();
await ProfilesViewModel.RefreshServersBiz();
await StatusBarViewModel.RefreshServersBiz();
// await Task.Delay(200);
}

View File

@@ -29,24 +29,12 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
[Reactive] public bool EnableFragment { get; set; }
[Reactive] public bool EnableFinalFragment { get; set; }
[Reactive] public string FragmentPackets { get; set; }
[Reactive] public string FragmentLength { get; set; }
[Reactive] public string FragmentInterval { get; set; }
[Reactive] public string FragmentLengths { get; set; }
[Reactive] public string FragmentDelays { get; set; }
[Reactive] public string FragmentMaxSplit { get; set; }
#endregion Core
#region Core KCP
//[Reactive] public int Kcpmtu { get; set; }
//[Reactive] public int Kcptti { get; set; }
//[Reactive] public int KcpuplinkCapacity { get; set; }
//[Reactive] public int KcpdownlinkCapacity { get; set; }
//[Reactive] public int KcpreadBufferSize { get; set; }
//[Reactive] public int KcpwriteBufferSize { get; set; }
//[Reactive] public bool Kcpcongestion { get; set; }
#endregion Core KCP
#region UI
[Reactive] public bool AutoRun { get; set; }
@@ -107,6 +95,8 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
[Reactive] public string TunIcmpRouting { get; set; }
[Reactive] public bool TunEnableLegacyProtect { get; set; }
[Reactive] public string TunRouteExcludeAddress { get; set; }
[Reactive] public string TunIPv4Address { get; set; }
[Reactive] public string TunIPv6Address { get; set; }
#endregion Tun mode
@@ -150,6 +140,7 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
UdpEnabled = inbound.UdpEnabled;
SniffingEnabled = inbound.SniffingEnabled;
DestOverride = inbound.DestOverride ?? [];
RouteOnly = inbound.RouteOnly;
AllowLANConn = inbound.AllowLANConn;
NewPort4LAN = inbound.NewPort4LAN;
@@ -168,24 +159,12 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
EnableFragment = _config.CoreBasicItem.EnableFragment;
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
FragmentPackets = _config.Fragment4RayItem?.Packets;
FragmentLength = _config.Fragment4RayItem?.Length;
FragmentInterval = _config.Fragment4RayItem?.Interval;
FragmentLengths = Utils.List2String(_config.Fragment4RayItem?.Lengths);
FragmentDelays = Utils.List2String(_config.Fragment4RayItem?.Delays);
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
#endregion Core
#region Core KCP
//Kcpmtu = _config.kcpItem.mtu;
//Kcptti = _config.kcpItem.tti;
//KcpuplinkCapacity = _config.kcpItem.uplinkCapacity;
//KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity;
//KcpreadBufferSize = _config.kcpItem.readBufferSize;
//KcpwriteBufferSize = _config.kcpItem.writeBufferSize;
//Kcpcongestion = _config.kcpItem.congestion;
#endregion Core KCP
#region UI
AutoRun = _config.GuiItem.AutoRun;
@@ -237,6 +216,8 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
TunIPv4Address = _config.TunModeItem.IPv4Address;
TunIPv6Address = _config.TunModeItem.IPv6Address;
#endregion Tun mode
@@ -309,34 +290,33 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return;
}
var fragmentLengths = Utils.String2List(FragmentLengths) ?? [];
var fragmentDelays = Utils.String2List(FragmentDelays) ?? [];
if (fragmentLengths.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|| fragmentDelays.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|| (FragmentMaxSplit.IsNotEmpty() && !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _)))
{
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
return;
}
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|| EnableHWA != _config.GuiItem.EnableHWA
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
//if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString())
// || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString())
// || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString())
// || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString())
// || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString())
// || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString()))
//{
// NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters);
// return;
//}
//Core
_config.Inbound.First().LocalPort = LocalPort;
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
_config.Inbound.First().UdpEnabled = UdpEnabled;
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
_config.Inbound.First().DestOverride = DestOverride?.ToList();
_config.Inbound.First().RouteOnly = RouteOnly;
_config.Inbound.First().AllowLANConn = AllowLANConn;
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
_config.Inbound.First().User = User;
_config.Inbound.First().Pass = Pass;
var inbound = _config.Inbound.First();
inbound.LocalPort = LocalPort;
inbound.SecondLocalPortEnabled = SecondLocalPortEnabled;
inbound.UdpEnabled = UdpEnabled;
inbound.SniffingEnabled = SniffingEnabled;
inbound.DestOverride = DestOverride?.ToList();
inbound.RouteOnly = RouteOnly;
inbound.AllowLANConn = AllowLANConn;
inbound.NewPort4LAN = NewPort4LAN;
inbound.User = User;
inbound.Pass = Pass;
if (_config.Inbound.Count > 1)
{
_config.Inbound.RemoveAt(1);
@@ -351,30 +331,12 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
if (EnableFragment)
{
if (!Utils.TryParseRange(FragmentLength, 0, int.MaxValue, out _, out _))
{
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
return;
}
if (!Utils.TryParseRange(FragmentInterval, 1, 100, out _, out _))
{
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
return;
}
if (FragmentMaxSplit.IsNotEmpty()
&& !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _))
{
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
return;
}
}
_config.CoreBasicItem.EnableFragment = EnableFragment;
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
_config.Fragment4RayItem ??= new();
_config.Fragment4RayItem.Packets = FragmentPackets;
_config.Fragment4RayItem.Length = FragmentLength;
_config.Fragment4RayItem.Interval = FragmentInterval;
_config.Fragment4RayItem.Lengths = fragmentLengths;
_config.Fragment4RayItem.Delays = fragmentDelays;
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
_config.GuiItem.AutoRun = AutoRun;
@@ -420,6 +382,8 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
_config.TunModeItem.IPv4Address = TunIPv4Address;
_config.TunModeItem.IPv6Address = TunIPv6Address;
//coreType
await SaveCoreType();

View File

@@ -3,6 +3,7 @@ namespace ServiceLib.ViewModels;
public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
{
public event EventHandler? RequestClose;
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
#region private prop

View File

@@ -11,6 +11,7 @@ public class ProfilesViewModel : MyReactiveObject
public Interaction<Unit, Unit> AdjustMainLvColWidthInteraction { get; } = new();
public EventChannel<Unit> ReloadRequested { get; } = new();
public EventChannel<Unit> RefreshServersRequested { get; } = new();
#region private prop
@@ -368,12 +369,14 @@ public class ProfilesViewModel : MyReactiveObject
public async Task RefreshServers()
{
await RefreshServersBiz();
RefreshServersRequested.Publish();
// await Task.Delay(200);
await Task.CompletedTask;
}
private async Task RefreshServersBiz()
public async Task RefreshServersBiz()
{
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];

View File

@@ -3,6 +3,7 @@ namespace ServiceLib.ViewModels;
public class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
{
public event EventHandler? RequestClose;
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();

View File

@@ -264,12 +264,7 @@ public class StatusBarViewModel : MyReactiveObject
await Task.Delay(1000);
}
public async Task RefreshServers()
{
await RefreshServersBiz();
}
private async Task RefreshServersBiz()
public async Task RefreshServersBiz()
{
await RefreshServersMenu();

View File

@@ -22,6 +22,14 @@
<StyleInclude Source="Assets/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<dialogHost:DialogHostStyles />
<Style Selector="dialogHost|DialogHost">
<Setter Property="OverlayBackground" Value="{DynamicResource SemiColorOverlayBackground}" />
<Setter Property="Background" Value="{DynamicResource SemiColorFill0}" />
<Setter Property="dialogHost:DialogHostStyle.BoxShadow" Value="{DynamicResource SemiShadowElevated}" />
<Setter Property="dialogHost:DialogHostStyle.CornerRadius" Value="6" />
</Style>
</Application.Styles>
<TrayIcon.Icons>

View File

@@ -26,6 +26,11 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
@@ -43,11 +48,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
// Context menu actions that require custom logic (Add, SelectAll)

View File

@@ -38,6 +38,11 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
var configTypeBindings = new SerialDisposable().DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
@@ -188,11 +193,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
}

View File

@@ -42,7 +42,7 @@
x:Name="gridBasicDNSSettings"
Margin="{StaticResource Margin8}"
ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
x:Name="txtBasicDNSSettingsInvalid"
@@ -167,28 +167,71 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleSwitch
x:Name="togParallelQuery"
Text="{x:Static resx:ResUI.TbProxyDialResolveStrategy}" />
<ComboBox
x:Name="cmbProxyDialDNSStrategy"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
VerticalAlignment="Center"
PlaceholderText="Default" />
<TextBlock
Grid.Row="7"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbProxyDialResolveStrategyTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbServeStale}" />
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleSwitch
x:Name="togServeStale"
x:Name="togParallelQuery"
Grid.Row="8"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbServeStale}" />
<ToggleSwitch
x:Name="togServeStale"
Grid.Row="9"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEnableHappyEyeballs}" />
<ToggleSwitch
x:Name="togEnableHappyEyeballs"
Grid.Row="10"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="10"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEnableHappyEyeballsTip}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
</TabItem>
@@ -243,13 +286,24 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIP}" />
<ToggleSwitch
x:Name="togFakeIP"
<Grid
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
ColumnDefinitions="Auto,*">
<ToggleSwitch
x:Name="togFakeIP"
Grid.Column="0"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ComboBox
x:Name="cmbFakeIPRange"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsEditable="True" />
</Grid>
<TextBlock
Grid.Row="3"
Grid.Column="2"
@@ -318,8 +372,8 @@
VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea"
TextWrapping="Wrap"
PlaceholderText="{x:Static resx:ResUI.TbDNSHostsConfig}" />
PlaceholderText="{x:Static resx:ResUI.TbDNSHostsConfig}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
</TabItem>

View File

@@ -16,9 +16,11 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
cmbDirectDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbRemoteDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbProxyDialDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
cmbFakeIPRange.ItemsSource = Global.FakeIPRanges;
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy;
@@ -31,16 +33,19 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
this.Bind(ViewModel, vm => vm.UseSystemHosts, v => v.togUseSystemHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4ProxyDial, v => v.cmbProxyDialDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableHappyEyeballs, v => v.togEnableHappyEyeballs.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -18,10 +18,7 @@
ShowInTaskbar="True"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<dialogHost:DialogHost
Background="Gray"
CloseOnClickAway="True"
DisableOpeningAnimation="True">
<dialogHost:DialogHost CloseOnClickAway="True" DisableOpeningAnimation="True">
<DockPanel>
<DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
<ContentControl x:Name="conTheme" DockPanel.Dock="Right" />

View File

@@ -347,7 +347,7 @@
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsFragmentLength}" />
<TextBox
x:Name="txtFragmentLength"
x:Name="txtFragmentLengths"
Grid.Row="1"
Grid.Column="1"
Width="120"
@@ -369,7 +369,7 @@
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsFragmentInterval}" />
<TextBox
x:Name="txtFragmentInterval"
x:Name="txtFragmentDelays"
Grid.Row="2"
Grid.Column="1"
Width="120"
@@ -961,7 +961,7 @@
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="2"
@@ -1087,6 +1087,34 @@
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRouteExcludeAddressTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbIpv4Address}" />
<ComboBox
x:Name="cmbIpv4Address"
Grid.Row="10"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbIpv6Address}" />
<ComboBox
x:Name="cmbIpv6Address"
Grid.Row="11"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</Grid>
</ScrollViewer>
</TabItem>

View File

@@ -33,6 +33,8 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbMtu.ItemsSource = Global.TunMtus;
cmbStack.ItemsSource = Global.TunStacks;
cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies;
cmbIpv4Address.ItemsSource = Global.TunIPv4Address;
cmbIpv6Address.ItemsSource = Global.TunIPv6Address;
cmbFragmentPackets.ItemsSource = Global.FragmentPacketsOptions;
cmbCoreType1.ItemsSource = Global.CoreTypes;
@@ -86,8 +88,8 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentPackets, v => v.cmbFragmentPackets.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentLength, v => v.txtFragmentLength.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentInterval, v => v.txtFragmentInterval.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentLengths, v => v.txtFragmentLengths.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentDelays, v => v.txtFragmentDelays.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentMaxSplit, v => v.txtFragmentMaxSplit.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
@@ -129,6 +131,8 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIPv4Address, v => v.cmbIpv4Address.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIPv6Address, v => v.cmbIpv6Address.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables);

View File

@@ -21,6 +21,11 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
@@ -35,11 +40,6 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
this.BindCommand(ViewModel, vm => vm.SelectProfileCmd, v => v.btnSelectProfile).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
}

View File

@@ -17,7 +17,6 @@
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<dialogHost:DialogHost
Background="Gray"
CloseOnClickAway="True"
DisableOpeningAnimation="True"
Identifier="dialogHostSub">

View File

@@ -25,6 +25,11 @@ public partial class AddGroupServerWindow
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables);
@@ -45,11 +50,6 @@ public partial class AddGroupServerWindow
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
}

View File

@@ -36,6 +36,11 @@ public partial class AddServerWindow
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
var configTypeBindings = new SerialDisposable().DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
@@ -186,11 +191,6 @@ public partial class AddServerWindow
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);

View File

@@ -52,6 +52,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -195,13 +197,23 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleButton
x:Name="togParallelQuery"
Text="{x:Static resx:ResUI.TbProxyDialResolveStrategy}" />
<ComboBox
x:Name="cmbProxyDialDNSStrategy"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
materialDesign:HintAssist.Hint="Default"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="7"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbProxyDialResolveStrategyTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="8"
@@ -209,13 +221,49 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbServeStale}" />
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleButton
x:Name="togServeStale"
x:Name="togParallelQuery"
Grid.Row="8"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbServeStale}" />
<ToggleButton
x:Name="togServeStale"
Grid.Row="9"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEnableHappyEyeballs}" />
<ToggleButton
x:Name="togEnableHappyEyeballs"
Grid.Row="10"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="10"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEnableHappyEyeballsTip}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
</TabItem>
@@ -283,12 +331,25 @@
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFakeIP}" />
<ToggleButton
x:Name="togFakeIP"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<Grid Grid.Row="3" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToggleButton
x:Name="togFakeIP"
Grid.Column="0"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<ComboBox
x:Name="cmbFakeIPRange"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Stretch"
IsEditable="True"
Style="{StaticResource DefComboBox}" />
</Grid>
<TextBlock
Grid.Row="3"
Grid.Column="2"

View File

@@ -12,9 +12,11 @@ public partial class DNSSettingWindow
cmbDirectDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbRemoteDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbProxyDialDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
cmbFakeIPRange.ItemsSource = Global.FakeIPRanges;
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy;
@@ -27,16 +29,19 @@ public partial class DNSSettingWindow
this.Bind(ViewModel, vm => vm.UseSystemHosts, v => v.togUseSystemHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4ProxyDial, v => v.cmbProxyDialDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableHappyEyeballs, v => v.togEnableHappyEyeballs.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -419,7 +419,7 @@
Text="{x:Static resx:ResUI.TbSettingsFragmentLength}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtFragmentLength"
x:Name="txtFragmentLengths"
Grid.Row="1"
Grid.Column="1"
Width="200"
@@ -437,7 +437,7 @@
Text="{x:Static resx:ResUI.TbSettingsFragmentInterval}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtFragmentInterval"
x:Name="txtFragmentDelays"
Grid.Row="2"
Grid.Column="1"
Width="200"
@@ -1198,6 +1198,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -1346,6 +1348,39 @@
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbRouteExcludeAddressTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbIpv4Address}" />
<ComboBox
x:Name="cmbIpv4Address"
Grid.Row="10"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbIpv6Address}" />
<ComboBox
x:Name="cmbIpv6Address"
Grid.Row="11"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left"
Style="{StaticResource DefComboBox}" />
</Grid>
</ScrollViewer>
</TabItem>

View File

@@ -29,6 +29,8 @@ public partial class OptionSettingWindow
cmbMtu.ItemsSource = Global.TunMtus;
cmbStack.ItemsSource = Global.TunStacks;
cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies;
cmbIpv4Address.ItemsSource = Global.TunIPv4Address;
cmbIpv6Address.ItemsSource = Global.TunIPv6Address;
cmbFragmentPackets.ItemsSource = Global.FragmentPacketsOptions;
cmbCoreType1.ItemsSource = Global.CoreTypes;
@@ -82,18 +84,10 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentPackets, v => v.cmbFragmentPackets.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentLength, v => v.txtFragmentLength.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentInterval, v => v.txtFragmentInterval.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentLengths, v => v.txtFragmentLengths.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentDelays, v => v.txtFragmentDelays.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FragmentMaxSplit, v => v.txtFragmentMaxSplit.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.KcpuplinkCapacity, v => v.txtKcpuplinkCapacity.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.KcpdownlinkCapacity, v => v.txtKcpdownlinkCapacity.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.KcpreadBufferSize, v => v.txtKcpreadBufferSize.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.KcpwriteBufferSize, v => v.txtKcpwriteBufferSize.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcpcongestion, v => v.togKcpcongestion.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DisplayRealTimeSpeed, v => v.togDisplayRealTimeSpeed.IsChecked).DisposeWith(disposables);
@@ -132,6 +126,8 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIPv4Address, v => v.cmbIpv4Address.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIPv6Address, v => v.cmbIpv6Address.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.Text).DisposeWith(disposables);

View File

@@ -18,6 +18,11 @@ public partial class RoutingRuleDetailsWindow
this.WhenActivated(disposables =>
{
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
@@ -31,11 +36,6 @@ public partial class RoutingRuleDetailsWindow
this.BindCommand(ViewModel, vm => vm.SelectProfileCmd, v => v.btnSelectProfile).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
.WhereNotNull()
.Subscribe(InitializeData)
.DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
}

View File

@@ -75,6 +75,8 @@ public partial class StatusBarView
interaction.SetOutput(Unit.Default);
}).DisposeWith(disposables);
});
_ = RefreshIcon();
}
private async Task RefreshIcon()