mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-23 15:42:06 +03:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af0eb9ed14 | ||
|
|
0436081436 | ||
|
|
2a89a2e597 | ||
|
|
2be63d1655 | ||
|
|
74bd28bbd0 | ||
|
|
be501cbd2a | ||
|
|
e20b4dbfab | ||
|
|
728cbe70f2 | ||
|
|
26a86f9bd2 | ||
|
|
c66c55b05e | ||
|
|
ebb4bd5daa | ||
|
|
14e27f3d23 | ||
|
|
2d514ff480 | ||
|
|
11bd52b6a5 | ||
|
|
3b3fe8def3 | ||
|
|
c9e843aa3b | ||
|
|
7be264c65c | ||
|
|
5e6660aeee | ||
|
|
5b27f4836f |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -6,6 +6,6 @@ updates:
|
|||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
|
||||||
- package-ecosystem: "nuget"
|
- package-ecosystem: "nuget"
|
||||||
directory: "/"
|
directory: "/v2rayN"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
|||||||
2
.github/workflows/build-windows-x86.yml
vendored
2
.github/workflows/build-windows-x86.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
|||||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||||
|
|
||||||
- name: Setup .NET 10.0.1xx
|
- name: Setup .NET 10.0.1xx
|
||||||
uses: actions/setup-dotnet@v6.0.0
|
uses: actions/setup-dotnet@v6
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.1xx
|
dotnet-version: 10.0.1xx
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -69,10 +69,10 @@ jobs:
|
|||||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v6.0.0
|
uses: actions/setup-dotnet@v6
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.1xx'
|
dotnet-version: 10.0.1xx
|
||||||
|
|
||||||
- name: Build v2rayN
|
- name: Build v2rayN
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ./v2rayN
|
working-directory: ./v2rayN
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.1
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.arch }}
|
name: ${{ matrix.arch }}
|
||||||
path: ${{ matrix.arch }}
|
path: ${{ matrix.arch }}
|
||||||
|
|||||||
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
@@ -9,6 +9,10 @@ on:
|
|||||||
- 'v2rayN/ServiceLib/Handler/Fmt/**'
|
- 'v2rayN/ServiceLib/Handler/Fmt/**'
|
||||||
- '.github/workflows/test.yml'
|
- '.github/workflows/test.yml'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -20,10 +24,17 @@ jobs:
|
|||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v6.0.0
|
uses: actions/setup-dotnet@v6
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '10.x'
|
||||||
|
dotnet-quality: 'ga'
|
||||||
|
|
||||||
- name: Test Code
|
- name: Test Code
|
||||||
working-directory: ./v2rayN
|
run: dotnet test --project ./v2rayN/ServiceLib.Tests -c Release --results-directory ./TestResults -- --report-trx
|
||||||
run: dotnet test ./ServiceLib.Tests
|
|
||||||
|
- name: Comment PR with results
|
||||||
|
if: always()
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||||
|
with:
|
||||||
|
files: ./TestResults/*.trx
|
||||||
|
comment_mode: failures
|
||||||
|
|||||||
5
global.json
Normal file
5
global.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"test": {
|
||||||
|
"runner": "Microsoft.Testing.Platform"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ PKGROOT="v2rayN-publish"
|
|||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
OUTPUT_DIR="${HOME}/debbuild"
|
OUTPUT_DIR="${HOME}/debbuild"
|
||||||
DOTNET_TFM="net10.0"
|
DOTNET_TFM="net10.0"
|
||||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
DOTNET_LOONGARCH_VERSION="10.0.111"
|
||||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
DOTNET_LOONGARCH_TAG="v10.0.111-loongarch64"
|
||||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
OUTPUT_DIR="${HOME}/debbuild"
|
OUTPUT_DIR="${HOME}/debbuild"
|
||||||
DOTNET_RISCV_VERSION="10.0.110"
|
DOTNET_RISCV_VERSION="10.0.111"
|
||||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ MIN_KERNEL="6.12"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
DOTNET_LOONGARCH_VERSION="10.0.111"
|
||||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
DOTNET_LOONGARCH_TAG="v10.0.111-loongarch64"
|
||||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||||
DOTNET_RISCV_VERSION="10.0.110"
|
DOTNET_RISCV_VERSION="10.0.111"
|
||||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.24.7</Version>
|
<Version>7.24.8</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -9,13 +9,11 @@
|
|||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||||
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
|
||||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.1" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.1" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.10.4" />
|
<PackageVersion Include="CliWrap" Version="3.10.5" />
|
||||||
<PackageVersion Include="Downloader" Version="5.9.5" />
|
<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="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||||
@@ -25,15 +23,15 @@
|
|||||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
|
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0.1" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="NLog" Version="6.1.4" />
|
<PackageVersion Include="NLog" Version="6.2.0" />
|
||||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
|
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
|
<PackageVersion Include="TUnit" Version="1.65.38" />
|
||||||
|
<PackageVersion Include="TUnit.Assertions.Should" Version="1.65.38-beta" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
|
||||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
|
||||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Handler.Builder;
|
|
||||||
using ServiceLib.Helper;
|
|
||||||
using ServiceLib.Models;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.CoreConfig.Context;
|
namespace ServiceLib.Tests.CoreConfig.Context;
|
||||||
|
|
||||||
public class CoreConfigContextBuilderTests
|
public class CoreConfigContextBuilderTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public async Task ResolveNodeAsync_DirectCycleDependency_ShouldFailWithCycleError()
|
public async Task ResolveNodeAsync_DirectCycleDependency_ShouldFailWithCycleError()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig();
|
var config = CoreConfigTestFactory.CreateConfig();
|
||||||
@@ -27,13 +20,13 @@ public class CoreConfigContextBuilderTests
|
|||||||
|
|
||||||
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
||||||
|
|
||||||
validatorResult.Success.Should().BeFalse();
|
await validatorResult.Success.Should().BeFalse();
|
||||||
validatorResult.Errors.Should().Contain(msg => ContainsCycleDependencyMessage(msg));
|
await validatorResult.Errors.Should().Contain(ContainsCycleDependencyMessage);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupA.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupA.IndexId);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public async Task ResolveNodeAsync_IndirectCycleDependency_ShouldFailWithCycleError()
|
public async Task ResolveNodeAsync_IndirectCycleDependency_ShouldFailWithCycleError()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig();
|
var config = CoreConfigTestFactory.CreateConfig();
|
||||||
@@ -53,14 +46,14 @@ public class CoreConfigContextBuilderTests
|
|||||||
|
|
||||||
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
||||||
|
|
||||||
validatorResult.Success.Should().BeFalse();
|
await validatorResult.Success.Should().BeFalse();
|
||||||
validatorResult.Errors.Should().Contain(msg => ContainsCycleDependencyMessage(msg));
|
await validatorResult.Errors.Should().Contain(ContainsCycleDependencyMessage);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupA.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupA.IndexId);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupC.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupC.IndexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public async Task ResolveNodeAsync_CycleWithValidBranch_ShouldSkipCycleAndKeepValidChild()
|
public async Task ResolveNodeAsync_CycleWithValidBranch_ShouldSkipCycleAndKeepValidChild()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig();
|
var config = CoreConfigTestFactory.CreateConfig();
|
||||||
@@ -80,14 +73,14 @@ public class CoreConfigContextBuilderTests
|
|||||||
|
|
||||||
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
var (_, validatorResult) = await CoreConfigContextBuilder.ResolveNodeAsync(context, groupA, false);
|
||||||
|
|
||||||
validatorResult.Success.Should().BeTrue();
|
await validatorResult.Success.Should().BeTrue();
|
||||||
validatorResult.Errors.Should().BeEmpty();
|
await validatorResult.Errors.Should().BeEmpty();
|
||||||
validatorResult.Warnings.Should().Contain(msg => ContainsCycleDependencyMessage(msg));
|
await validatorResult.Warnings.Should().Contain(ContainsCycleDependencyMessage);
|
||||||
|
|
||||||
context.AllProxiesMap.Should().ContainKey(leaf.IndexId);
|
await context.AllProxiesMap.Should().ContainKey(leaf.IndexId);
|
||||||
context.AllProxiesMap.Should().ContainKey(groupA.IndexId);
|
await context.AllProxiesMap.Should().ContainKey(groupA.IndexId);
|
||||||
context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
await context.AllProxiesMap.Should().NotContainKey(groupB.IndexId);
|
||||||
groupA.GetProtocolExtra().ChildItems.Should().Be(leaf.IndexId);
|
await groupA.GetProtocolExtra().ChildItems.Should().BeEqualTo(leaf.IndexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NewId(string prefix)
|
private static string NewId(string prefix)
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Common;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Handler.Fmt;
|
|
||||||
using ServiceLib.Manager;
|
|
||||||
using ServiceLib.Models;
|
|
||||||
using ServiceLib.Models.Dto;
|
|
||||||
using ServiceLib.Services.CoreConfig;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.CoreConfig.Singbox;
|
namespace ServiceLib.Tests.CoreConfig.Singbox;
|
||||||
|
|
||||||
public class CoreConfigSingboxServiceTests
|
public class CoreConfigSingboxServiceTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ShouldGenerateBasicProxyConfig()
|
public async Task GenerateClientConfigContent_ShouldGenerateBasicProxyConfig()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -22,17 +12,17 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
result.Data.Should().NotBeNull();
|
await result.Data.Should().NotBeNull();
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
||||||
singboxConfig.Should().NotBeNull();
|
await singboxConfig.Should().NotBeNull();
|
||||||
singboxConfig!.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "socks");
|
await singboxConfig!.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "socks");
|
||||||
singboxConfig.inbounds.Should().Contain(i => i.type == nameof(EInboundProtocol.mixed));
|
await singboxConfig.inbounds.Should().Contain(i => i.type == nameof(EInboundProtocol.mixed));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunWithLoopbackPreSocks_ShouldKeepMixedInbound()
|
public async Task GenerateClientConfigContent_TunWithLoopbackPreSocks_ShouldKeepMixedInbound()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -46,18 +36,18 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.inbounds.Should().Contain(i =>
|
await cfg.inbounds.Should().Contain(i =>
|
||||||
i.type == nameof(EInboundProtocol.mixed)
|
i.type == nameof(EInboundProtocol.mixed)
|
||||||
&& i.listen == Global.Loopback
|
&& i.listen == Global.Loopback
|
||||||
&& i.listen_port == AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
|
&& i.listen_port == AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
|
||||||
cfg.inbounds.Should().Contain(i => i.type == "tun");
|
await cfg.inbounds.Should().Contain(i => i.type == "tun");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules()
|
public async Task GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules()
|
||||||
{
|
{
|
||||||
// The embedded tun rules reject local-network noise (NetBIOS/mDNS, multicast).
|
// The embedded tun rules reject local-network noise (NetBIOS/mDNS, multicast).
|
||||||
// They are deserialized into List<Rule4Sbox>, so a schema mismatch in the
|
// They are deserialized into List<Rule4Sbox>, so a schema mismatch in the
|
||||||
@@ -75,22 +65,22 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.route.rules.Should().Contain(
|
await cfg.route.rules.Should().Contain(
|
||||||
r => r.action == "reject"
|
r => r.action == "reject"
|
||||||
&& r.network != null && r.network.Contains("udp")
|
&& r.network != null && r.network.Contains("udp")
|
||||||
&& r.port != null && r.port.Contains(5353),
|
&& r.port != null && r.port.Contains(5353),
|
||||||
"the embedded tun rules must reject mDNS/NetBIOS noise");
|
"the embedded tun rules must reject mDNS/NetBIOS noise");
|
||||||
cfg.route.rules.Should().Contain(
|
await cfg.route.rules.Should().Contain(
|
||||||
r => r.action == "reject"
|
r => r.action == "reject"
|
||||||
&& r.ip_cidr != null && r.ip_cidr.Contains("224.0.0.0/3"),
|
&& r.ip_cidr != null && r.ip_cidr.Contains("224.0.0.0/3"),
|
||||||
"the embedded tun rules must reject multicast traffic");
|
"the embedded tun rules must reject multicast traffic");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunEnabled_ShouldRejectTrafficToTunOwnAddresses()
|
public async Task GenerateClientConfigContent_TunEnabled_ShouldRejectTrafficToTunOwnAddresses()
|
||||||
{
|
{
|
||||||
// Regression test: traffic addressed to the TUN interface's own addresses must
|
// Regression test: traffic addressed to the TUN interface's own addresses must
|
||||||
// never reach an outbound. auto_route hijacks the default route, so `direct`
|
// never reach an outbound. auto_route hijacks the default route, so `direct`
|
||||||
@@ -111,17 +101,19 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var tun = cfg.inbounds.First(i => i.type == "tun");
|
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||||
tun.address.Should().NotBeNullOrEmpty();
|
//tun.address.Should().NotBeNullOrEmpty();
|
||||||
|
await tun.address.Should().NotBeNull();
|
||||||
|
await tun.address.Should().NotBeEmpty();
|
||||||
|
|
||||||
foreach (var address in tun.address!)
|
foreach (var address in tun.address!)
|
||||||
{
|
{
|
||||||
var self = IPAddress.Parse(address.Split('/').First());
|
var self = IPAddress.Parse(address.Split('/').First());
|
||||||
var hostBits = self.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
|
var hostBits = self.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
|
||||||
var expected = $"{self}/{hostBits}";
|
var expected = $"{self}/{hostBits}";
|
||||||
cfg.route.rules.Should().Contain(
|
await cfg.route.rules.Should().Contain(
|
||||||
r => r.action == "reject" && r.ip_cidr != null && r.ip_cidr.Contains(expected),
|
r => r.action == "reject" && r.ip_cidr != null && r.ip_cidr.Contains(expected),
|
||||||
$"traffic to the TUN's own address '{address}' must be rejected, not routed");
|
$"traffic to the TUN's own address '{address}' must be rejected, not routed");
|
||||||
}
|
}
|
||||||
@@ -131,12 +123,13 @@ public class CoreConfigSingboxServiceTests
|
|||||||
// here leaves room for it, so a prefix match would drop system name lookups too.
|
// here leaves room for it, so a prefix match would drop system name lookups too.
|
||||||
var dropRule = cfg.route.rules.First(r =>
|
var dropRule = cfg.route.rules.First(r =>
|
||||||
r.action == "reject" && r.method == "drop" && r.ip_cidr?.Count > 0);
|
r.action == "reject" && r.method == "drop" && r.ip_cidr?.Count > 0);
|
||||||
dropRule.ip_cidr!.Should().OnlyContain(c =>
|
//dropRule.ip_cidr!.Should().OnlyContain(c =>
|
||||||
c.EndsWith("/32", StringComparison.Ordinal) || c.EndsWith("/128", StringComparison.Ordinal));
|
// c.EndsWith("/32", StringComparison.Ordinal) || c.EndsWith("/128", StringComparison.Ordinal));
|
||||||
|
await dropRule.ip_cidr.Should().All(c => c.EndsWith("/32", StringComparison.Ordinal) || c.EndsWith("/128", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
public async Task GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
config.CoreBasicItem.BindInterface = "eth0";
|
config.CoreBasicItem.BindInterface = "eth0";
|
||||||
@@ -150,16 +143,16 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
||||||
|
|
||||||
proxy.bind_interface.Should().Be("eth0");
|
await proxy.bind_interface.Should().BeEqualTo("eth0");
|
||||||
proxy.detour.Should().BeNullOrEmpty();
|
await proxy.detour.Should().BeNull().Or.BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildSelector()
|
public async Task GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildSelector()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -176,17 +169,17 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
await cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
await cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ProxyChain_ShouldBuildDetourChain()
|
public async Task GenerateClientConfigContent_ProxyChain_ShouldBuildDetourChain()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -203,18 +196,18 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "socks");
|
await cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "socks");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o =>
|
await cfg.outbounds.Should().Contain(o =>
|
||||||
o.tag == Global.ProxyTag &&
|
o.tag == Global.ProxyTag &&
|
||||||
(o.detour ?? string.Empty).StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
(o.detour ?? string.Empty).StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_PolicyGroupWithProxyChain_ShouldBuildCombinedOutbounds()
|
public async Task GenerateClientConfigContent_PolicyGroupWithProxyChain_ShouldBuildCombinedOutbounds()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -236,18 +229,18 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
await cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
await cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ProxyChainWithPolicyGroup_ShouldBuildClonedChainBranches()
|
public async Task GenerateClientConfigContent_ProxyChainWithPolicyGroup_ShouldBuildClonedChainBranches()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -269,25 +262,25 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
await cfg.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.type == "selector");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
await cfg.outbounds.Should().Contain(o => o.tag == $"{Global.ProxyTag}-auto" && o.type == "urltest");
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-2-", StringComparison.Ordinal));
|
||||||
|
|
||||||
var proxyCloneCount = cfg.outbounds.Count(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal));
|
var proxyCloneCount = cfg.outbounds.Count(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal));
|
||||||
proxyCloneCount.Should().Be(2);
|
await proxyCloneCount.Should().BeEqualTo(2);
|
||||||
|
|
||||||
var allCloneDetoursPointToGroupBranches = cfg.outbounds
|
var allCloneDetoursPointToGroupBranches = cfg.outbounds
|
||||||
.Where(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal))
|
.Where(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal))
|
||||||
.All(o => (o.detour ?? string.Empty).StartsWith("chain-proxy-1-group-", StringComparison.Ordinal));
|
.All(o => (o.detour ?? string.Empty).StartsWith("chain-proxy-1-group-", StringComparison.Ordinal));
|
||||||
allCloneDetoursPointToGroupBranches.Should().BeTrue();
|
await allCloneDetoursPointToGroupBranches.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RoutingSplit_DirectAndBlock_ShouldApplyRules()
|
public async Task GenerateClientConfigContent_RoutingSplit_DirectAndBlock_ShouldApplyRules()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -323,24 +316,24 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hasDirectRule = cfg.route.rules.Any(r =>
|
var hasDirectRule = cfg.route.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("direct.example.com")
|
&& r.domain.Contains("direct.example.com")
|
||||||
&& r.outbound == Global.DirectTag);
|
&& r.outbound == Global.DirectTag);
|
||||||
hasDirectRule.Should().BeTrue();
|
await hasDirectRule.Should().BeTrue();
|
||||||
|
|
||||||
var hasBlockRule = cfg.route.rules.Any(r =>
|
var hasBlockRule = cfg.route.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("block.example.com")
|
&& r.domain.Contains("block.example.com")
|
||||||
&& r.action == "reject");
|
&& r.action == "reject");
|
||||||
hasBlockRule.Should().BeTrue();
|
await hasBlockRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RoutingSplit_ByRemark_ShouldGenerateTargetOutbound()
|
public async Task GenerateClientConfigContent_RoutingSplit_ByRemark_ShouldGenerateTargetOutbound()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -372,21 +365,21 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var expectedPrefix = $"{routeNode.IndexId}-{Global.ProxyTag}-{routeNode.Remarks}";
|
var expectedPrefix = $"{routeNode.IndexId}-{Global.ProxyTag}-{routeNode.Remarks}";
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith(expectedPrefix, StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith(expectedPrefix, StringComparison.Ordinal));
|
||||||
|
|
||||||
var hasRouteRule = cfg.route.rules.Any(r =>
|
var hasRouteRule = cfg.route.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("route.example.com")
|
&& r.domain.Contains("route.example.com")
|
||||||
&& (r.outbound ?? string.Empty).StartsWith(expectedPrefix, StringComparison.Ordinal));
|
&& (r.outbound ?? string.Empty).StartsWith(expectedPrefix, StringComparison.Ordinal));
|
||||||
hasRouteRule.Should().BeTrue();
|
await hasRouteRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_ShouldApplyGeoipAndCidrToDirectDnsRule()
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_ShouldApplyGeoipAndCidrToDirectDnsRule()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(
|
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(
|
||||||
ECoreType.sing_box,
|
ECoreType.sing_box,
|
||||||
@@ -417,7 +410,7 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
||||||
@@ -426,11 +419,11 @@ public class CoreConfigSingboxServiceTests
|
|||||||
&& r.rule_set?.Contains("geosite-cn") == true
|
&& r.rule_set?.Contains("geosite-cn") == true
|
||||||
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
||||||
|
|
||||||
hasExpectedRule.Should().BeTrue();
|
await hasExpectedRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_BootstrapDNS_ShouldConfigurePureIPResolver()
|
public async Task GenerateClientConfigContent_BootstrapDNS_ShouldConfigurePureIPResolver()
|
||||||
{
|
{
|
||||||
var bootstrapDns = "8.8.8.8";
|
var bootstrapDns = "8.8.8.8";
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithBootstrapDNS(ECoreType.sing_box, bootstrapDns);
|
var config = CoreConfigTestFactory.CreateConfigWithBootstrapDNS(ECoreType.sing_box, bootstrapDns);
|
||||||
@@ -441,17 +434,17 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
config.SimpleDNSItem.BootstrapDNS.Should().Be(bootstrapDns);
|
await config.SimpleDNSItem.BootstrapDNS.Should().BeEqualTo(bootstrapDns);
|
||||||
|
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var bootstrapServer = cfg.dns.servers?.FirstOrDefault(s => s.tag == Global.SingboxLocalDNSTag);
|
var bootstrapServer = cfg.dns?.servers.FirstOrDefault(s => s.tag == Global.SingboxLocalDNSTag);
|
||||||
bootstrapServer.Should().NotBeNull();
|
await bootstrapServer.Should().NotBeNull();
|
||||||
(bootstrapServer?.server ?? string.Empty).Should().Contain(bootstrapDns);
|
await bootstrapServer!.server.Should().Contain(bootstrapDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DnsFallback_LastRuleDirect_ShouldUseDirectFinalDns()
|
public async Task GenerateClientConfigContent_DnsFallback_LastRuleDirect_ShouldUseDirectFinalDns()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
config.SimpleDNSItem.DirectDNS = "1.1.1.1";
|
config.SimpleDNSItem.DirectDNS = "1.1.1.1";
|
||||||
@@ -484,14 +477,14 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.dns.final.Should().Be(Global.SingboxDirectDNSTag);
|
await cfg.dns.final.Should().BeEqualTo(Global.SingboxDirectDNSTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_NonMatchingRegion_ShouldNotApplyExpectedRule()
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_NonMatchingRegion_ShouldNotApplyExpectedRule()
|
||||||
{
|
{
|
||||||
var config =
|
var config =
|
||||||
CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.sing_box, "192.168.0.0/16,geoip:cn");
|
CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.sing_box, "192.168.0.0/16,geoip:cn");
|
||||||
@@ -521,21 +514,21 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
||||||
r.server == Global.SingboxDirectDNSTag
|
r.server == Global.SingboxDirectDNSTag
|
||||||
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
|
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
|
||||||
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
||||||
hasExpectedRule.Should().BeFalse();
|
await hasExpectedRule.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Test]
|
||||||
[InlineData("geosite:cn", "geosite-cn")]
|
[Arguments("geosite:cn", "geosite-cn")]
|
||||||
[InlineData("geosite:geolocation-cn", "geosite-geolocation-cn")]
|
[Arguments("geosite:geolocation-cn", "geosite-geolocation-cn")]
|
||||||
[InlineData("geosite:tld-cn", "geosite-tld-cn")]
|
[Arguments("geosite:tld-cn", "geosite-tld-cn")]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_RegionVariant_ShouldApplyExpectedRule(string domainTag,
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_RegionVariant_ShouldApplyExpectedRule(string domainTag,
|
||||||
string expectedRuleSetTag)
|
string expectedRuleSetTag)
|
||||||
{
|
{
|
||||||
var config =
|
var config =
|
||||||
@@ -563,7 +556,7 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
var hasExpectedRule = cfg.dns.rules?.Any(r =>
|
||||||
@@ -571,11 +564,11 @@ public class CoreConfigSingboxServiceTests
|
|||||||
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
|
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
|
||||||
&& r.rule_set?.Contains(expectedRuleSetTag) == true
|
&& r.rule_set?.Contains(expectedRuleSetTag) == true
|
||||||
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
|
||||||
hasExpectedRule.Should().BeTrue();
|
await hasExpectedRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_Hosts_ShouldPopulateHostsServerAndDomainResolver()
|
public async Task GenerateClientConfigContent_Hosts_ShouldPopulateHostsServerAndDomainResolver()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
config.SimpleDNSItem.Hosts = "resolver.example 1.1.1.1";
|
config.SimpleDNSItem.Hosts = "resolver.example 1.1.1.1";
|
||||||
@@ -587,21 +580,21 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hostsServer = cfg.dns.servers.FirstOrDefault(s => s.tag == Global.SingboxHostsDNSTag);
|
var hostsServer = cfg.dns.servers.FirstOrDefault(s => s.tag == Global.SingboxHostsDNSTag);
|
||||||
hostsServer.Should().NotBeNull();
|
await hostsServer.Should().NotBeNull();
|
||||||
hostsServer!.predefined.Should().ContainKey("resolver.example");
|
await hostsServer!.predefined.Should().ContainKey("resolver.example");
|
||||||
hostsServer.predefined!["resolver.example"].Should().Contain("1.1.1.1");
|
await hostsServer.predefined!["resolver.example"].Should().Contain("1.1.1.1");
|
||||||
|
|
||||||
var directServer = cfg.dns.servers.FirstOrDefault(s => s.tag == Global.SingboxDirectDNSTag);
|
var directServer = cfg.dns.servers.FirstOrDefault(s => s.tag == Global.SingboxDirectDNSTag);
|
||||||
directServer.Should().NotBeNull();
|
await directServer.Should().NotBeNull();
|
||||||
directServer!.domain_resolver.Should().Be(Global.SingboxHostsDNSTag);
|
await directServer!.domain_resolver.Should().BeEqualTo(Global.SingboxHostsDNSTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RawDnsEnabled_ShouldUseCustomDnsAndInjectLocalResolver()
|
public async Task GenerateClientConfigContent_RawDnsEnabled_ShouldUseCustomDnsAndInjectLocalResolver()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -630,22 +623,22 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.dns.servers.Should().Contain(s => s.tag == "remote" && s.type == "udp" && s.server == "8.8.8.8");
|
await cfg.dns.servers.Should().Contain(s => s.tag == "remote" && s.type == "udp" && s.server == "8.8.8.8");
|
||||||
cfg.dns.servers.Should().Contain(s => s.tag == Global.SingboxLocalDNSTag);
|
await cfg.dns.servers.Should().Contain(s => s.tag == Global.SingboxLocalDNSTag);
|
||||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
await cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
||||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
await cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
|
public async Task GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
|
||||||
{
|
{
|
||||||
var shareLink =
|
var shareLink =
|
||||||
"hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
"hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||||
var node = Hysteria2Fmt.ResolveRealm(shareLink, out _);
|
var node = Hysteria2Fmt.ResolveRealm(shareLink, out _);
|
||||||
node.Should().NotBeNull();
|
await node.Should().NotBeNull();
|
||||||
node!.CoreType = ECoreType.sing_box;
|
node!.CoreType = ECoreType.sing_box;
|
||||||
|
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
@@ -658,22 +651,22 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
||||||
|
|
||||||
proxy.type.Should().Be("hysteria2");
|
await proxy.type.Should().BeEqualTo("hysteria2");
|
||||||
proxy.realm.Should().NotBeNull();
|
await proxy.realm.Should().NotBeNull();
|
||||||
proxy.realm!.server_url.Should().StartWith("https://");
|
await proxy.realm!.server_url.Should().StartWith("https://");
|
||||||
proxy.realm.server_url.Should().Contain("realm.hy2.io");
|
await proxy.realm.server_url.Should().Contain("realm.hy2.io");
|
||||||
proxy.realm.token.Should().Be("public");
|
await proxy.realm.token.Should().BeEqualTo("public");
|
||||||
proxy.realm.realm_id.Should().Be("my-realm-id");
|
await proxy.realm.realm_id.Should().BeEqualTo("my-realm-id");
|
||||||
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
await proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||||
proxy.server.Should().BeNull();
|
await proxy.server.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunSystemStackWithIpv6_ShouldUsePrefixWithPeerAddress()
|
public async Task GenerateClientConfigContent_TunSystemStackWithIpv6_ShouldUsePrefixWithPeerAddress()
|
||||||
{
|
{
|
||||||
// Regression test for #9820: sing-box fails with "need one more IPv6 address in
|
// 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.
|
// first prefix for system stack" when the TUN inbound uses a /128 IPv6 prefix.
|
||||||
@@ -691,23 +684,24 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
var tun = cfg.inbounds.First(i => i.type == "tun");
|
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||||
|
|
||||||
tun.address.Should().NotBeNullOrEmpty();
|
await tun.address.Should().NotBeNull();
|
||||||
|
await tun.address.Should().NotBeEmpty();
|
||||||
foreach (var address in tun.address!)
|
foreach (var address in tun.address!)
|
||||||
{
|
{
|
||||||
var prefixLength = int.Parse(address[(address.LastIndexOf('/') + 1)..]);
|
var prefixLength = int.Parse(address[(address.LastIndexOf('/') + 1)..]);
|
||||||
var isIpv6 = address.Contains(':');
|
var isIpv6 = address.Contains(':');
|
||||||
prefixLength.Should().BeLessThanOrEqualTo(isIpv6 ? 126 : 30,
|
await prefixLength.Should().BeLessThanOrEqualTo(isIpv6 ? 126 : 30,
|
||||||
$"'{address}' must leave room for the peer address the system stack derives");
|
$"'{address}' must leave room for the peer address the system stack derives");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
public async Task GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -728,17 +722,17 @@ public class CoreConfigSingboxServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
result.Data.Should().NotBeNull();
|
await result.Data.Should().NotBeNull();
|
||||||
|
|
||||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
||||||
cfg.Should().NotBeNull();
|
await cfg.Should().NotBeNull();
|
||||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||||
proxyOutbound.Should().NotBeNull();
|
await proxyOutbound.Should().NotBeNull();
|
||||||
proxyOutbound!.type.Should().Be("shadowsocks");
|
await proxyOutbound!.type.Should().BeEqualTo("shadowsocks");
|
||||||
proxyOutbound.server.Should().Be("1.2.3.4");
|
await proxyOutbound.server.Should().BeEqualTo("1.2.3.4");
|
||||||
proxyOutbound.server_port.Should().Be(8388);
|
await proxyOutbound.server_port.Should().BeEqualTo(8388);
|
||||||
proxyOutbound.method.Should().Be("aes-128-gcm");
|
await proxyOutbound.method.Should().BeEqualTo("aes-128-gcm");
|
||||||
proxyOutbound.password.Should().Be("custom_password");
|
await proxyOutbound.password.Should().BeEqualTo("custom_password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Common;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Models;
|
|
||||||
using ServiceLib.Services.CoreConfig;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.CoreConfig.V2ray;
|
namespace ServiceLib.Tests.CoreConfig.V2ray;
|
||||||
|
|
||||||
public class CoreConfigV2rayServiceTests
|
public class CoreConfigV2rayServiceTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ShouldGenerateBasicProxyConfig()
|
public async Task GenerateClientConfigContent_ShouldGenerateBasicProxyConfig()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -19,17 +12,17 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
result.Data.Should().NotBeNull();
|
await result.Data.Should().NotBeNull();
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
||||||
v2rayConfig.Should().NotBeNull();
|
await v2rayConfig.Should().NotBeNull();
|
||||||
v2rayConfig!.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.protocol == "vmess");
|
await v2rayConfig!.outbounds.Should().Contain(o => o.tag == Global.ProxyTag && o.protocol == "vmess");
|
||||||
v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed));
|
await v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_HttpOutbound_ShouldEmitHeadersInSettings()
|
public async Task GenerateClientConfigContent_HttpOutbound_ShouldEmitHeadersInSettings()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -42,27 +35,27 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var outbound = cfg.outbounds.First(o => o.tag == Global.ProxyTag && o.protocol == "http");
|
var outbound = cfg.outbounds.First(o => o.tag == Global.ProxyTag && o.protocol == "http");
|
||||||
|
|
||||||
outbound.settings.address?.ToString().Should().Be("proxy.example.com");
|
await outbound.settings.address!.ToString().Should().BeEqualTo("proxy.example.com");
|
||||||
outbound.settings.port.Should().Be(8080);
|
await outbound.settings.port.Should().BeEqualTo(8080);
|
||||||
outbound.settings.user.Should().Be("user");
|
await outbound.settings.user.Should().BeEqualTo("user");
|
||||||
outbound.settings.pass.Should().Be("pass");
|
await outbound.settings.pass.Should().BeEqualTo("pass");
|
||||||
outbound.settings.level.Should().Be(1);
|
await outbound.settings.level.Should().BeEqualTo(1);
|
||||||
outbound.settings.headers.Should().NotBeNull();
|
await outbound.settings.headers.Should().NotBeNull();
|
||||||
var headers = JsonUtils.ParseJson(outbound.settings.headers.ToString());
|
var headers = JsonUtils.ParseJson(outbound.settings.headers!.ToString());
|
||||||
headers["User-Agent"]!.GetValue<string>().Should().Be("v2rayN");
|
await headers["User-Agent"]!.GetValue<string>().Should().BeEqualTo("v2rayN");
|
||||||
headers["Set-Cookie"]!.AsArray()
|
await headers["Set-Cookie"]!.AsArray()
|
||||||
.Select(item => item!.GetValue<string>())
|
.Select(item => item!.GetValue<string>())
|
||||||
.Should().Equal("a=1", "b=2");
|
.Should().BeEquivalentTo(["a=1", "b=2"]);
|
||||||
outbound.settings.servers.Should().BeNull();
|
await outbound.settings.servers.Should().BeNull();
|
||||||
outbound.settings.vnext.Should().BeNull();
|
await outbound.settings.vnext.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
public async Task GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -79,17 +72,17 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
||||||
cfg.routing.balancers.Should().NotBeNull();
|
await cfg.routing.balancers.Should().NotBeNull();
|
||||||
cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
await cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ProxyChain_ShouldBuildDialerProxyChain()
|
public async Task GenerateClientConfigContent_ProxyChain_ShouldBuildDialerProxyChain()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -105,21 +98,21 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
||||||
var hasDialerChain = cfg.outbounds.Any(o =>
|
var hasDialerChain = cfg.outbounds.Any(o =>
|
||||||
o.tag == Global.ProxyTag
|
o.tag == Global.ProxyTag
|
||||||
&& o.streamSettings is not null
|
&& o.streamSettings is not null
|
||||||
&& o.streamSettings.sockopt is not null
|
&& o.streamSettings.sockopt is not null
|
||||||
&& (o.streamSettings.sockopt.dialerProxy ?? string.Empty).StartsWith("chain-proxy-1-",
|
&& (o.streamSettings.sockopt.dialerProxy ?? string.Empty).StartsWith("chain-proxy-1-",
|
||||||
StringComparison.Ordinal));
|
StringComparison.Ordinal));
|
||||||
hasDialerChain.Should().BeTrue();
|
await hasDialerChain.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_PolicyGroupWithProxyChain_ShouldBuildCombinedOutbounds()
|
public async Task GenerateClientConfigContent_PolicyGroupWithProxyChain_ShouldBuildCombinedOutbounds()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -140,18 +133,18 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("proxy-2-", StringComparison.Ordinal));
|
||||||
cfg.routing.balancers.Should().NotBeNull();
|
await cfg.routing.balancers.Should().NotBeNull();
|
||||||
cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
await cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_ProxyChainWithPolicyGroup_ShouldBuildClonedChainBranches()
|
public async Task GenerateClientConfigContent_ProxyChainWithPolicyGroup_ShouldBuildClonedChainBranches()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -173,27 +166,27 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-1-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-1-", StringComparison.Ordinal));
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-2-", StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith("chain-proxy-1-group-2-", StringComparison.Ordinal));
|
||||||
|
|
||||||
var proxyCloneCount = cfg.outbounds.Count(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal));
|
var proxyCloneCount = cfg.outbounds.Count(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal));
|
||||||
proxyCloneCount.Should().Be(2);
|
await proxyCloneCount.Should().BeEqualTo(2);
|
||||||
|
|
||||||
var allCloneDialersPointToGroupBranches = cfg.outbounds
|
var allCloneDialersPointToGroupBranches = cfg.outbounds
|
||||||
.Where(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal))
|
.Where(o => o.tag.StartsWith("proxy-clone-", StringComparison.Ordinal))
|
||||||
.All(o => (o.streamSettings?.sockopt?.dialerProxy ?? string.Empty).StartsWith("chain-proxy-1-group-",
|
.All(o => (o.streamSettings?.sockopt?.dialerProxy ?? string.Empty).StartsWith("chain-proxy-1-group-",
|
||||||
StringComparison.Ordinal));
|
StringComparison.Ordinal));
|
||||||
allCloneDialersPointToGroupBranches.Should().BeTrue();
|
await allCloneDialersPointToGroupBranches.Should().BeTrue();
|
||||||
|
|
||||||
cfg.routing.balancers.Should().NotBeNull();
|
await cfg.routing.balancers.Should().NotBeNull();
|
||||||
cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
await cfg.routing.balancers!.Should().Contain(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RoutingSplit_DirectAndBlock_ShouldApplyRules()
|
public async Task GenerateClientConfigContent_RoutingSplit_DirectAndBlock_ShouldApplyRules()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -229,24 +222,24 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
var hasDirectRule = cfg.routing.rules.Any(r =>
|
var hasDirectRule = cfg.routing.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("full:direct.example.com")
|
&& r.domain.Contains("full:direct.example.com")
|
||||||
&& r.outboundTag == Global.DirectTag);
|
&& r.outboundTag == Global.DirectTag);
|
||||||
hasDirectRule.Should().BeTrue();
|
await hasDirectRule.Should().BeTrue();
|
||||||
|
|
||||||
var hasBlockRule = cfg.routing.rules.Any(r =>
|
var hasBlockRule = cfg.routing.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("full:block.example.com")
|
&& r.domain.Contains("full:block.example.com")
|
||||||
&& r.outboundTag == Global.BlockTag);
|
&& r.outboundTag == Global.BlockTag);
|
||||||
hasBlockRule.Should().BeTrue();
|
await hasBlockRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RoutingSplit_ByRemark_ShouldGenerateTargetOutbound()
|
public async Task GenerateClientConfigContent_RoutingSplit_ByRemark_ShouldGenerateTargetOutbound()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -278,20 +271,20 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var expectedPrefix = $"{routeNode.IndexId}-{Global.ProxyTag}-{routeNode.Remarks}";
|
var expectedPrefix = $"{routeNode.IndexId}-{Global.ProxyTag}-{routeNode.Remarks}";
|
||||||
|
|
||||||
cfg.outbounds.Should().Contain(o => o.tag.StartsWith(expectedPrefix, StringComparison.Ordinal));
|
await cfg.outbounds.Should().Contain(o => o.tag.StartsWith(expectedPrefix, StringComparison.Ordinal));
|
||||||
var hasRouteRule = cfg.routing.rules.Any(r =>
|
var hasRouteRule = cfg.routing.rules.Any(r =>
|
||||||
r.domain != null
|
r.domain != null
|
||||||
&& r.domain.Contains("full:route.example.com")
|
&& r.domain.Contains("full:route.example.com")
|
||||||
&& (r.outboundTag ?? string.Empty).StartsWith(expectedPrefix, StringComparison.Ordinal));
|
&& (r.outboundTag ?? string.Empty).StartsWith(expectedPrefix, StringComparison.Ordinal));
|
||||||
hasRouteRule.Should().BeTrue();
|
await hasRouteRule.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_ShouldApplyExpectedIPsToDirectDnsServer()
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_ShouldApplyExpectedIPsToDirectDnsServer()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -320,7 +313,7 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
|
|
||||||
@@ -335,11 +328,11 @@ public class CoreConfigV2rayServiceTests
|
|||||||
&& s.domains?.Contains("geosite:cn") == true
|
&& s.domains?.Contains("geosite:cn") == true
|
||||||
&& s.expectedIPs?.Contains("192.168.0.0/16") == true
|
&& s.expectedIPs?.Contains("192.168.0.0/16") == true
|
||||||
&& s.expectedIPs?.Contains("geoip:cn") == true);
|
&& s.expectedIPs?.Contains("geoip:cn") == true);
|
||||||
hasExpectedServer.Should().BeTrue();
|
await hasExpectedServer.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_BootstrapDNS_ShouldApplyToDnsServerDomains()
|
public async Task GenerateClientConfigContent_BootstrapDNS_ShouldApplyToDnsServerDomains()
|
||||||
{
|
{
|
||||||
var bootstrapDns = "8.8.8.8";
|
var bootstrapDns = "8.8.8.8";
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithBootstrapDNS(ECoreType.Xray, bootstrapDns);
|
var config = CoreConfigTestFactory.CreateConfigWithBootstrapDNS(ECoreType.Xray, bootstrapDns);
|
||||||
@@ -352,7 +345,7 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
|
|
||||||
@@ -366,11 +359,11 @@ public class CoreConfigV2rayServiceTests
|
|||||||
s.address == bootstrapDns
|
s.address == bootstrapDns
|
||||||
&& s.domains?.Contains("full:dns-direct.example") == true
|
&& s.domains?.Contains("full:dns-direct.example") == true
|
||||||
&& s.domains?.Contains("full:dns-remote.example") == true);
|
&& s.domains?.Contains("full:dns-remote.example") == true);
|
||||||
hasBootstrapServer.Should().BeTrue();
|
await hasBootstrapServer.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DnsFallback_LastRuleDirect_ShouldUseDirectDnsServers()
|
public async Task GenerateClientConfigContent_DnsFallback_LastRuleDirect_ShouldUseDirectDnsServers()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
config.SimpleDNSItem.DirectDNS = "1.1.1.1";
|
config.SimpleDNSItem.DirectDNS = "1.1.1.1";
|
||||||
@@ -403,7 +396,7 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
var dnsServers = dns.servers
|
var dnsServers = dns.servers
|
||||||
@@ -415,14 +408,14 @@ public class CoreConfigV2rayServiceTests
|
|||||||
var hasDirectFallback = dnsServers.Any(s =>
|
var hasDirectFallback = dnsServers.Any(s =>
|
||||||
(s.tag ?? string.Empty).StartsWith(Global.DirectDnsTag, StringComparison.Ordinal)
|
(s.tag ?? string.Empty).StartsWith(Global.DirectDnsTag, StringComparison.Ordinal)
|
||||||
&& s.address == "1.1.1.1");
|
&& s.address == "1.1.1.1");
|
||||||
hasDirectFallback.Should().BeTrue();
|
await hasDirectFallback.Should().BeTrue();
|
||||||
|
|
||||||
var hasRemoteFallback = dnsServers.Any(s => s.address == "9.9.9.9");
|
var hasRemoteFallback = dnsServers.Any(s => s.address == "9.9.9.9");
|
||||||
hasRemoteFallback.Should().BeFalse();
|
await hasRemoteFallback.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_NonMatchingRegion_ShouldNotApplyExpectedIPs()
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_NonMatchingRegion_ShouldNotApplyExpectedIPs()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -451,7 +444,7 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
var dnsServers = dns.servers
|
var dnsServers = dns.servers
|
||||||
@@ -463,14 +456,14 @@ public class CoreConfigV2rayServiceTests
|
|||||||
var hasExpectedIPs = dnsServers.Any(s =>
|
var hasExpectedIPs = dnsServers.Any(s =>
|
||||||
s.expectedIPs?.Contains("192.168.0.0/16") == true
|
s.expectedIPs?.Contains("192.168.0.0/16") == true
|
||||||
|| s.expectedIPs?.Contains("geoip:cn") == true);
|
|| s.expectedIPs?.Contains("geoip:cn") == true);
|
||||||
hasExpectedIPs.Should().BeFalse();
|
await hasExpectedIPs.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Test]
|
||||||
[InlineData("geosite:cn")]
|
[Arguments("geosite:cn")]
|
||||||
[InlineData("geosite:geolocation-cn")]
|
[Arguments("geosite:geolocation-cn")]
|
||||||
[InlineData("geosite:tld-cn")]
|
[Arguments("geosite:tld-cn")]
|
||||||
public void GenerateClientConfigContent_DirectExpectedIPs_RegionVariant_ShouldApplyExpectedIPs(string domainTag)
|
public async Task GenerateClientConfigContent_DirectExpectedIPs_RegionVariant_ShouldApplyExpectedIPs(string domainTag)
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(ECoreType.Xray, "192.168.0.0/16,geoip:cn");
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -496,7 +489,7 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
var dnsServers = dns.servers
|
var dnsServers = dns.servers
|
||||||
@@ -510,11 +503,11 @@ public class CoreConfigV2rayServiceTests
|
|||||||
&& s.domains?.Contains(domainTag) == true
|
&& s.domains?.Contains(domainTag) == true
|
||||||
&& s.expectedIPs?.Contains("192.168.0.0/16") == true
|
&& s.expectedIPs?.Contains("192.168.0.0/16") == true
|
||||||
&& s.expectedIPs?.Contains("geoip:cn") == true);
|
&& s.expectedIPs?.Contains("geoip:cn") == true);
|
||||||
hasExpectedServer.Should().BeTrue();
|
await hasExpectedServer.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_Hosts_ShouldPopulateDnsHosts()
|
public async Task GenerateClientConfigContent_Hosts_ShouldPopulateDnsHosts()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
config.SimpleDNSItem.Hosts = "resolver.example 1.1.1.1";
|
config.SimpleDNSItem.Hosts = "resolver.example 1.1.1.1";
|
||||||
@@ -525,17 +518,17 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
|
|
||||||
dns.hosts.Should().NotBeNull();
|
await dns.hosts.Should().NotBeNull();
|
||||||
dns.hosts!.Should().ContainKey("resolver.example");
|
await dns.hosts!.Should().ContainKey("resolver.example");
|
||||||
JsonUtils.Serialize(dns.hosts!["resolver.example"]).Should().Contain("1.1.1.1");
|
await JsonUtils.Serialize(dns.hosts!["resolver.example"]).Should().Contain("1.1.1.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_RawDnsEnabled_ShouldUseCustomDnsConfig()
|
public async Task GenerateClientConfigContent_RawDnsEnabled_ShouldUseCustomDnsConfig()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -556,24 +549,24 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
var dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(cfg.dns))!;
|
||||||
|
|
||||||
JsonUtils.Serialize(dns.servers).Should().Contain("8.8.8.8");
|
await JsonUtils.Serialize(dns.servers).Should().Contain("8.8.8.8");
|
||||||
dns.hosts.Should().NotBeNull();
|
await dns.hosts.Should().NotBeNull();
|
||||||
dns.hosts!.Should().ContainKey("raw.example");
|
await dns.hosts!.Should().ContainKey("raw.example");
|
||||||
JsonUtils.Serialize(dns.hosts!["raw.example"]).Should().Contain("1.1.1.1");
|
await JsonUtils.Serialize(dns.hosts!["raw.example"]).Should().Contain("1.1.1.1");
|
||||||
|
|
||||||
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
||||||
directOutbound.Should().NotBeNull();
|
await directOutbound.Should().NotBeNull();
|
||||||
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
await directOutbound!.streamSettings.sockopt!.domainStrategy.Should().BeEqualTo("UseIPv4");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Test]
|
||||||
[InlineData(false)]
|
[Arguments(false)]
|
||||||
[InlineData(true)]
|
[Arguments(true)]
|
||||||
public void GenerateClientConfigContent_Tun_ShouldRouteIPv6IntoTunnel(bool enableIPv6Address)
|
public async Task GenerateClientConfigContent_Tun_ShouldRouteIPv6IntoTunnel(bool enableIPv6Address)
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -583,20 +576,20 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
tunInbound.Should().NotBeNull();
|
await tunInbound.Should().NotBeNull();
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("0.0.0.0/0");
|
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("0.0.0.0/0");
|
||||||
tunInbound.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
await tunInbound.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
||||||
|
|
||||||
// EnableIPv6Address governs the interface address only, never the routing table.
|
// EnableIPv6Address governs the interface address only, never the routing table.
|
||||||
tunInbound.settings.gateway.Should().HaveCount(enableIPv6Address ? 2 : 1);
|
await tunInbound.settings.gateway.Should().HaveCount(enableIPv6Address ? 2 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunRouteExcludeAddress_ShouldIncludeIPv6Ranges()
|
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldIncludeIPv6Ranges()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
||||||
config.TunModeItem.EnableIPv6Address = false;
|
config.TunModeItem.EnableIPv6Address = false;
|
||||||
@@ -607,16 +600,16 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
tunInbound.Should().NotBeNull();
|
await tunInbound.Should().NotBeNull();
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain(x => x.Contains(':'));
|
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain(x => x.Contains(':'));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_TunRouteExcludeAddress()
|
public async Task GenerateClientConfigContent_TunRouteExcludeAddress()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -626,20 +619,20 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
await result.Success.Should().BeTrue();
|
||||||
|
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
tunInbound.Should().NotBeNull();
|
await tunInbound.Should().NotBeNull();
|
||||||
|
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().NotContain("0.0.0.0/0");
|
await tunInbound!.settings.autoSystemRoutingTable.Should().NotContain("0.0.0.0/0");
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
|
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
public async Task GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||||
{
|
{
|
||||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
@@ -666,14 +659,14 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
|
||||||
result.Data.Should().NotBeNull();
|
await result.Data.Should().NotBeNull();
|
||||||
|
|
||||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
||||||
cfg.Should().NotBeNull();
|
await cfg.Should().NotBeNull();
|
||||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||||
proxyOutbound.Should().NotBeNull();
|
await proxyOutbound.Should().NotBeNull();
|
||||||
proxyOutbound!.protocol.Should().Be("shadowsocks");
|
await proxyOutbound!.protocol.Should().BeEqualTo("shadowsocks");
|
||||||
proxyOutbound.settings.servers.Should().NotBeNull();
|
await proxyOutbound.settings.servers.Should().NotBeNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,99 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Handler.Fmt;
|
|
||||||
using ServiceLib.Models;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Fmt;
|
namespace ServiceLib.Tests.Fmt;
|
||||||
|
|
||||||
public class FmtHandlerTests
|
public class FmtHandlerTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUriAndResolveConfig_Vmess_ShouldRoundTripBasicFields()
|
public async Task GetShareUriAndResolveConfig_Vmess_ShouldRoundTripBasicFields()
|
||||||
{
|
{
|
||||||
var source = CreateVmessProfile();
|
var source = CreateVmessProfile();
|
||||||
|
|
||||||
var resolved = ExportThenImport(source);
|
var resolved = await ExportThenImport(source);
|
||||||
|
|
||||||
resolved.ConfigType.Should().Be(EConfigType.VMess);
|
await resolved.ConfigType.Should().BeEqualTo(EConfigType.VMess);
|
||||||
resolved.Remarks.Should().Be(source.Remarks);
|
await resolved.Remarks.Should().BeEqualTo(source.Remarks);
|
||||||
resolved.Address.Should().Be(source.Address);
|
await resolved.Address.Should().BeEqualTo(source.Address);
|
||||||
resolved.Port.Should().Be(source.Port);
|
await resolved.Port.Should().BeEqualTo(source.Port);
|
||||||
resolved.Password.Should().Be(source.Password);
|
await resolved.Password.Should().BeEqualTo(source.Password);
|
||||||
resolved.GetProtocolExtra().AlterId.Should().Be(source.GetProtocolExtra().AlterId);
|
await resolved.GetProtocolExtra().AlterId.Should().BeEqualTo(source.GetProtocolExtra().AlterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUriAndResolveConfig_Vless_ShouldRoundTripBasicFields()
|
public async Task GetShareUriAndResolveConfig_Vless_ShouldRoundTripBasicFields()
|
||||||
{
|
{
|
||||||
var source = CreateVlessProfile();
|
var source = CreateVlessProfile();
|
||||||
|
|
||||||
var resolved = ExportThenImport(source);
|
var resolved = await ExportThenImport(source);
|
||||||
|
|
||||||
resolved.ConfigType.Should().Be(EConfigType.VLESS);
|
await resolved.ConfigType.Should().BeEqualTo(EConfigType.VLESS);
|
||||||
resolved.Remarks.Should().Be(source.Remarks);
|
await resolved.Remarks.Should().BeEqualTo(source.Remarks);
|
||||||
resolved.Address.Should().Be(source.Address);
|
await resolved.Address.Should().BeEqualTo(source.Address);
|
||||||
resolved.Port.Should().Be(source.Port);
|
await resolved.Port.Should().BeEqualTo(source.Port);
|
||||||
resolved.Password.Should().Be(source.Password);
|
await resolved.Password.Should().BeEqualTo(source.Password);
|
||||||
resolved.GetProtocolExtra().VlessEncryption.Should().Be(Global.None);
|
await resolved.GetProtocolExtra().VlessEncryption.Should().BeEqualTo(Global.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUriAndResolveConfig_Shadowsocks_ShouldRoundTripBasicFields()
|
public async Task GetShareUriAndResolveConfig_Shadowsocks_ShouldRoundTripBasicFields()
|
||||||
{
|
{
|
||||||
var source = CreateShadowsocksProfile();
|
var source = CreateShadowsocksProfile();
|
||||||
|
|
||||||
var resolved = ExportThenImport(source);
|
var resolved = await ExportThenImport(source);
|
||||||
|
|
||||||
resolved.ConfigType.Should().Be(EConfigType.Shadowsocks);
|
await resolved.ConfigType.Should().BeEqualTo(EConfigType.Shadowsocks);
|
||||||
resolved.Remarks.Should().Be(source.Remarks);
|
await resolved.Remarks.Should().BeEqualTo(source.Remarks);
|
||||||
resolved.Address.Should().Be(source.Address);
|
await resolved.Address.Should().BeEqualTo(source.Address);
|
||||||
resolved.Port.Should().Be(source.Port);
|
await resolved.Port.Should().BeEqualTo(source.Port);
|
||||||
resolved.Password.Should().Be(source.Password);
|
await resolved.Password.Should().BeEqualTo(source.Password);
|
||||||
resolved.GetProtocolExtra().SsMethod.Should().Be(source.GetProtocolExtra().SsMethod);
|
await resolved.GetProtocolExtra().SsMethod.Should().BeEqualTo(source.GetProtocolExtra().SsMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUriAndResolveConfig_Socks_ShouldRoundTripBasicFields()
|
public async Task GetShareUriAndResolveConfig_Socks_ShouldRoundTripBasicFields()
|
||||||
{
|
{
|
||||||
var source = CreateSocksProfile();
|
var source = CreateSocksProfile();
|
||||||
|
|
||||||
var resolved = ExportThenImport(source);
|
var resolved = await ExportThenImport(source);
|
||||||
|
|
||||||
resolved.ConfigType.Should().Be(EConfigType.SOCKS);
|
await resolved.ConfigType.Should().BeEqualTo(EConfigType.SOCKS);
|
||||||
resolved.Remarks.Should().Be(source.Remarks);
|
await resolved.Remarks.Should().BeEqualTo(source.Remarks);
|
||||||
resolved.Address.Should().Be(source.Address);
|
await resolved.Address.Should().BeEqualTo(source.Address);
|
||||||
resolved.Port.Should().Be(source.Port);
|
await resolved.Port.Should().BeEqualTo(source.Port);
|
||||||
resolved.Username.Should().Be(source.Username);
|
await resolved.Username.Should().BeEqualTo(source.Username);
|
||||||
resolved.Password.Should().Be(source.Password);
|
await resolved.Password.Should().BeEqualTo(source.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ResolveConfig_UnsupportedProtocol_ShouldReturnNull()
|
public async Task ResolveConfig_UnsupportedProtocol_ShouldReturnNull()
|
||||||
{
|
{
|
||||||
var resolved = FmtHandler.ResolveConfig("not-a-share-uri", out var msg);
|
var resolved = FmtHandler.ResolveConfig("not-a-share-uri", out var msg);
|
||||||
|
|
||||||
resolved.Should().BeNull();
|
await resolved.Should().BeNull();
|
||||||
msg.Should().NotBeNullOrWhiteSpace();
|
await msg.Should().NotBeNull();
|
||||||
|
await msg.Should().NotBeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUri_UnsupportedConfigType_ShouldReturnNull()
|
public async Task GetShareUri_UnsupportedConfigType_ShouldReturnNull()
|
||||||
{
|
{
|
||||||
var item = new ProfileItem { ConfigType = EConfigType.PolicyGroup, Remarks = "group", };
|
var item = new ProfileItem { ConfigType = EConfigType.PolicyGroup, Remarks = "group", };
|
||||||
|
|
||||||
var uri = FmtHandler.GetShareUri(item);
|
var uri = FmtHandler.GetShareUri(item);
|
||||||
|
|
||||||
uri.Should().BeNull();
|
await uri.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ProfileItem ExportThenImport(ProfileItem source)
|
private static async Task<ProfileItem> ExportThenImport(ProfileItem source)
|
||||||
{
|
{
|
||||||
var uri = FmtHandler.GetShareUri(source);
|
var uri = FmtHandler.GetShareUri(source);
|
||||||
|
|
||||||
uri.Should().NotBeNullOrWhiteSpace();
|
await uri.Should().NotBeNull();
|
||||||
uri!.StartsWith(Global.ProtocolShares[source.ConfigType], StringComparison.OrdinalIgnoreCase).Should()
|
await uri.Should().NotBeEmpty();
|
||||||
|
await uri!.StartsWith(Global.ProtocolShares[source.ConfigType], StringComparison.OrdinalIgnoreCase).Should()
|
||||||
.BeTrue();
|
.BeTrue();
|
||||||
|
|
||||||
var resolved = FmtHandler.ResolveConfig(uri, out var msg);
|
var resolved = FmtHandler.ResolveConfig(uri, out var msg);
|
||||||
|
|
||||||
resolved.Should().NotBeNull($"uri: {uri}, msg: {msg}");
|
await resolved.Should().NotBeNull().Because($"uri: {uri}, msg: {msg}");
|
||||||
return resolved!;
|
return resolved!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,27 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Handler.Fmt;
|
|
||||||
using ServiceLib.Models.Dto;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Fmt;
|
namespace ServiceLib.Tests.Fmt;
|
||||||
|
|
||||||
public class HyRealmTests
|
public class HyRealmTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void TryParse_ShouldParseValidRealm()
|
public async Task TryParse_ShouldParseValidRealm()
|
||||||
{
|
{
|
||||||
var str = "realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478";
|
var str = "realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478";
|
||||||
var result = HyRealm.TryParse(str, out var realm);
|
var result = HyRealm.TryParse(str, out var realm);
|
||||||
result.Should().BeTrue();
|
await result.Should().BeTrue();
|
||||||
realm.Should().NotBeNull();
|
await realm.Should().NotBeNull();
|
||||||
|
|
||||||
realm.IsHttp.Should().BeFalse();
|
await realm.IsHttp.Should().BeFalse();
|
||||||
realm.Token.Should().Be("public");
|
await realm.Token.Should().BeEqualTo("public");
|
||||||
realm.RendezvousHost.Should().Be("realm.hy2.io");
|
await realm.RendezvousHost.Should().BeEqualTo("realm.hy2.io");
|
||||||
realm.RendezvousPort.Should().Be(443);
|
await realm.RendezvousPort.Should().BeEqualTo(443);
|
||||||
realm.RealmName.Should().Be("57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
await realm.RealmName.Should().BeEqualTo("57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||||
realm.StunList.Should().HaveCount(2);
|
await realm.StunList.Should().HaveCount(2);
|
||||||
realm.StunList.Should().Contain("example.stun:3478");
|
await realm.StunList.Should().Contain("example.stun:3478");
|
||||||
realm.StunList.Should().Contain("example2.stun:3478");
|
await realm.StunList.Should().Contain("example2.stun:3478");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ToUri_ShouldGenerateValidUri()
|
public async Task ToUri_ShouldGenerateValidUri()
|
||||||
{
|
{
|
||||||
var realm = new HyRealm(
|
var realm = new HyRealm(
|
||||||
IsHttp: false,
|
IsHttp: false,
|
||||||
@@ -37,32 +32,32 @@ public class HyRealmTests
|
|||||||
StunList: ["example.stun:3478", "example2.stun:3478"]
|
StunList: ["example.stun:3478", "example2.stun:3478"]
|
||||||
);
|
);
|
||||||
var uri = realm.ToUri();
|
var uri = realm.ToUri();
|
||||||
uri.Should().Contain("realm://public@realm.hy2.io");
|
await uri.Should().Contain("realm://public@realm.hy2.io");
|
||||||
uri.Should().Contain("/57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
await uri.Should().Contain("/57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||||
uri.Should().Contain("stun=example.stun:3478");
|
await uri.Should().Contain("stun=example.stun:3478");
|
||||||
uri.Should().Contain("stun=example2.stun:3478");
|
await uri.Should().Contain("stun=example2.stun:3478");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void GetShareUriAndResolveConfig_Hy2Realm_ShouldRoundTripBasicFields()
|
public async Task GetShareUriAndResolveConfig_Hy2Realm_ShouldRoundTripBasicFields()
|
||||||
{
|
{
|
||||||
var str = "hysteria2+realm://mytoken@rendezvous.example.com/my-cabin-1f3a8c2e9b?auth=your_password&insecure=1&pinSHA256=deadbeef#remark";
|
var str = "hysteria2+realm://mytoken@rendezvous.example.com/my-cabin-1f3a8c2e9b?auth=your_password&insecure=1&pinSHA256=deadbeef#remark";
|
||||||
var resolved = Hysteria2Fmt.ResolveRealm(str, out var msg);
|
var resolved = Hysteria2Fmt.ResolveRealm(str, out var msg);
|
||||||
resolved.Should().NotBeNull();
|
await resolved.Should().NotBeNull();
|
||||||
resolved.Password.Should().Be("your_password");
|
await resolved.Password.Should().BeEqualTo("your_password");
|
||||||
var result = HyRealm.TryParse(resolved.GetProtocolExtra().Hy2RealmUrl, out var realm);
|
var result = HyRealm.TryParse(resolved.GetProtocolExtra().Hy2RealmUrl, out var realm);
|
||||||
result.Should().BeTrue();
|
await result.Should().BeTrue();
|
||||||
realm.Should().NotBeNull();
|
await realm.Should().NotBeNull();
|
||||||
realm.Token.Should().Be("mytoken");
|
await realm.Token.Should().BeEqualTo("mytoken");
|
||||||
|
|
||||||
// To uri
|
// To uri
|
||||||
var uri = Hysteria2Fmt.ToUri(resolved);
|
var uri = Hysteria2Fmt.ToUri(resolved);
|
||||||
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
await uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
||||||
uri.Should().EndWith("#remark");
|
await uri.Should().EndWith("#remark");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ToServerUrl_ShouldIncludeSchemeForSingbox()
|
public async Task ToServerUrl_ShouldIncludeSchemeForSingbox()
|
||||||
{
|
{
|
||||||
var realm = new HyRealm(
|
var realm = new HyRealm(
|
||||||
IsHttp: false,
|
IsHttp: false,
|
||||||
@@ -73,19 +68,19 @@ public class HyRealmTests
|
|||||||
StunList: ["turn.cloudflare.com:3478"]
|
StunList: ["turn.cloudflare.com:3478"]
|
||||||
);
|
);
|
||||||
|
|
||||||
realm.ToServerUrl().Should().Be("https://realm.hy2.io:443");
|
await realm.ToServerUrl().Should().BeEqualTo("https://realm.hy2.io:443");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ResolveRealm_Issue9635_ShouldProduceHttpsServerUrl()
|
public async Task ResolveRealm_Issue9635_ShouldProduceHttpsServerUrl()
|
||||||
{
|
{
|
||||||
var str = "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
var str = "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||||
var resolved = Hysteria2Fmt.ResolveRealm(str, out _);
|
var resolved = Hysteria2Fmt.ResolveRealm(str, out _);
|
||||||
resolved.Should().NotBeNull();
|
await resolved.Should().NotBeNull();
|
||||||
|
|
||||||
HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue();
|
await HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue();
|
||||||
realm!.ToServerUrl().Should().StartWith("https://");
|
await realm!.ToServerUrl().Should().StartWith("https://");
|
||||||
realm.ToServerUrl().Should().Contain("realm.hy2.io");
|
await realm.ToServerUrl().Should().Contain("realm.hy2.io");
|
||||||
realm.StunList.Should().Contain("turn.cloudflare.com:3478");
|
await realm.StunList.Should().Contain("turn.cloudflare.com:3478");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Handler.Fmt;
|
|
||||||
using ServiceLib.Tests.CoreConfig;
|
using ServiceLib.Tests.CoreConfig;
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Fmt;
|
namespace ServiceLib.Tests.Fmt;
|
||||||
|
|
||||||
public class InnerFmtTests
|
public class InnerFmtTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void ToUriAndResolve_ShouldRoundTripPolicyGroupReferences()
|
public async Task ToUriAndResolve_ShouldRoundTripPolicyGroupReferences()
|
||||||
{
|
{
|
||||||
var childA = CoreConfigTestFactory.CreateSocksNode(ECoreType.Xray, "child-a", "child-a");
|
var childA = CoreConfigTestFactory.CreateSocksNode(ECoreType.Xray, "child-a", "child-a");
|
||||||
var childB = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "child-b", "child-b");
|
var childB = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "child-b", "child-b");
|
||||||
@@ -19,19 +15,20 @@ public class InnerFmtTests
|
|||||||
|
|
||||||
var uri = InnerFmt.ToUri([group, childA, childB]);
|
var uri = InnerFmt.ToUri([group, childA, childB]);
|
||||||
|
|
||||||
uri.Should().NotBeNullOrWhiteSpace();
|
await uri.Should().NotBeNull();
|
||||||
|
await uri.Should().NotBeEmpty();
|
||||||
|
|
||||||
var resolved = InnerFmt.Resolve(uri!, "sub-123");
|
var resolved = InnerFmt.Resolve(uri!, "sub-123");
|
||||||
|
|
||||||
resolved.Should().NotBeNull();
|
await resolved.Should().NotBeNull();
|
||||||
resolved.Should().HaveCount(3);
|
await resolved.Should().HaveCount(3);
|
||||||
|
|
||||||
var resolvedGroup = resolved!.Single(x => x.Remarks == group.Remarks);
|
var resolvedGroup = resolved!.Single(x => x.Remarks == group.Remarks);
|
||||||
var resolvedChildA = resolved.Single(x => x.Remarks == childA.Remarks);
|
var resolvedChildA = resolved.Single(x => x.Remarks == childA.Remarks);
|
||||||
var resolvedChildB = resolved.Single(x => x.Remarks == childB.Remarks);
|
var resolvedChildB = resolved.Single(x => x.Remarks == childB.Remarks);
|
||||||
|
|
||||||
resolvedGroup.ConfigType.Should().Be(EConfigType.PolicyGroup);
|
await resolvedGroup.ConfigType.Should().BeEqualTo(EConfigType.PolicyGroup);
|
||||||
resolvedGroup.GetProtocolExtra().SubChildItems.Should().Be("sub-123");
|
await resolvedGroup.GetProtocolExtra().SubChildItems.Should().BeEqualTo("sub-123");
|
||||||
resolvedGroup.GetProtocolExtra().ChildItems.Should().Be($"{resolvedChildA.IndexId},{resolvedChildB.IndexId}");
|
await resolvedGroup.GetProtocolExtra().ChildItems.Should().BeEqualTo($"{resolvedChildA.IndexId},{resolvedChildB.IndexId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Handler.Fmt;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Fmt;
|
namespace ServiceLib.Tests.Fmt;
|
||||||
|
|
||||||
public class WireguardFmtTests
|
public class WireguardFmtTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Test]
|
||||||
public void ResolveConfig_ShouldParsePeersAndIgnoreInlineComments()
|
public async Task ResolveConfig_ShouldParsePeersAndIgnoreInlineComments()
|
||||||
{
|
{
|
||||||
const string config =
|
const string config =
|
||||||
"""
|
"""
|
||||||
@@ -29,19 +25,19 @@ public class WireguardFmtTests
|
|||||||
|
|
||||||
var resolved = WireguardFmt.ResolveConfig(config);
|
var resolved = WireguardFmt.ResolveConfig(config);
|
||||||
|
|
||||||
resolved.Should().NotBeNull();
|
await resolved.Should().NotBeNull();
|
||||||
resolved.Should().HaveCount(2);
|
await resolved.Should().HaveCount(2);
|
||||||
|
|
||||||
var first = resolved![0];
|
var first = resolved![0];
|
||||||
first.Address.Should().Be("2001:db8::1");
|
await first.Address.Should().BeEqualTo("2001:db8::1");
|
||||||
first.Port.Should().Be(51820);
|
await first.Port.Should().BeEqualTo(51820);
|
||||||
first.Password.Should().Be("interface-private-key");
|
await first.Password.Should().BeEqualTo("interface-private-key");
|
||||||
first.GetProtocolExtra().WgReserved.Should().Be("1, 2, 3");
|
await first.GetProtocolExtra().WgReserved.Should().BeEqualTo("1, 2, 3");
|
||||||
first.GetProtocolExtra().WgInterfaceAddress.Should().Be("10.0.0.2/32, fd00::2/128");
|
await first.GetProtocolExtra().WgInterfaceAddress.Should().BeEqualTo("10.0.0.2/32, fd00::2/128");
|
||||||
first.GetProtocolExtra().WgMtu.Should().Be(1420);
|
await first.GetProtocolExtra().WgMtu.Should().BeEqualTo(1420);
|
||||||
|
|
||||||
var second = resolved[1];
|
var second = resolved[1];
|
||||||
second.Address.Should().Be("example.com");
|
await second.Address.Should().BeEqualTo("example.com");
|
||||||
second.Port.Should().Be(12345);
|
await second.Port.Should().BeEqualTo(12345);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,39 @@
|
|||||||
using AwesomeAssertions;
|
|
||||||
using ServiceLib.Enums;
|
|
||||||
using ServiceLib.Manager;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Manager;
|
namespace ServiceLib.Tests.Manager;
|
||||||
|
|
||||||
public class CoreManagerTests
|
public class CoreManagerTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Test]
|
||||||
[InlineData(ECoreType.sing_box)]
|
[Arguments(ECoreType.sing_box)]
|
||||||
[InlineData(ECoreType.mihomo)]
|
[Arguments(ECoreType.mihomo)]
|
||||||
[InlineData(ECoreType.Xray)]
|
[Arguments(ECoreType.Xray)]
|
||||||
public void ShouldRunAsSudo_TunLaunchOnNonWindows_RequiresElevation(ECoreType coreType)
|
public async Task ShouldRunAsSudo_TunLaunchOnNonWindows_RequiresElevation(ECoreType coreType)
|
||||||
{
|
{
|
||||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeTrue();
|
await CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ShouldRunAsSudo_NonTunLaunch_ShouldNotElevate()
|
public async Task ShouldRunAsSudo_NonTunLaunch_ShouldNotElevate()
|
||||||
{
|
{
|
||||||
// Regression guard for the macOS TUN failure: the elevation decision must follow
|
// 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
|
// 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
|
// disabled must never elevate, and a launch whose snapshot has TUN enabled must
|
||||||
// elevate regardless of later changes to the live config.
|
// elevate regardless of later changes to the live config.
|
||||||
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.sing_box, isNonWindows: true).Should().BeFalse();
|
await CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.sing_box, isNonWindows: true).Should().BeFalse();
|
||||||
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.Xray, isNonWindows: true).Should().BeFalse();
|
await CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.Xray, isNonWindows: true).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Test]
|
||||||
public void ShouldRunAsSudo_OnWindows_ShouldNotElevate()
|
public async Task ShouldRunAsSudo_OnWindows_ShouldNotElevate()
|
||||||
{
|
{
|
||||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, ECoreType.sing_box, isNonWindows: false).Should().BeFalse();
|
await CoreManager.ShouldRunAsSudo(isTunLaunch: true, ECoreType.sing_box, isNonWindows: false).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Test]
|
||||||
[InlineData(ECoreType.v2fly)]
|
[Arguments(ECoreType.v2fly)]
|
||||||
[InlineData(ECoreType.hysteria)]
|
[Arguments(ECoreType.hysteria)]
|
||||||
[InlineData(null)]
|
[Arguments(null)]
|
||||||
public void ShouldRunAsSudo_UnsupportedCoreType_ShouldNotElevate(ECoreType? coreType)
|
public async Task ShouldRunAsSudo_UnsupportedCoreType_ShouldNotElevate(ECoreType? coreType)
|
||||||
{
|
{
|
||||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeFalse();
|
await CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeFalse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AwesomeAssertions" />
|
<PackageReference Include="TUnit" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
<PackageReference Include="TUnit.Assertions.Should" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio">
|
</ItemGroup>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="xunit.v3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ServiceLib\ServiceLib.csproj" />
|
<ProjectReference Include="..\ServiceLib\ServiceLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="TUnit.Assertions.Should" />
|
||||||
|
<Using Include="TUnit.Assertions.Should.Extensions" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
@@ -138,7 +139,10 @@ public static class Extension
|
|||||||
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
||||||
this Interaction<TInput, TOutput> interaction,
|
this Interaction<TInput, TOutput> interaction,
|
||||||
TInput input,
|
TInput input,
|
||||||
TOutput defaultValue = default!)
|
TOutput defaultValue = default!,
|
||||||
|
[CallerMemberName] string memberName = "",
|
||||||
|
[CallerFilePath] string filePath = "",
|
||||||
|
[CallerLineNumber] int lineNumber = 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -146,12 +150,14 @@ public static class Extension
|
|||||||
}
|
}
|
||||||
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog($"Unhandled interaction exception for input: {input}", ex);
|
var title = $"Unhandled interaction exception in {memberName} at {filePath}:{lineNumber}";
|
||||||
|
Logging.SaveLog(title, ex);
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog($"Exception occurred while handling interaction for input: {input}", ex);
|
var title = $"Exception occurred while handling interaction in {memberName} at {filePath}:{lineNumber}, input: {input}";
|
||||||
|
Logging.SaveLog(title, ex);
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ public sealed class EventChannel<T>
|
|||||||
private readonly Signal<T> _signal = new();
|
private readonly Signal<T> _signal = new();
|
||||||
private readonly Lock _gate = new();
|
private readonly Lock _gate = new();
|
||||||
private readonly IObservable<T> _observable;
|
private readonly IObservable<T> _observable;
|
||||||
|
|
||||||
public EventChannel()
|
public EventChannel()
|
||||||
{
|
{
|
||||||
_observable = _signal.Synchronize(_gate);
|
_observable = _signal.Synchronize(_gate);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Builder;
|
namespace ServiceLib.Handler.Builder;
|
||||||
|
|
||||||
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
||||||
@@ -205,7 +203,7 @@ public class CoreConfigContextBuilder
|
|||||||
Context = preSocksResult.Context with
|
Context = preSocksResult.Context with
|
||||||
{
|
{
|
||||||
ProtectDomainList =
|
ProtectDomainList =
|
||||||
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
[.. nodeContext.ProtectDomainList, .. preSocksResult.Context.ProtectDomainList],
|
||||||
ProtectCoreTypeList = protectCoreTypeList,
|
ProtectCoreTypeList = protectCoreTypeList,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -386,7 +384,7 @@ public class CoreConfigContextBuilder
|
|||||||
{
|
{
|
||||||
var echQuerySni = node.Sni;
|
var echQuerySni = node.Sni;
|
||||||
if (node.StreamSecurity == Global.StreamSecurity
|
if (node.StreamSecurity == Global.StreamSecurity
|
||||||
&& node.EchConfigList?.Contains("://") == true)
|
&& node.EchConfigList.Contains("://"))
|
||||||
{
|
{
|
||||||
var idx = node.EchConfigList.IndexOf('+');
|
var idx = node.EchConfigList.IndexOf('+');
|
||||||
echQuerySni = idx > 0 ? node.EchConfigList[..idx] : node.Sni;
|
echQuerySni = idx > 0 ? node.EchConfigList[..idx] : node.Sni;
|
||||||
|
|||||||
@@ -115,10 +115,10 @@ public class NodeValidator
|
|||||||
|
|
||||||
if (item.GetNetwork() is nameof(ETransport.ws)
|
if (item.GetNetwork() is nameof(ETransport.ws)
|
||||||
&& item.EchConfigList.IsNullOrEmpty()
|
&& item.EchConfigList.IsNullOrEmpty()
|
||||||
&& item.GetAlpn()?.FirstOrDefault() == "h3")
|
&& item.GetAlpn()?.FirstOrDefault() is "h3" or "h2")
|
||||||
{
|
{
|
||||||
v.Warning(
|
v.Warning(
|
||||||
"WebSocket but ALPN is set to h3, the core may ignore the ALPN setting or cause unexpected issues.");
|
$"WebSocket but ALPN is set to {item.Alpn}, the core may ignore the ALPN setting or cause unexpected issues.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS & Security
|
// TLS & Security
|
||||||
@@ -131,14 +131,6 @@ public class NodeValidator
|
|||||||
isCertProvided = false;
|
isCertProvided = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for deprecated allowInsecure property when TLS is enabled
|
|
||||||
if (item.GetAllowInsecure()
|
|
||||||
&& item.Cert.IsNullOrEmpty()
|
|
||||||
&& item.CertSha.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
v.Warning(ResUI.MsgAllowInsecureDeprecated);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((coreType == ECoreType.Xray
|
if ((coreType == ECoreType.Xray
|
||||||
&& item.GetAllowInsecure()
|
&& item.GetAllowInsecure()
|
||||||
&& !isCertProvided
|
&& !isCertProvided
|
||||||
@@ -147,6 +139,10 @@ public class NodeValidator
|
|||||||
&& item.GetAllowInsecure()
|
&& item.GetAllowInsecure()
|
||||||
&& !isCertProvided))
|
&& !isCertProvided))
|
||||||
{
|
{
|
||||||
|
if (coreType == ECoreType.Xray)
|
||||||
|
{
|
||||||
|
v.Warning(ResUI.MsgAllowInsecureDeprecated);
|
||||||
|
}
|
||||||
v.Warning(ResUI.MsgInsecureConfiguration);
|
v.Warning(ResUI.MsgInsecureConfiguration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ public static class ConfigHandler
|
|||||||
config.ConstItem ??= new ConstItem();
|
config.ConstItem ??= new ConstItem();
|
||||||
|
|
||||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||||
|
config.SimpleDNSItem.BlockAAAAQuery ??= false;
|
||||||
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
||||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||||
@@ -1797,21 +1798,18 @@ public static class ConfigHandler
|
|||||||
{
|
{
|
||||||
ECoreType.Xray => V2rayFmt.ResolveToCustom(strData, subRemarks),
|
ECoreType.Xray => V2rayFmt.ResolveToCustom(strData, subRemarks),
|
||||||
ECoreType.sing_box => SingboxFmt.ResolveToCustom(strData, subRemarks),
|
ECoreType.sing_box => SingboxFmt.ResolveToCustom(strData, subRemarks),
|
||||||
_ => null
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lstProfiles is not null)
|
if ((lstProfiles?.Count ?? 0) == 0)
|
||||||
{
|
{
|
||||||
if (lstProfiles.Count == 0)
|
return -1;
|
||||||
{
|
}
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
return count;
|
return count;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await SaveCustomRawFileServer(config, strData, subid, isSub, subItem, customCoreType);
|
return await SaveCustomRawFileServer(config, strData, subid, isSub, subItem, customCoreType);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Fmt;
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
public class BaseFmt
|
public class BaseFmt
|
||||||
|
|||||||
@@ -166,15 +166,19 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
}
|
}
|
||||||
if (item.CertSha.IsNullOrEmpty())
|
if (item.CertSha.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
var pinSHA256 = GetQueryDecoded(query, "pinSHA256");
|
||||||
// NOTE:
|
item.CertSha = pinSHA256;
|
||||||
// To accommodate Xray changes,
|
if (!pinSHA256.IsNullOrEmpty())
|
||||||
// some providers issue self-signed cert links with `insecure = false` and a certificate fingerprint,
|
{
|
||||||
// breaking interoperability between Xray, official Hysteria 2 client, and sing-box.
|
// NOTE:
|
||||||
// Since this won't compromise the overall security model,
|
// To accommodate Xray changes,
|
||||||
// `insecure = true` is automatically set when a fingerprint is detected,
|
// some providers issue self-signed cert links with `insecure = false` and a certificate fingerprint,
|
||||||
// and the value is restored when generating configurations.
|
// breaking interoperability between Xray, official Hysteria 2 client, and sing-box.
|
||||||
item.AllowInsecure = Global.StringTrue;
|
// Since this won't compromise the overall security model,
|
||||||
|
// `insecure = true` is automatically set when a fingerprint is detected,
|
||||||
|
// and the value is restored when generating configurations.
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item.EchConfigList = GetQueryDecoded(query, "ech");
|
item.EchConfigList = GetQueryDecoded(query, "ech");
|
||||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ public class SingboxFmt : BaseFmt
|
|||||||
if (!isOutbound)
|
if (!isOutbound)
|
||||||
{
|
{
|
||||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||||
profileList.Add(fullProfile);
|
|
||||||
if (fullProfile is not null)
|
if (fullProfile is not null)
|
||||||
{
|
{
|
||||||
|
profileList.Add(fullProfile);
|
||||||
return profileList;
|
return profileList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ public class V2rayFmt : BaseFmt
|
|||||||
if (!isOutbound)
|
if (!isOutbound)
|
||||||
{
|
{
|
||||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||||
profileList.Add(fullProfile);
|
|
||||||
if (fullProfile is not null)
|
if (fullProfile is not null)
|
||||||
{
|
{
|
||||||
|
profileList.Add(fullProfile);
|
||||||
return profileList;
|
return profileList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ public class DownloaderHelper
|
|||||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||||
using StreamReader reader = new(stream);
|
using StreamReader reader = new(stream);
|
||||||
|
|
||||||
downloadOpt = null;
|
|
||||||
|
|
||||||
return await reader.ReadToEndAsync(cts.Token);
|
return await reader.ReadToEndAsync(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,72 +126,150 @@ public class DownloaderHelper
|
|||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||||
|
|
||||||
downloadOpt = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress<double> progress, int timeout)
|
public async Task DownloadFileAsync(IWebProxy? webProxy, FileDownloadRequest request, Action<FileDownloadState> onProgress, TimeSpan connectTimeout, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (url.IsNullOrEmpty())
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
if (request.FilePath.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(url));
|
throw new ArgumentNullException(nameof(request.FilePath));
|
||||||
}
|
}
|
||||||
if (fileName.IsNullOrEmpty())
|
if (File.Exists(request.FilePath))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(fileName));
|
File.Delete(request.FilePath);
|
||||||
}
|
|
||||||
if (File.Exists(fileName))
|
|
||||||
{
|
|
||||||
File.Delete(fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
var state = new FileDownloadState
|
||||||
|
{
|
||||||
|
Request = request,
|
||||||
|
};
|
||||||
|
|
||||||
var requestConfiguration = new RequestConfiguration()
|
var requestConfiguration = new RequestConfiguration()
|
||||||
{
|
{
|
||||||
ConnectTimeout = connectTimeout * 1000,
|
ConnectTimeout = (int)connectTimeout.TotalMilliseconds,
|
||||||
Proxy = webProxy
|
Proxy = webProxy,
|
||||||
};
|
};
|
||||||
var downloadOpt = new DownloadConfiguration()
|
var downloadOpt = new DownloadConfiguration()
|
||||||
{
|
{
|
||||||
BlockTimeout = timeout * 1000,
|
ChunkCount = 100,
|
||||||
MaxTryAgainOnFailure = 2,
|
MinimumChunkSize = 8 * 1024 * 1024, // 8 MB
|
||||||
|
MinimumSizeOfChunking = 8 * 1024 * 1024, // 8 MB
|
||||||
|
ParallelDownload = true,
|
||||||
|
ParallelCount = 4,
|
||||||
|
|
||||||
RequestConfiguration = requestConfiguration,
|
RequestConfiguration = requestConfiguration,
|
||||||
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||||
};
|
};
|
||||||
|
|
||||||
var progressPercentage = 0;
|
|
||||||
var hasValue = false;
|
|
||||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||||
downloader.DownloadStarted += (sender, value) => progress?.Report(0);
|
downloader.DownloadStarted += (sender, value) =>
|
||||||
|
{
|
||||||
|
state = state with
|
||||||
|
{
|
||||||
|
TotalBytes = value.TotalBytesToReceive,
|
||||||
|
};
|
||||||
|
onProgress.Invoke(state);
|
||||||
|
};
|
||||||
downloader.DownloadProgressChanged += (sender, value) =>
|
downloader.DownloadProgressChanged += (sender, value) =>
|
||||||
{
|
{
|
||||||
hasValue = true;
|
state = state with
|
||||||
var percent = (int)value.ProgressPercentage;// Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100);
|
|
||||||
if (progressPercentage != percent && percent % 10 == 0)
|
|
||||||
{
|
{
|
||||||
progressPercentage = percent;
|
DownloadedBytes = value.ReceivedBytesSize,
|
||||||
progress.Report(percent);
|
TotalBytes = value.TotalBytesToReceive,
|
||||||
}
|
SpeedBytesPerSecond = value.BytesPerSecondSpeed,
|
||||||
|
};
|
||||||
|
onProgress.Invoke(state);
|
||||||
};
|
};
|
||||||
downloader.DownloadFileCompleted += (sender, value) =>
|
downloader.DownloadFileCompleted += (sender, value) =>
|
||||||
{
|
{
|
||||||
if (progress != null)
|
state = state with
|
||||||
{
|
{
|
||||||
if (hasValue && value.Error == null)
|
Completed = true,
|
||||||
{
|
Error = value.Error,
|
||||||
progress.Report(101);
|
};
|
||||||
}
|
onProgress.Invoke(state);
|
||||||
else if (value.Error != null)
|
|
||||||
{
|
|
||||||
throw value.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using var cts = new CancellationTokenSource();
|
await downloader.DownloadFileTaskAsync(request.FileUrl, request.FilePath, cancellationToken);
|
||||||
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token);
|
}
|
||||||
|
|
||||||
downloadOpt = null;
|
public async Task DownloadSmallFilesAsync(IWebProxy? webProxy, List<FileDownloadRequest> requests, Action<ReadOnlyMemory<FileDownloadState>> onProgress, TimeSpan connectTimeout, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (requests is not { Count: > 0 })
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(requests));
|
||||||
|
}
|
||||||
|
|
||||||
|
var states = new FileDownloadState[requests.Count];
|
||||||
|
for (var i = 0; i < requests.Count; i++)
|
||||||
|
{
|
||||||
|
states[i] = new FileDownloadState
|
||||||
|
{
|
||||||
|
Request = requests[i],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var readOnlyStates = new ReadOnlyMemory<FileDownloadState>(states);
|
||||||
|
|
||||||
|
var requestConfiguration = new RequestConfiguration()
|
||||||
|
{
|
||||||
|
ConnectTimeout = (int)connectTimeout.TotalMilliseconds,
|
||||||
|
Proxy = webProxy,
|
||||||
|
|
||||||
|
KeepAlive = true,
|
||||||
|
};
|
||||||
|
using var socketsHttpHandler = GetSocketsHttpHandler(requestConfiguration);
|
||||||
|
|
||||||
|
var parallelOptions = new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = 4,
|
||||||
|
//CancellationToken = cancellationToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
await Parallel.ForEachAsync(Enumerable.Range(0, requests.Count), parallelOptions, async (index, parallelCancellationToken) =>
|
||||||
|
{
|
||||||
|
var request = requests[index];
|
||||||
|
var downloadOpt = new DownloadConfiguration()
|
||||||
|
{
|
||||||
|
RequestConfiguration = requestConfiguration,
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
CustomHttpMessageHandlerFactory = () => socketsHttpHandler,
|
||||||
|
};
|
||||||
|
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||||
|
downloader.DownloadStarted += (sender, value) =>
|
||||||
|
{
|
||||||
|
states[index] = states[index] with
|
||||||
|
{
|
||||||
|
DownloadedBytes = 0,
|
||||||
|
TotalBytes = value.TotalBytesToReceive,
|
||||||
|
SpeedBytesPerSecond = 0,
|
||||||
|
Completed = false,
|
||||||
|
};
|
||||||
|
onProgress.Invoke(readOnlyStates);
|
||||||
|
};
|
||||||
|
downloader.DownloadProgressChanged += (sender, value) =>
|
||||||
|
{
|
||||||
|
states[index] = states[index] with
|
||||||
|
{
|
||||||
|
DownloadedBytes = value.ReceivedBytesSize,
|
||||||
|
TotalBytes = value.TotalBytesToReceive,
|
||||||
|
SpeedBytesPerSecond = value.BytesPerSecondSpeed,
|
||||||
|
Completed = false,
|
||||||
|
};
|
||||||
|
onProgress.Invoke(readOnlyStates);
|
||||||
|
};
|
||||||
|
downloader.DownloadFileCompleted += (sender, value) =>
|
||||||
|
{
|
||||||
|
var newState = states[index] with { Completed = true };
|
||||||
|
if (value.Error != null)
|
||||||
|
{
|
||||||
|
newState = newState with { Error = value.Error };
|
||||||
|
}
|
||||||
|
states[index] = newState;
|
||||||
|
onProgress.Invoke(readOnlyStates);
|
||||||
|
};
|
||||||
|
await downloader.DownloadFileTaskAsync(request.FileUrl, request.FilePath, parallelCancellationToken);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/SocketClient.cs#L45
|
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/SocketClient.cs#L45
|
||||||
@@ -213,7 +289,7 @@ public class DownloaderHelper
|
|||||||
PooledConnectionIdleTimeout = config.KeepAliveTimeout,
|
PooledConnectionIdleTimeout = config.KeepAliveTimeout,
|
||||||
PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
|
PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
|
||||||
EnableMultipleHttp2Connections = true,
|
EnableMultipleHttp2Connections = true,
|
||||||
ConnectTimeout = TimeSpan.FromMilliseconds(config.ConnectTimeout)
|
ConnectTimeout = TimeSpan.FromMilliseconds(config.ConnectTimeout),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up the SslClientAuthenticationOptions for custom certificate validation
|
// Set up the SslClientAuthenticationOptions for custom certificate validation
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ public class SimpleDNSItem
|
|||||||
public bool? GlobalFakeIp { get; set; }
|
public bool? GlobalFakeIp { get; set; }
|
||||||
public string? FakeIPRange { get; set; }
|
public string? FakeIPRange { get; set; }
|
||||||
public bool? BlockBindingQuery { get; set; }
|
public bool? BlockBindingQuery { get; set; }
|
||||||
|
public bool? BlockAAAAQuery { get; set; }
|
||||||
public string? DirectDNS { get; set; }
|
public string? DirectDNS { get; set; }
|
||||||
public string? RemoteDNS { get; set; }
|
public string? RemoteDNS { get; set; }
|
||||||
public string? BootstrapDNS { get; set; }
|
public string? BootstrapDNS { get; set; }
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ public class Dns4Ray
|
|||||||
public List<object> servers { get; set; }
|
public List<object> servers { get; set; }
|
||||||
public bool? serveStale { get; set; }
|
public bool? serveStale { get; set; }
|
||||||
public bool? enableParallelQuery { get; set; }
|
public bool? enableParallelQuery { get; set; }
|
||||||
|
public string? queryStrategy { get; set; }
|
||||||
public string? tag { get; set; }
|
public string? tag { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
v2rayN/ServiceLib/Models/Dto/FileDownloadState.cs
Normal file
22
v2rayN/ServiceLib/Models/Dto/FileDownloadState.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace ServiceLib.Models.Dto;
|
||||||
|
|
||||||
|
public record FileDownloadState
|
||||||
|
{
|
||||||
|
public required FileDownloadRequest Request { get; init; }
|
||||||
|
public long DownloadedBytes { get; init; } = 0;
|
||||||
|
public long TotalBytes { get; init; } = 0;
|
||||||
|
public double SpeedBytesPerSecond { get; init; } = 0;
|
||||||
|
public bool Completed { get; init; } = false;
|
||||||
|
|
||||||
|
public Exception? Error { get; init; }
|
||||||
|
public bool IsFailed => Error != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FileDownloadRequest
|
||||||
|
{
|
||||||
|
public required string FileUrl { get; init; }
|
||||||
|
public required string FilePath { get; init; }
|
||||||
|
public string? DisplayFileName { get; init; }
|
||||||
|
|
||||||
|
public string FileName => DisplayFileName ?? Path.GetFileName(FilePath);
|
||||||
|
}
|
||||||
29
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
29
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -2769,6 +2769,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Block AAAA Queries 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbBlockAAAAQueries {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbBlockAAAAQueries", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Block IPv6 queries when enabled 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbBlockAAAAQueriesTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbBlockAAAAQueriesTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。
|
/// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2779,7 +2797,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Block ECH and HTTP/3 availability checks when enabled 的本地化字符串。
|
/// 查找类似 Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbBlockSVCBHTTPSQueriesTips {
|
public static string TbBlockSVCBHTTPSQueriesTips {
|
||||||
get {
|
get {
|
||||||
@@ -5031,6 +5049,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Xray Only 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbXrayOnly {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbXrayOnly", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
|
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1456,7 +1456,7 @@
|
|||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Block ECH and HTTP/3 availability checks when enabled</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Please fill in the correct config template</value>
|
<value>Please fill in the correct config template</value>
|
||||||
|
|||||||
@@ -1453,7 +1453,7 @@
|
|||||||
<value>DNS personnalisé activé ; la configuration de cette page sera ignorée</value>
|
<value>DNS personnalisé activé ; la configuration de cette page sera ignorée</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Une fois activé, bloque les requêtes ECH et de disponibilité HTTP/3</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Veuillez saisir un modèle de configuration valide</value>
|
<value>Veuillez saisir un modèle de configuration valide</value>
|
||||||
|
|||||||
@@ -1456,7 +1456,7 @@
|
|||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Block ECH and HTTP/3 availability checks when enabled</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Please fill in the correct config template</value>
|
<value>Please fill in the correct config template</value>
|
||||||
|
|||||||
@@ -1456,7 +1456,7 @@
|
|||||||
<value>DNS kustom diaktifkan, pengaturan halaman ini tidak berlaku</value>
|
<value>DNS kustom diaktifkan, pengaturan halaman ini tidak berlaku</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Blokir pemeriksaan ketersediaan ECH dan HTTP/3 saat diaktifkan</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Isi template konfigurasi yang benar</value>
|
<value>Isi template konfigurasi yang benar</value>
|
||||||
|
|||||||
@@ -1462,7 +1462,7 @@
|
|||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Block ECH and HTTP/3 availability checks when enabled</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Please fill in the correct config template</value>
|
<value>Please fill in the correct config template</value>
|
||||||
@@ -1860,4 +1860,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="LvCustomCoreType" xml:space="preserve">
|
<data name="LvCustomCoreType" xml:space="preserve">
|
||||||
<value>Custom config core</value>
|
<value>Custom config core</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="TbXrayOnly" xml:space="preserve">
|
||||||
|
<value>Xray Only</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbBlockAAAAQueries" xml:space="preserve">
|
||||||
|
<value>Block AAAA Queries</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbBlockAAAAQueriesTips" xml:space="preserve">
|
||||||
|
<value>Block IPv6 queries when enabled</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -1462,7 +1462,7 @@
|
|||||||
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
|
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>При включении блокирует проверки доступности ECH и HTTP/3</value>
|
<value>Block ECH and HTTP/3 availability checks when enabled. Always enabled in Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
|
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
|
||||||
|
|||||||
@@ -1459,7 +1459,7 @@
|
|||||||
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>开启后将阻止 ECH 和 HTTP/3 可用性查询</value>
|
<value>开启后将阻止 ECH 和 HTTP/3 可用性查询。Xray 固定开启</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>请填写正确的配置模板</value>
|
<value>请填写正确的配置模板</value>
|
||||||
@@ -1861,4 +1861,13 @@
|
|||||||
<data name="LvCustomCoreType" xml:space="preserve">
|
<data name="LvCustomCoreType" xml:space="preserve">
|
||||||
<value>自定义配置核心</value>
|
<value>自定义配置核心</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbXrayOnly" xml:space="preserve">
|
||||||
|
<value>仅 Xray</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbBlockAAAAQueries" xml:space="preserve">
|
||||||
|
<value>阻止 AAAA 查询</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbBlockAAAAQueriesTips" xml:space="preserve">
|
||||||
|
<value>开启后将阻止 IPv6 查询</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1459,7 +1459,7 @@
|
|||||||
<value>已啟用自訂 DNS,此頁面的設定將不會生效</value>
|
<value>已啟用自訂 DNS,此頁面的設定將不會生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>啟用後將封鎖 ECH 與 HTTP/3 可用性查詢</value>
|
<value>啟用後將封鎖 ECH 與 HTTP/3 可用性查詢。Xray 固定啟用</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>請填寫正確的設定範本</value>
|
<value>請填寫正確的設定範本</value>
|
||||||
@@ -1861,4 +1861,4 @@
|
|||||||
<data name="LvCustomCoreType" xml:space="preserve">
|
<data name="LvCustomCoreType" xml:space="preserve">
|
||||||
<value>自訂設定核心</value>
|
<value>自訂設定核心</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using YamlDotNet.Core;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
namespace ServiceLib.Services.CoreConfig;
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -127,7 +130,27 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
|
|||||||
Logging.SaveLog($"{_tag}-Mixin", ex);
|
Logging.SaveLog($"{_tag}-Mixin", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mihomo parses plain values such as 815458e4 as floats, so quote REALITY short IDs.
|
||||||
|
var originalRealityShortIds = new List<(Dictionary<object, object> RealityOptions, string ShortId)>();
|
||||||
|
if (fileContent.GetValueOrDefault("proxies") is List<object> proxies)
|
||||||
|
{
|
||||||
|
foreach (var proxy in proxies.OfType<Dictionary<object, object>>())
|
||||||
|
{
|
||||||
|
if (proxy.GetValueOrDefault("reality-opts") is Dictionary<object, object> realityOptions
|
||||||
|
&& realityOptions.GetValueOrDefault("short-id") is string shortId
|
||||||
|
&& !shortId.StartsWith(tagYamlStr2, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
originalRealityShortIds.Add((realityOptions, shortId));
|
||||||
|
realityOptions["short-id"] = new YamlScalarNode(shortId) { Style = ScalarStyle.DoubleQuoted };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
|
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
|
||||||
|
foreach (var (realityOptions, shortId) in originalRealityShortIds)
|
||||||
|
{
|
||||||
|
realityOptions["short-id"] = shortId;
|
||||||
|
}
|
||||||
await File.WriteAllTextAsync(fileName, txtFileNew);
|
await File.WriteAllTextAsync(fileName, txtFileNew);
|
||||||
//check again
|
//check again
|
||||||
if (!File.Exists(fileName))
|
if (!File.Exists(fileName))
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ public partial class CoreConfigSingboxService
|
|||||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
// Process endpoints
|
// Process endpoints
|
||||||
if (fullConfigTemplateNode["endpoints"] is JsonArray { Count: > 0 } coreConfigEndpointsNode)
|
var coreConfigEndpointsNode = coreConfigNode?["endpoints"] as JsonArray ?? [];
|
||||||
|
if (coreConfigEndpointsNode is { Count: > 0 })
|
||||||
{
|
{
|
||||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
|
var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
|
||||||
foreach (var endpoint in coreConfigEndpointsNode)
|
foreach (var endpoint in coreConfigEndpointsNode)
|
||||||
|
|||||||
@@ -282,13 +282,23 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
query_type = [1, 28], // A and AAAA
|
query_type = [1, 28], // A and AAAA
|
||||||
},
|
},
|
||||||
fakeipFilterRule
|
fakeipFilterRule,
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
_coreConfig.dns.rules.Add(rule4Fake);
|
_coreConfig.dns.rules.Add(rule4Fake);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simpleDnsItem.BlockAAAAQuery == true)
|
||||||
|
{
|
||||||
|
_coreConfig.dns.rules.Add(new()
|
||||||
|
{
|
||||||
|
query_type = [28],
|
||||||
|
action = "predefined",
|
||||||
|
rcode = "NOERROR",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var routing = context.RoutingItem;
|
var routing = context.RoutingItem;
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ public partial class CoreConfigV2rayService
|
|||||||
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
|
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
|
||||||
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
|
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
|
||||||
|
|
||||||
|
if (simpleDnsItem.BlockAAAAQuery == true)
|
||||||
|
{
|
||||||
|
dnsItem.queryStrategy = "UseIPv4";
|
||||||
|
}
|
||||||
|
|
||||||
// DNS routing
|
// DNS routing
|
||||||
var directDnsTags = dnsItem.servers
|
var directDnsTags = dnsItem.servers
|
||||||
.Select(server =>
|
.Select(server =>
|
||||||
|
|||||||
@@ -42,21 +42,88 @@ public class DownloadService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads a file and reports progress through events.
|
/// Downloads a file and reports progress through events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
|
public async Task DownloadFileAsync(FileDownloadRequest request, bool blProxy, TimeSpan connectTimeout)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}"));
|
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {request.FileUrl}"));
|
||||||
|
|
||||||
var progress = new Progress<double>();
|
|
||||||
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%"));
|
|
||||||
|
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
|
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
|
||||||
url,
|
request,
|
||||||
fileName,
|
OnProgress,
|
||||||
progress,
|
connectTimeout);
|
||||||
downloadTimeout);
|
|
||||||
|
void OnProgress(FileDownloadState state)
|
||||||
|
{
|
||||||
|
UpdateCompleted?.Invoke(this, new UpdateResult(state.Completed, $"{Utils.HumanFy((long)state.SpeedBytesPerSecond / 1024)}/s | {Utils.HumanFy(state.DownloadedBytes / 1024)}/{Utils.HumanFy(state.TotalBytes / 1024)}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
|
||||||
|
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadSmallFilesAsync(List<FileDownloadRequest> requests, bool blProxy, TimeSpan connectTimeout)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} 0/{requests.Count}"));
|
||||||
|
|
||||||
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
|
await DownloaderHelper.Instance.DownloadSmallFilesAsync(webProxy,
|
||||||
|
requests,
|
||||||
|
OnProgress,
|
||||||
|
connectTimeout);
|
||||||
|
|
||||||
|
void OnProgress(ReadOnlyMemory<FileDownloadState> states)
|
||||||
|
{
|
||||||
|
var span = states.Span;
|
||||||
|
var completedCount = 0;
|
||||||
|
var downloadingStates = new List<FileDownloadState>();
|
||||||
|
foreach (ref readonly var item in span)
|
||||||
|
{
|
||||||
|
if (item.Completed)
|
||||||
|
{
|
||||||
|
completedCount++;
|
||||||
|
}
|
||||||
|
else if (item.TotalBytes > 0)
|
||||||
|
{
|
||||||
|
downloadingStates.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var totalSpeed = downloadingStates.Sum(x => x.SpeedBytesPerSecond);
|
||||||
|
var totalDownloadedBytes = downloadingStates.Sum(x => x.DownloadedBytes);
|
||||||
|
var totalTotalBytes = downloadingStates.Sum(x => x.TotalBytes);
|
||||||
|
var downloadingFileName = string.Join(", ", downloadingStates.Select(x => x.Request.FileName));
|
||||||
|
var allCompleted = completedCount == span.Length;
|
||||||
|
if (allCompleted)
|
||||||
|
{
|
||||||
|
// check and throw errors if any
|
||||||
|
FileDownloadState? failedState = null;
|
||||||
|
foreach (ref readonly var item in span)
|
||||||
|
{
|
||||||
|
if (!item.IsFailed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
failedState = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (failedState?.Error != null)
|
||||||
|
{
|
||||||
|
throw failedState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateCompleted?.Invoke(this, new UpdateResult(allCompleted, $"{completedCount}/{span.Length} | {Utils.HumanFy((long)totalSpeed / 1024)}/s {Utils.HumanFy(totalDownloadedBytes / 1024)}/{Utils.HumanFy(totalTotalBytes / 1024)} {downloadingFileName}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
||||||
await UpdateFunc(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Url.ToString();
|
url = result.Url!;
|
||||||
fileName = Utils.GetTempPath(Utils.GetGuid());
|
fileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
|
await downloadHandle.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, true, TimeSpan.FromSeconds(_timeout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -86,10 +86,10 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
||||||
await UpdateFunc(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Url.ToString();
|
url = result.Url!;
|
||||||
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
||||||
fileName = Utils.GetTempPath(Utils.GetGuid() + ext);
|
fileName = Utils.GetTempPath(Utils.GetGuid() + ext);
|
||||||
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
|
await downloadHandle.DownloadFileAsync(new() { FileUrl = url, FilePath = fileName }, true, TimeSpan.FromSeconds(_timeout));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -139,9 +139,13 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
|
|
||||||
public async Task UpdateGeoFileAll()
|
public async Task UpdateGeoFileAll()
|
||||||
{
|
{
|
||||||
await UpdateGeoFiles();
|
var requests = new List<FileDownloadRequest>();
|
||||||
await UpdateOtherFiles();
|
requests.AddRange(GetGeoFilesRequest());
|
||||||
await UpdateSrsFileAll();
|
requests.AddRange(GetOtherFilesRequest());
|
||||||
|
requests.AddRange(await GetSrsFileAllRequest());
|
||||||
|
// NOTE: srs files are more small, so we reverse the order to ensure a good download experience for the user.
|
||||||
|
requests.Reverse();
|
||||||
|
await DownloadGeoFiles(requests);
|
||||||
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,41 +367,53 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
|
|
||||||
#region Geo private
|
#region Geo private
|
||||||
|
|
||||||
private async Task UpdateGeoFiles()
|
private List<FileDownloadRequest> GetGeoFilesRequest()
|
||||||
{
|
{
|
||||||
var geoUrl = string.IsNullOrEmpty(_config?.ConstItem.GeoSourceUrl)
|
var geoUrl = string.IsNullOrEmpty(_config?.ConstItem.GeoSourceUrl)
|
||||||
? Global.GeoUrl
|
? Global.GeoUrl
|
||||||
: _config.ConstItem.GeoSourceUrl;
|
: _config.ConstItem.GeoSourceUrl;
|
||||||
|
|
||||||
List<string> files = ["geosite", "geoip"];
|
List<string> files = ["geosite", "geoip"];
|
||||||
foreach (var geoName in files)
|
return
|
||||||
{
|
[
|
||||||
var fileName = $"{geoName}.dat";
|
.. from geoName in files
|
||||||
var targetPath = Utils.GetBinPath($"{fileName}");
|
let fileName = $"{geoName}.dat"
|
||||||
var url = string.Format(geoUrl, geoName);
|
let targetPath = Utils.GetBinPath($"{fileName}")
|
||||||
|
let url = string.Format(geoUrl, geoName)
|
||||||
await DownloadGeoFile(url, fileName, targetPath);
|
select new FileDownloadRequest()
|
||||||
}
|
{
|
||||||
|
FileUrl = url,
|
||||||
|
FilePath = targetPath,
|
||||||
|
DisplayFileName = fileName,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateOtherFiles()
|
private List<FileDownloadRequest> GetOtherFilesRequest()
|
||||||
{
|
{
|
||||||
//If it is not in China area, no update is required
|
//If it is not in China area, no update is required
|
||||||
if (_config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
if (_config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var url in Global.OtherGeoUrls)
|
return
|
||||||
{
|
[
|
||||||
var fileName = Path.GetFileName(url);
|
.. Global.OtherGeoUrls.Select(url =>
|
||||||
var targetPath = Utils.GetBinPath($"{fileName}");
|
{
|
||||||
|
var fileName = Path.GetFileName(url);
|
||||||
await DownloadGeoFile(url, fileName, targetPath);
|
var targetPath = Utils.GetBinPath($"{fileName}");
|
||||||
}
|
return new FileDownloadRequest()
|
||||||
|
{
|
||||||
|
FileUrl = url,
|
||||||
|
FilePath = targetPath,
|
||||||
|
DisplayFileName = fileName,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFileAll()
|
private async Task<List<FileDownloadRequest>> GetSrsFileAllRequest()
|
||||||
{
|
{
|
||||||
var geoipFiles = new List<string>();
|
var geoipFiles = new List<string>();
|
||||||
var geoSiteFiles = new List<string>();
|
var geoSiteFiles = new List<string>();
|
||||||
@@ -432,15 +448,12 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in geoipFiles.Distinct())
|
return
|
||||||
{
|
[
|
||||||
await UpdateSrsFile("geoip", item);
|
.. geoipFiles.Distinct().Select(f => (type: "geoip", file: f))
|
||||||
}
|
.Concat(geoSiteFiles.Distinct().Select(f => (type: "geosite", file: f)))
|
||||||
|
.Select(item => GetSrsFileRequest(item.type, item.file)),
|
||||||
foreach (var item in geoSiteFiles.Distinct())
|
];
|
||||||
{
|
|
||||||
await UpdateSrsFile("geosite", item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddPrefixedItems(List<string>? items, string prefix, List<string> output)
|
private void AddPrefixedItems(List<string>? items, string prefix, List<string> output)
|
||||||
@@ -500,7 +513,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFile(string type, string srsName)
|
private FileDownloadRequest GetSrsFileRequest(string type, string srsName)
|
||||||
{
|
{
|
||||||
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
|
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
|
||||||
? Global.SingboxRulesetUrl
|
? Global.SingboxRulesetUrl
|
||||||
@@ -510,33 +523,57 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
|
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
|
||||||
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
|
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
|
||||||
|
|
||||||
await DownloadGeoFile(url, fileName, targetPath);
|
return new FileDownloadRequest()
|
||||||
|
{
|
||||||
|
FileUrl = url,
|
||||||
|
FilePath = targetPath,
|
||||||
|
DisplayFileName = fileName,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadGeoFile(string url, string fileName, string targetPath)
|
private async Task DownloadGeoFiles(List<FileDownloadRequest> requests)
|
||||||
{
|
{
|
||||||
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());
|
var tmpFilePathDict = new Dictionary<string, string>();
|
||||||
|
var tmpFileRequests = new List<FileDownloadRequest>();
|
||||||
|
foreach (var request in requests)
|
||||||
|
{
|
||||||
|
var tmpFilePath = Utils.GetTempPath(Utils.GetGuid());
|
||||||
|
tmpFilePathDict[request.FilePath] = tmpFilePath;
|
||||||
|
tmpFileRequests.Add(request with
|
||||||
|
{
|
||||||
|
FilePath = tmpFilePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
DownloadService downloadHandle = new();
|
DownloadService downloadHandle = new();
|
||||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_ = UpdateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
//_ = UpdateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
||||||
|
|
||||||
try
|
foreach (var request in requests)
|
||||||
{
|
{
|
||||||
if (File.Exists(tmpFileName))
|
try
|
||||||
{
|
{
|
||||||
File.Copy(tmpFileName, targetPath, true);
|
//if (File.Exists(tmpFileName))
|
||||||
|
//{
|
||||||
|
// File.Copy(tmpFileName, targetPath, true);
|
||||||
|
|
||||||
File.Delete(tmpFileName);
|
// File.Delete(tmpFileName);
|
||||||
//await UpdateFunc(true, "");
|
// //await UpdateFunc(true, "");
|
||||||
|
//}
|
||||||
|
var tmpFileName = tmpFilePathDict[request.FilePath];
|
||||||
|
if (File.Exists(tmpFileName))
|
||||||
|
{
|
||||||
|
File.Copy(tmpFileName, request.FilePath, true);
|
||||||
|
File.Delete(tmpFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_ = UpdateFunc(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_ = UpdateFunc(false, ex.Message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -549,7 +586,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
|||||||
_ = UpdateFunc(false, args.GetException().Message);
|
_ = UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout);
|
await downloadHandle.DownloadSmallFilesAsync(tmpFileRequests, true, TimeSpan.FromSeconds(_timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Geo private
|
#endregion Geo private
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
[Reactive] public partial bool FakeIP { get; set; }
|
[Reactive] public partial bool FakeIP { get; set; }
|
||||||
[Reactive] public partial string FakeIPRange { get; set; }
|
[Reactive] public partial string FakeIPRange { get; set; }
|
||||||
[Reactive] public partial bool BlockBindingQuery { get; set; }
|
[Reactive] public partial bool BlockBindingQuery { get; set; }
|
||||||
|
[Reactive] public partial bool BlockAAAAQuery { get; set; }
|
||||||
[Reactive] public partial string DirectDNS { get; set; }
|
[Reactive] public partial string DirectDNS { get; set; }
|
||||||
[Reactive] public partial string RemoteDNS { get; set; }
|
[Reactive] public partial string RemoteDNS { get; set; }
|
||||||
[Reactive] public partial string BootstrapDNS { get; set; }
|
[Reactive] public partial string BootstrapDNS { get; set; }
|
||||||
@@ -74,6 +75,7 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
FakeIP = item.FakeIP ?? false;
|
FakeIP = item.FakeIP ?? false;
|
||||||
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
||||||
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
||||||
|
BlockAAAAQuery = item.BlockAAAAQuery ?? false;
|
||||||
DirectDNS = item.DirectDNS ?? string.Empty;
|
DirectDNS = item.DirectDNS ?? string.Empty;
|
||||||
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
||||||
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
||||||
@@ -108,6 +110,7 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
_config.SimpleDNSItem.FakeIP = FakeIP;
|
_config.SimpleDNSItem.FakeIP = FakeIP;
|
||||||
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
||||||
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
||||||
|
_config.SimpleDNSItem.BlockAAAAQuery = BlockAAAAQuery;
|
||||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace ServiceLib.ViewModels;
|
|||||||
public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
||||||
|
|
||||||
#region private prop
|
#region private prop
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public partial string CustomCoreType { get; set; }
|
public partial string CustomCoreType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public partial string PrevProfile { get; set; }
|
public partial string PrevProfile { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal static class MacAppUtils
|
|||||||
public static bool IsWindowMiniaturized(Window window)
|
public static bool IsWindowMiniaturized(Window window)
|
||||||
=> window.TryGetPlatformHandle() is IMacOSTopLevelPlatformHandle { NSWindow: not 0 } handle
|
=> window.TryGetPlatformHandle() is IMacOSTopLevelPlatformHandle { NSWindow: not 0 } handle
|
||||||
&& objc_msgSend_bool(handle.NSWindow, sel_registerName("isMiniaturized"));
|
&& objc_msgSend_bool(handle.NSWindow, sel_registerName("isMiniaturized"));
|
||||||
|
|
||||||
[DllImport(LibObjC)]
|
[DllImport(LibObjC)]
|
||||||
private static extern nint objc_getClass(string name);
|
private static extern nint objc_getClass(string name);
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ internal static class MacAppUtils
|
|||||||
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
|
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
|
||||||
[return: MarshalAs(UnmanagedType.I1)]
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
private static extern bool objc_msgSend_bool(nint receiver, nint selector);
|
private static extern bool objc_msgSend_bool(nint receiver, nint selector);
|
||||||
|
|
||||||
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
|
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
|
||||||
private static extern void objc_msgSend(nint receiver, nint selector, nint argument);
|
private static extern void objc_msgSend(nint receiver, nint selector, nint argument);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using ServiceLib.Models.Entities;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using v2rayN.Desktop.Manager;
|
using v2rayN.Desktop.Manager;
|
||||||
using v2rayN.Desktop.ViewModels;
|
using v2rayN.Desktop.ViewModels;
|
||||||
|
|||||||
@@ -1265,8 +1265,10 @@
|
|||||||
Width="400"
|
Width="400"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
|
TextWrapping="Wrap">
|
||||||
TextWrapping="Wrap" />
|
<Run Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
|
||||||
|
<Run FontWeight="Bold" Text="{x:Static resx:ResUI.TbXrayOnly}" />
|
||||||
|
</TextBlock>
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtCertSha256Pinning"
|
x:Name="txtCertSha256Pinning"
|
||||||
Width="400"
|
Width="400"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
using v2rayN.Desktop.Common;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
x:Name="gridAdvancedDNSSettings"
|
x:Name="gridAdvancedDNSSettings"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
ColumnDefinitions="Auto,Auto,*"
|
ColumnDefinitions="Auto,Auto,*"
|
||||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*">
|
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="txtAdvancedDNSSettingsInvalid"
|
x:Name="txtAdvancedDNSSettingsInvalid"
|
||||||
@@ -338,17 +338,38 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbBlockAAAAQueries}" />
|
||||||
|
<ToggleSwitch
|
||||||
|
x:Name="togBlockAAAAQuery"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbBlockAAAAQueriesTips}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbDirectExpectedIPs"
|
x:Name="cmbDirectExpectedIPs"
|
||||||
Grid.Row="5"
|
Grid.Row="6"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="200"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsEditable="True" />
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="5"
|
Grid.Row="6"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -356,7 +377,7 @@
|
|||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="6"
|
Grid.Row="7"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
@@ -365,7 +386,7 @@
|
|||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtHosts"
|
x:Name="txtHosts"
|
||||||
Grid.Row="7"
|
Grid.Row="8"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
|||||||
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.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.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.BlockAAAAQuery, v => v.togBlockAAAAQuery.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).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.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
lstProfiles.SetValue(DragDrop.AllowDropProperty, true);
|
lstProfiles.SetValue(DragDrop.AllowDropProperty, true);
|
||||||
|
|
||||||
lstProfiles.AddHandler(PointerPressedEvent, LstProfiles_PointerPressed, RoutingStrategies.Bubble, true);
|
lstProfiles.AddHandler(PointerPressedEvent, LstProfiles_PointerPressed, RoutingStrategies.Bubble, true);
|
||||||
|
lstProfiles.AddHandler(PointerMovedEvent, LstProfiles_PointerMoved, RoutingStrategies.Bubble, true);
|
||||||
|
lstProfiles.AddHandler(PointerReleasedEvent, LstProfiles_PointerReleased, RoutingStrategies.Bubble, true);
|
||||||
lstProfiles.AddHandler(DragDrop.DragOverEvent, LstProfiles_DragOver, RoutingStrategies.Bubble);
|
lstProfiles.AddHandler(DragDrop.DragOverEvent, LstProfiles_DragOver, RoutingStrategies.Bubble);
|
||||||
lstProfiles.AddHandler(DragDrop.DropEvent, LstProfiles_Drop, RoutingStrategies.Bubble);
|
lstProfiles.AddHandler(DragDrop.DropEvent, LstProfiles_Drop, RoutingStrategies.Bubble);
|
||||||
}
|
}
|
||||||
@@ -435,31 +437,66 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
|
|
||||||
#region Drag and Drop
|
#region Drag and Drop
|
||||||
|
|
||||||
private static readonly DataFormat<object> LstProfilesRowFormat =
|
private static readonly DataFormat<ProfileItemModel> LstProfilesRowFormat =
|
||||||
DataFormat.CreateInProcessFormat<object>("LstProfilesRow");
|
DataFormat.CreateInProcessFormat<ProfileItemModel>("LstProfilesRow");
|
||||||
|
private (Point, PointerPressedEventArgs)? _dragStartPoint;
|
||||||
|
|
||||||
private async void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
|
private void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
var properties = e.GetCurrentPoint(this).Properties;
|
||||||
|
if (properties.IsLeftButtonPressed)
|
||||||
|
{
|
||||||
|
_dragStartPoint = (e.GetPosition(this), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LstProfiles_PointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (e.Source is not Visual visualSource)
|
if (_dragStartPoint == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var properties = e.GetCurrentPoint(this).Properties;
|
||||||
|
if (!properties.IsLeftButtonPressed)
|
||||||
|
{
|
||||||
|
_dragStartPoint = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentPoint = e.GetPosition(this);
|
||||||
|
var startPoint = _dragStartPoint.Value.Item1;
|
||||||
|
var delta = startPoint - currentPoint;
|
||||||
|
|
||||||
|
var threshold = new Vector(4, 4);
|
||||||
|
|
||||||
|
if (!(Math.Abs(delta.X) >= threshold.X) && !(Math.Abs(delta.Y) >= threshold.Y))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dragStartEventArgs = _dragStartPoint.Value.Item2;
|
||||||
|
_dragStartPoint = null;
|
||||||
|
|
||||||
|
if (e.Source is not Visual visualSource)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
var row = visualSource.FindAncestorOfType<DataGridRow>(true);
|
var row = visualSource.FindAncestorOfType<DataGridRow>(true);
|
||||||
if (row?.DataContext == null)
|
if (row?.DataContext == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.GetCurrentPoint(row).Properties.IsLeftButtonPressed)
|
e.Handled = true;
|
||||||
{
|
|
||||||
var dragData = new DataTransfer();
|
var dragData = new DataTransfer();
|
||||||
var item = DataTransferItem.Create(LstProfilesRowFormat, row.DataContext);
|
var item = DataTransferItem.Create(LstProfilesRowFormat, row.DataContext as ProfileItemModel);
|
||||||
dragData.Add(item);
|
|
||||||
await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Move);
|
dragData.Add(item);
|
||||||
}
|
|
||||||
|
await DragDrop.DoDragDropAsync(dragStartEventArgs, dragData, DragDropEffects.Move);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -467,6 +504,11 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_PointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
_dragStartPoint = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void LstProfiles_DragOver(object? sender, DragEventArgs e)
|
private void LstProfiles_DragOver(object? sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (!e.DataTransfer.Contains(LstProfilesRowFormat))
|
if (!e.DataTransfer.Contains(LstProfilesRowFormat))
|
||||||
|
|||||||
@@ -1593,8 +1593,10 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
|
TextWrapping="Wrap">
|
||||||
TextWrapping="Wrap" />
|
<Run Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
|
||||||
|
<Run FontWeight="Bold" Text="{x:Static resx:ResUI.TbXrayOnly}" />
|
||||||
|
</TextBlock>
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtCertSha256Pinning"
|
x:Name="txtCertSha256Pinning"
|
||||||
Width="400"
|
Width="400"
|
||||||
|
|||||||
@@ -278,6 +278,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -387,17 +388,39 @@
|
|||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbBlockAAAAQueries}" />
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="togBlockAAAAQuery"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
HorizontalAlignment="Left" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbBlockAAAAQueriesTips}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbDirectExpectedIPs"
|
x:Name="cmbDirectExpectedIPs"
|
||||||
Grid.Row="5"
|
Grid.Row="6"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="200"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
Style="{StaticResource DefComboBox}" />
|
Style="{StaticResource DefComboBox}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="5"
|
Grid.Row="6"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -406,7 +429,7 @@
|
|||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="6"
|
Grid.Row="7"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
@@ -415,7 +438,7 @@
|
|||||||
Text="{x:Static resx:ResUI.TbDNSHostsConfig}" />
|
Text="{x:Static resx:ResUI.TbDNSHostsConfig}" />
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtHosts"
|
x:Name="txtHosts"
|
||||||
Grid.Row="7"
|
Grid.Row="8"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public partial class DNSSettingWindow
|
|||||||
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.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.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.BlockAAAAQuery, v => v.togBlockAAAAQuery.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).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.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
|
||||||
|
|||||||
Reference in New Issue
Block a user