mirror of
https://github.com/2dust/v2rayN.git
synced 2026-04-14 19:45:45 +00:00
Compare commits
4 commits
53041906b3
...
6c8f22ab86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c8f22ab86 | ||
|
|
49f65579aa | ||
|
|
a69e407bda | ||
|
|
96e5c11fc7 |
26 changed files with 471 additions and 70 deletions
6
.github/workflows/build-linux.yml
vendored
6
.github/workflows/build-linux.yml
vendored
|
|
@ -47,7 +47,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-linux
|
name: v2rayN-linux
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -116,7 +116,7 @@ jobs:
|
||||||
ls -R "$GITHUB_WORKSPACE/dist/deb" || true
|
ls -R "$GITHUB_WORKSPACE/dist/deb" || true
|
||||||
|
|
||||||
- name: Upload DEB artifacts
|
- name: Upload DEB artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-deb
|
name: v2rayN-deb
|
||||||
path: dist/deb/**/*.deb
|
path: dist/deb/**/*.deb
|
||||||
|
|
@ -229,7 +229,7 @@ jobs:
|
||||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||||
|
|
||||||
- name: Upload RPM artifacts
|
- name: Upload RPM artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-rpm
|
name: v2rayN-rpm
|
||||||
path: dist/rpm/**/*.rpm
|
path: dist/rpm/**/*.rpm
|
||||||
|
|
|
||||||
2
.github/workflows/build-osx.yml
vendored
2
.github/workflows/build-osx.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-macos
|
name: v2rayN-macos
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
2
.github/workflows/build-windows-desktop.yml
vendored
2
.github/workflows/build-windows-desktop.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows-desktop
|
name: v2rayN-windows-desktop
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.0
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows
|
name: v2rayN-windows
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
||||||
<PackageVersion Include="Downloader" Version="5.1.0" />
|
<PackageVersion Include="Downloader" Version="5.1.0" />
|
||||||
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.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.1" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.1" />
|
||||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
||||||
|
|
@ -26,6 +27,8 @@
|
||||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
|
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
|
||||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
|
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
228
v2rayN/ServiceLib.Tests/CoreConfigV2rayServiceTests.cs
Normal file
228
v2rayN/ServiceLib.Tests/CoreConfigV2rayServiceTests.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using ServiceLib;
|
||||||
|
using ServiceLib.Enums;
|
||||||
|
using ServiceLib.Models;
|
||||||
|
using ServiceLib.Services.CoreConfig;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ServiceLib.Tests;
|
||||||
|
|
||||||
|
public class CoreConfigV2rayServiceTests
|
||||||
|
{
|
||||||
|
private const string SendThrough = "198.51.100.10";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_OnlyAppliesSendThroughToRemoteProxyOutbounds()
|
||||||
|
{
|
||||||
|
var node = CreateProxyNode("proxy-1", "198.51.100.1", 443);
|
||||||
|
var service = new CoreConfigV2rayService(CreateContext(node));
|
||||||
|
|
||||||
|
var result = service.GenerateClientConfigContent();
|
||||||
|
|
||||||
|
Assert.True(result.Success);
|
||||||
|
|
||||||
|
var outbounds = GetOutbounds(result.Data?.ToString());
|
||||||
|
var proxyOutbound = outbounds.Single(outbound => outbound["tag"]!.GetValue<string>() == Global.ProxyTag);
|
||||||
|
var directOutbound = outbounds.Single(outbound => outbound["tag"]!.GetValue<string>() == Global.DirectTag);
|
||||||
|
var blockOutbound = outbounds.Single(outbound => outbound["tag"]!.GetValue<string>() == Global.BlockTag);
|
||||||
|
|
||||||
|
Assert.Equal(SendThrough, proxyOutbound["sendThrough"]?.GetValue<string>());
|
||||||
|
Assert.Null(directOutbound["sendThrough"]);
|
||||||
|
Assert.Null(blockOutbound["sendThrough"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_OnlyAppliesSendThroughToChainExitOutbounds()
|
||||||
|
{
|
||||||
|
var exitNode = CreateProxyNode("exit", "198.51.100.2", 443);
|
||||||
|
var entryNode = CreateProxyNode("entry", "198.51.100.3", 443);
|
||||||
|
var chainNode = CreateChainNode("chain", exitNode, entryNode);
|
||||||
|
|
||||||
|
var service = new CoreConfigV2rayService(CreateContext(
|
||||||
|
chainNode,
|
||||||
|
allProxiesMap: new Dictionary<string, ProfileItem>
|
||||||
|
{
|
||||||
|
[exitNode.IndexId] = exitNode,
|
||||||
|
[entryNode.IndexId] = entryNode,
|
||||||
|
}));
|
||||||
|
|
||||||
|
var result = service.GenerateClientConfigContent();
|
||||||
|
|
||||||
|
Assert.True(result.Success);
|
||||||
|
|
||||||
|
var outbounds = GetOutbounds(result.Data?.ToString())
|
||||||
|
.Where(outbound => outbound["protocol"]?.GetValue<string>() is not ("freedom" or "blackhole" or "dns"))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var sendThroughOutbounds = outbounds
|
||||||
|
.Where(outbound => outbound["sendThrough"]?.GetValue<string>() == SendThrough)
|
||||||
|
.ToList();
|
||||||
|
var chainedOutbounds = outbounds
|
||||||
|
.Where(outbound => outbound["streamSettings"]?["sockopt"]?["dialerProxy"] is not null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.Single(sendThroughOutbounds);
|
||||||
|
Assert.All(chainedOutbounds, outbound => Assert.Null(outbound["sendThrough"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_DoesNotApplySendThroughToTunRelayLoopbackOutbound()
|
||||||
|
{
|
||||||
|
var node = CreateProxyNode("proxy-1", "198.51.100.4", 443);
|
||||||
|
var config = CreateConfig();
|
||||||
|
config.TunModeItem.EnableLegacyProtect = false;
|
||||||
|
|
||||||
|
var service = new CoreConfigV2rayService(CreateContext(
|
||||||
|
node,
|
||||||
|
config,
|
||||||
|
isTunEnabled: true,
|
||||||
|
tunProtectSsPort: 10811,
|
||||||
|
proxyRelaySsPort: 10812));
|
||||||
|
|
||||||
|
var result = service.GenerateClientConfigContent();
|
||||||
|
|
||||||
|
Assert.True(result.Success);
|
||||||
|
|
||||||
|
var outbounds = GetOutbounds(result.Data?.ToString());
|
||||||
|
Assert.DoesNotContain(outbounds, outbound => outbound["sendThrough"]?.GetValue<string>() == SendThrough);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CoreConfigContext CreateContext(
|
||||||
|
ProfileItem node,
|
||||||
|
Config? config = null,
|
||||||
|
Dictionary<string, ProfileItem>? allProxiesMap = null,
|
||||||
|
bool isTunEnabled = false,
|
||||||
|
int tunProtectSsPort = 0,
|
||||||
|
int proxyRelaySsPort = 0)
|
||||||
|
{
|
||||||
|
return new CoreConfigContext
|
||||||
|
{
|
||||||
|
Node = node,
|
||||||
|
RunCoreType = ECoreType.Xray,
|
||||||
|
AppConfig = config ?? CreateConfig(),
|
||||||
|
AllProxiesMap = allProxiesMap ?? new(),
|
||||||
|
SimpleDnsItem = new SimpleDNSItem(),
|
||||||
|
IsTunEnabled = isTunEnabled,
|
||||||
|
TunProtectSsPort = tunProtectSsPort,
|
||||||
|
ProxyRelaySsPort = proxyRelaySsPort,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Config CreateConfig()
|
||||||
|
{
|
||||||
|
return new Config
|
||||||
|
{
|
||||||
|
IndexId = string.Empty,
|
||||||
|
SubIndexId = string.Empty,
|
||||||
|
CoreBasicItem = new()
|
||||||
|
{
|
||||||
|
LogEnabled = false,
|
||||||
|
Loglevel = "warning",
|
||||||
|
MuxEnabled = false,
|
||||||
|
DefAllowInsecure = false,
|
||||||
|
DefFingerprint = Global.Fingerprints.First(),
|
||||||
|
DefUserAgent = string.Empty,
|
||||||
|
SendThrough = SendThrough,
|
||||||
|
EnableFragment = false,
|
||||||
|
EnableCacheFile4Sbox = true,
|
||||||
|
},
|
||||||
|
TunModeItem = new()
|
||||||
|
{
|
||||||
|
EnableTun = false,
|
||||||
|
AutoRoute = true,
|
||||||
|
StrictRoute = true,
|
||||||
|
Stack = string.Empty,
|
||||||
|
Mtu = 9000,
|
||||||
|
EnableIPv6Address = false,
|
||||||
|
IcmpRouting = Global.TunIcmpRoutingPolicies.First(),
|
||||||
|
EnableLegacyProtect = false,
|
||||||
|
},
|
||||||
|
KcpItem = new(),
|
||||||
|
GrpcItem = new(),
|
||||||
|
RoutingBasicItem = new()
|
||||||
|
{
|
||||||
|
DomainStrategy = Global.DomainStrategies.First(),
|
||||||
|
DomainStrategy4Singbox = Global.DomainStrategies4Sbox.First(),
|
||||||
|
RoutingIndexId = string.Empty,
|
||||||
|
},
|
||||||
|
GuiItem = new(),
|
||||||
|
MsgUIItem = new(),
|
||||||
|
UiItem = new()
|
||||||
|
{
|
||||||
|
CurrentLanguage = "en",
|
||||||
|
CurrentFontFamily = string.Empty,
|
||||||
|
MainColumnItem = [],
|
||||||
|
WindowSizeItem = [],
|
||||||
|
},
|
||||||
|
ConstItem = new(),
|
||||||
|
SpeedTestItem = new(),
|
||||||
|
Mux4RayItem = new()
|
||||||
|
{
|
||||||
|
Concurrency = 8,
|
||||||
|
XudpConcurrency = 8,
|
||||||
|
XudpProxyUDP443 = "reject",
|
||||||
|
},
|
||||||
|
Mux4SboxItem = new()
|
||||||
|
{
|
||||||
|
Protocol = string.Empty,
|
||||||
|
},
|
||||||
|
HysteriaItem = new(),
|
||||||
|
ClashUIItem = new()
|
||||||
|
{
|
||||||
|
ConnectionsColumnItem = [],
|
||||||
|
},
|
||||||
|
SystemProxyItem = new(),
|
||||||
|
WebDavItem = new(),
|
||||||
|
CheckUpdateItem = new(),
|
||||||
|
Fragment4RayItem = null,
|
||||||
|
Inbound = [new InItem
|
||||||
|
{
|
||||||
|
Protocol = EInboundProtocol.socks.ToString(),
|
||||||
|
LocalPort = 10808,
|
||||||
|
UdpEnabled = true,
|
||||||
|
SniffingEnabled = true,
|
||||||
|
RouteOnly = false,
|
||||||
|
}],
|
||||||
|
GlobalHotkeys = [],
|
||||||
|
CoreTypeItem = [],
|
||||||
|
SimpleDNSItem = new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProfileItem CreateProxyNode(string indexId, string address, int port)
|
||||||
|
{
|
||||||
|
return new ProfileItem
|
||||||
|
{
|
||||||
|
IndexId = indexId,
|
||||||
|
Remarks = indexId,
|
||||||
|
ConfigType = EConfigType.SOCKS,
|
||||||
|
CoreType = ECoreType.Xray,
|
||||||
|
Address = address,
|
||||||
|
Port = port,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProfileItem CreateChainNode(string indexId, params ProfileItem[] nodes)
|
||||||
|
{
|
||||||
|
var chainNode = new ProfileItem
|
||||||
|
{
|
||||||
|
IndexId = indexId,
|
||||||
|
Remarks = indexId,
|
||||||
|
ConfigType = EConfigType.ProxyChain,
|
||||||
|
CoreType = ECoreType.Xray,
|
||||||
|
};
|
||||||
|
chainNode.SetProtocolExtra(new ProtocolExtraItem
|
||||||
|
{
|
||||||
|
ChildItems = string.Join(',', nodes.Select(node => node.IndexId)),
|
||||||
|
});
|
||||||
|
return chainNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<JsonObject> GetOutbounds(string? json)
|
||||||
|
{
|
||||||
|
var root = JsonNode.Parse(json ?? throw new InvalidOperationException("Config JSON is missing"))?.AsObject()
|
||||||
|
?? throw new InvalidOperationException("Failed to parse config JSON");
|
||||||
|
return root["outbounds"]?.AsArray().Select(node => node!.AsObject()).ToList()
|
||||||
|
?? throw new InvalidOperationException("Config JSON does not contain outbounds");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
v2rayN/ServiceLib.Tests/ServiceLib.Tests.csproj
Normal file
21
v2rayN/ServiceLib.Tests/ServiceLib.Tests.csproj
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||||
|
<PackageReference Include="xunit" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ServiceLib\ServiceLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -522,6 +522,23 @@ public class Utils
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsIpv4(string? ip)
|
||||||
|
{
|
||||||
|
if (ip.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ip.Trim();
|
||||||
|
if (!IPAddress.TryParse(ip, out var address))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return address.AddressFamily == AddressFamily.InterNetwork
|
||||||
|
&& ip.Count(c => c == '.') == 3;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsIpAddress(string? ip)
|
public static bool IsIpAddress(string? ip)
|
||||||
{
|
{
|
||||||
if (ip.IsNullOrEmpty())
|
if (ip.IsNullOrEmpty())
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ public class Global
|
||||||
@"https://cachefly.cachefly.net/50mb.test",
|
@"https://cachefly.cachefly.net/50mb.test",
|
||||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||||
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
||||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
@"https://speed.cloudflare.com/__down?bytes=99999999",
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> SpeedPingTestUrls =
|
public static readonly List<string> SpeedPingTestUrls =
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,14 @@ public record CoreConfigContextBuilderAllResult(
|
||||||
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
|
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
|
/// The main context with TunProtectSocksPort/ProxyRelaySocksPort and ProtectDomainList merged in
|
||||||
/// from the pre-socks result (if any). Pass this to the core runner.
|
/// from the pre-socks result (if any). Pass this to the core runner.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
|
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
|
||||||
? MainResult.Context with
|
? MainResult.Context with
|
||||||
{
|
{
|
||||||
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
|
TunProtectSocksPort = PreSocksResult.Context.TunProtectSocksPort,
|
||||||
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
|
ProxyRelaySocksPort = PreSocksResult.Context.ProxyRelaySocksPort,
|
||||||
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
|
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
|
||||||
}
|
}
|
||||||
: MainResult.Context;
|
: MainResult.Context;
|
||||||
|
|
@ -58,8 +58,8 @@ public class CoreConfigContextBuilder
|
||||||
IsTunEnabled = config.TunModeItem.EnableTun,
|
IsTunEnabled = config.TunModeItem.EnableTun,
|
||||||
SimpleDnsItem = config.SimpleDNSItem,
|
SimpleDnsItem = config.SimpleDNSItem,
|
||||||
ProtectDomainList = [],
|
ProtectDomainList = [],
|
||||||
TunProtectSsPort = 0,
|
TunProtectSocksPort = 0,
|
||||||
ProxyRelaySsPort = 0,
|
ProxyRelaySocksPort = 0,
|
||||||
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
|
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
|
||||||
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
||||||
};
|
};
|
||||||
|
|
@ -155,28 +155,23 @@ public class CoreConfigContextBuilder
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tunProtectSsPort = Utils.GetFreePort();
|
var tunProtectSocksPort = Utils.GetFreePort();
|
||||||
var proxyRelaySsPort = Utils.GetFreePort();
|
var proxyRelaySocksPort = Utils.GetFreePort();
|
||||||
var preItem = new ProfileItem()
|
var preItem = new ProfileItem()
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.sing_box,
|
CoreType = ECoreType.sing_box,
|
||||||
ConfigType = EConfigType.Shadowsocks,
|
ConfigType = EConfigType.SOCKS,
|
||||||
Address = Global.Loopback,
|
Address = Global.Loopback,
|
||||||
Port = proxyRelaySsPort,
|
Port = proxyRelaySocksPort,
|
||||||
Password = Global.None,
|
|
||||||
};
|
};
|
||||||
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
|
|
||||||
{
|
|
||||||
SsMethod = Global.None,
|
|
||||||
});
|
|
||||||
var preResult2 = await Build(nodeContext.AppConfig, preItem);
|
var preResult2 = await Build(nodeContext.AppConfig, preItem);
|
||||||
return preResult2 with
|
return preResult2 with
|
||||||
{
|
{
|
||||||
Context = preResult2.Context with
|
Context = preResult2.Context with
|
||||||
{
|
{
|
||||||
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
|
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
|
||||||
TunProtectSsPort = tunProtectSsPort,
|
TunProtectSocksPort = tunProtectSocksPort,
|
||||||
ProxyRelaySsPort = proxyRelaySsPort,
|
ProxyRelaySocksPort = proxyRelaySocksPort,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ public static class ConfigHandler
|
||||||
Loglevel = "warning",
|
Loglevel = "warning",
|
||||||
MuxEnabled = false,
|
MuxEnabled = false,
|
||||||
};
|
};
|
||||||
|
config.CoreBasicItem.SendThrough = config.CoreBasicItem.SendThrough?.TrimEx();
|
||||||
|
|
||||||
if (config.Inbound == null)
|
if (config.Inbound == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ public class CoreBasicItem
|
||||||
|
|
||||||
public string DefUserAgent { get; set; }
|
public string DefUserAgent { get; set; }
|
||||||
|
|
||||||
|
public string? SendThrough { get; set; }
|
||||||
|
|
||||||
public bool EnableFragment { get; set; }
|
public bool EnableFragment { get; set; }
|
||||||
|
|
||||||
public bool EnableCacheFile4Sbox { get; set; } = true;
|
public bool EnableCacheFile4Sbox { get; set; } = true;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,6 @@ public record CoreConfigContext
|
||||||
// -> tun inbound --(if routing proxy)--> relay outbound
|
// -> tun inbound --(if routing proxy)--> relay outbound
|
||||||
// -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound)
|
// -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound)
|
||||||
// -> protect inbound -> direct proxy outbound data -> internet
|
// -> protect inbound -> direct proxy outbound data -> internet
|
||||||
public int TunProtectSsPort { get; init; } = 0;
|
public int TunProtectSocksPort { get; init; } = 0;
|
||||||
public int ProxyRelaySsPort { get; init; } = 0;
|
public int ProxyRelaySocksPort { get; init; } = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ public class Outbounds4Ray
|
||||||
|
|
||||||
public string protocol { get; set; }
|
public string protocol { get; set; }
|
||||||
|
|
||||||
|
public string? sendThrough { get; set; }
|
||||||
|
|
||||||
public string? targetStrategy { get; set; }
|
public string? targetStrategy { get; set; }
|
||||||
|
|
||||||
public Outboundsettings4Ray settings { get; set; }
|
public Outboundsettings4Ray settings { get; set; }
|
||||||
|
|
|
||||||
27
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
27
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
|
@ -222,6 +222,15 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Please fill in the correct IPv4 address for SendThrough. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string FillCorrectSendThroughIPv4 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FillCorrectSendThroughIPv4", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Please enter the correct port format. 的本地化字符串。
|
/// 查找类似 Please enter the correct port format. 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -4005,6 +4014,24 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Local outbound address (SendThrough) 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsSendThrough {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsSendThrough", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Only applies to Xray. Fill in a local IPv4 address; leave empty to disable. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsSendThroughTip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsSendThroughTip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enable Log 的本地化字符串。
|
/// 查找类似 Enable Log 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1320,6 +1320,15 @@
|
||||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||||
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
|
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsSendThrough" xml:space="preserve">
|
||||||
|
<value>Local outbound address (SendThrough)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||||
|
<value>Only applies to Xray. Fill in a local IPv4 address; leave empty to disable.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||||
|
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||||
|
</data>
|
||||||
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
||||||
<value>*xhttp mode</value>
|
<value>*xhttp mode</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -1317,6 +1317,15 @@
|
||||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||||
<value>密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
|
<value>密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsSendThrough" xml:space="preserve">
|
||||||
|
<value>本地出站地址 (SendThrough)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||||
|
<value>仅对 Xray 生效,填写本机 IPv4;留空则不设置。</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||||
|
<value>请填写正确的 SendThrough IPv4 地址。</value>
|
||||||
|
</data>
|
||||||
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
||||||
<value>*XHTTP 模式</value>
|
<value>*XHTTP 模式</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -62,16 +62,23 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||||
|
|
||||||
ret.Data = ApplyFullConfigTemplate();
|
ret.Data = ApplyFullConfigTemplate();
|
||||||
if (!context.AppConfig.TunModeItem.EnableLegacyProtect
|
if (!context.AppConfig.TunModeItem.EnableLegacyProtect
|
||||||
&& context.TunProtectSsPort is > 0 and <= 65535)
|
&& context.TunProtectSocksPort is > 0 and <= 65535)
|
||||||
{
|
{
|
||||||
|
// Replace relay proxy outbound, avoid mux or other feature cause issue, and add a socks inbound for tun protect
|
||||||
|
var relayProxyIndex = _coreConfig.outbounds.FindIndex(o => o.tag == Global.ProxyTag);
|
||||||
|
_coreConfig.outbounds[relayProxyIndex] = new Outbound4Sbox()
|
||||||
|
{
|
||||||
|
type = Global.ProtocolTypes[EConfigType.SOCKS],
|
||||||
|
tag = Global.ProxyTag,
|
||||||
|
server = Global.Loopback,
|
||||||
|
server_port = context.ProxyRelaySocksPort,
|
||||||
|
};
|
||||||
var ssInbound = new
|
var ssInbound = new
|
||||||
{
|
{
|
||||||
type = "shadowsocks",
|
type = "socks",
|
||||||
tag = "tun-protect-ss",
|
tag = "tun-protect-socks",
|
||||||
listen = Global.Loopback,
|
listen = Global.Loopback,
|
||||||
listen_port = context.TunProtectSsPort,
|
listen_port = context.TunProtectSocksPort,
|
||||||
method = "none",
|
|
||||||
password = "none",
|
|
||||||
};
|
};
|
||||||
var directRule = new Rule4Sbox()
|
var directRule = new Rule4Sbox()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -345,14 +345,6 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// The synthetic TUN relay outbound talks to the local Xray shadowsocks relay.
|
|
||||||
// Xray cannot terminate sing-box h2mux, so muxing here turns local relay traffic
|
|
||||||
// into sp.mux.sing-box.arpa pseudo-destinations and breaks DNS over TUN.
|
|
||||||
if (IsTunRelayProxyOutbound())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||||
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
|
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -372,21 +364,6 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsTunRelayProxyOutbound()
|
|
||||||
{
|
|
||||||
if (!context.IsTunEnabled
|
|
||||||
|| _node.ConfigType != EConfigType.Shadowsocks
|
|
||||||
|| _node.Address != Global.Loopback
|
|
||||||
|| _node.Port != context.ProxyRelaySsPort
|
|
||||||
|| _node.Password != Global.None)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocolExtra = _node.GetProtocolExtra();
|
|
||||||
return protocolExtra.SsMethod == Global.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FillOutboundTls(Outbound4Sbox outbound)
|
private void FillOutboundTls(Outbound4Sbox outbound)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
{
|
{
|
||||||
if (!context.AppConfig.TunModeItem.EnableLegacyProtect
|
if (!context.AppConfig.TunModeItem.EnableLegacyProtect
|
||||||
&& context.IsTunEnabled
|
&& context.IsTunEnabled
|
||||||
&& context.TunProtectSsPort is > 0 and <= 65535
|
&& context.TunProtectSocksPort is > 0 and <= 65535
|
||||||
&& context.ProxyRelaySsPort is > 0 and <= 65535)
|
&& context.ProxyRelaySocksPort is > 0 and <= 65535)
|
||||||
{
|
{
|
||||||
return GenerateClientProxyRelayConfig();
|
return GenerateClientProxyRelayConfig();
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +62,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
GenDns();
|
GenDns();
|
||||||
|
|
||||||
GenStatistic();
|
GenStatistic();
|
||||||
|
ApplyOutboundSendThrough();
|
||||||
|
|
||||||
var finalRule = BuildFinalRule();
|
var finalRule = BuildFinalRule();
|
||||||
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
||||||
|
|
@ -195,6 +196,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
_coreConfig.routing.rules.Add(rule);
|
_coreConfig.routing.rules.Add(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyOutboundSendThrough();
|
||||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||||
|
|
@ -255,6 +257,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
});
|
});
|
||||||
|
|
||||||
_coreConfig.routing.rules.Add(BuildFinalRule());
|
_coreConfig.routing.rules.Add(BuildFinalRule());
|
||||||
|
ApplyOutboundSendThrough();
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
@ -309,17 +312,16 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
var protectNode = new ProfileItem()
|
var protectNode = new ProfileItem()
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.Xray,
|
CoreType = ECoreType.Xray,
|
||||||
ConfigType = EConfigType.Shadowsocks,
|
ConfigType = EConfigType.SOCKS,
|
||||||
Address = Global.Loopback,
|
Address = Global.Loopback,
|
||||||
Port = context.TunProtectSsPort,
|
Port = context.TunProtectSocksPort,
|
||||||
Password = Global.None,
|
|
||||||
};
|
};
|
||||||
protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with
|
protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with
|
||||||
{
|
{
|
||||||
SsMethod = Global.None,
|
SsMethod = Global.None,
|
||||||
});
|
});
|
||||||
|
|
||||||
const string protectTag = "tun-protect-ss";
|
const string protectTag = "tun-protect-socks";
|
||||||
foreach (var outbound in _coreConfig.outbounds
|
foreach (var outbound in _coreConfig.outbounds
|
||||||
.Where(o => o.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
.Where(o => o.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||||
{
|
{
|
||||||
|
|
@ -368,7 +370,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
|
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
|
||||||
_coreConfig.routing.rules.Add(new()
|
_coreConfig.routing.rules.Add(new()
|
||||||
{
|
{
|
||||||
inboundTag = ["proxy-relay-ss"],
|
inboundTag = ["proxy-relay-socks"],
|
||||||
outboundTag = hasBalancer ? null : Global.ProxyTag,
|
outboundTag = hasBalancer ? null : Global.ProxyTag,
|
||||||
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null,
|
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null,
|
||||||
type = "field"
|
type = "field"
|
||||||
|
|
@ -376,19 +378,19 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
|
|
||||||
//_coreConfig.inbounds.Clear();
|
//_coreConfig.inbounds.Clear();
|
||||||
|
|
||||||
|
ApplyOutboundSendThrough();
|
||||||
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
|
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
|
||||||
configNode["inbounds"]!.AsArray().Add(new
|
configNode["inbounds"]!.AsArray().Add(new
|
||||||
{
|
{
|
||||||
listen = Global.Loopback,
|
listen = Global.Loopback,
|
||||||
port = context.ProxyRelaySsPort,
|
port = context.ProxyRelaySocksPort,
|
||||||
protocol = "shadowsocks",
|
protocol = "socks",
|
||||||
settings = new
|
settings = new
|
||||||
{
|
{
|
||||||
network = "tcp,udp",
|
auth = "noauth",
|
||||||
method = Global.None,
|
udp = true,
|
||||||
password = Global.None,
|
|
||||||
},
|
},
|
||||||
tag = "proxy-relay-ss",
|
tag = "proxy-relay-socks",
|
||||||
});
|
});
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
|
@ -405,4 +407,43 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion public gen function
|
#endregion public gen function
|
||||||
|
|
||||||
|
private void ApplyOutboundSendThrough()
|
||||||
|
{
|
||||||
|
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
|
||||||
|
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||||
|
{
|
||||||
|
outbound.sendThrough = ShouldApplySendThrough(outbound, sendThrough) ? sendThrough : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldApplySendThrough(Outbounds4Ray outbound, string? sendThrough)
|
||||||
|
{
|
||||||
|
if (sendThrough.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outbound.protocol is "freedom" or "blackhole" or "dns" or "loopback")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
|
||||||
|
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
||||||
|
?? outbound.settings?.address?.ToString()
|
||||||
|
?? string.Empty;
|
||||||
|
|
||||||
|
if (outboundAddress.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !IPAddress.TryParse(outboundAddress, out var address) || !IPAddress.IsLoopback(address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
[Reactive] public bool defAllowInsecure { get; set; }
|
[Reactive] public bool defAllowInsecure { get; set; }
|
||||||
[Reactive] public string defFingerprint { get; set; }
|
[Reactive] public string defFingerprint { get; set; }
|
||||||
[Reactive] public string defUserAgent { get; set; }
|
[Reactive] public string defUserAgent { get; set; }
|
||||||
|
[Reactive] public string sendThrough { get; set; }
|
||||||
[Reactive] public string mux4SboxProtocol { get; set; }
|
[Reactive] public string mux4SboxProtocol { get; set; }
|
||||||
[Reactive] public bool enableCacheFile4Sbox { get; set; }
|
[Reactive] public bool enableCacheFile4Sbox { get; set; }
|
||||||
[Reactive] public int? hyUpMbps { get; set; }
|
[Reactive] public int? hyUpMbps { get; set; }
|
||||||
|
|
@ -154,6 +155,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure;
|
defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure;
|
||||||
defFingerprint = _config.CoreBasicItem.DefFingerprint;
|
defFingerprint = _config.CoreBasicItem.DefFingerprint;
|
||||||
defUserAgent = _config.CoreBasicItem.DefUserAgent;
|
defUserAgent = _config.CoreBasicItem.DefUserAgent;
|
||||||
|
sendThrough = _config.CoreBasicItem.SendThrough;
|
||||||
mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
|
mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
|
||||||
enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
|
enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
|
||||||
hyUpMbps = _config.HysteriaItem.UpMbps;
|
hyUpMbps = _config.HysteriaItem.UpMbps;
|
||||||
|
|
@ -297,6 +299,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var sendThroughValue = sendThrough?.TrimEx();
|
||||||
|
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||||
|
|
@ -336,6 +344,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
_config.CoreBasicItem.DefAllowInsecure = defAllowInsecure;
|
_config.CoreBasicItem.DefAllowInsecure = defAllowInsecure;
|
||||||
_config.CoreBasicItem.DefFingerprint = defFingerprint;
|
_config.CoreBasicItem.DefFingerprint = defFingerprint;
|
||||||
_config.CoreBasicItem.DefUserAgent = defUserAgent;
|
_config.CoreBasicItem.DefUserAgent = defUserAgent;
|
||||||
|
_config.CoreBasicItem.SendThrough = sendThrough?.TrimEx();
|
||||||
_config.Mux4SboxItem.Protocol = mux4SboxProtocol;
|
_config.Mux4SboxItem.Protocol = mux4SboxProtocol;
|
||||||
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox;
|
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox;
|
||||||
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0;
|
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,27 @@
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left" />
|
HorizontalAlignment="Left" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtsendThrough"
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="200"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Watermark="0.0.0.0" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbSettingsSendThroughTip}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||||
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Action", "GitHub Act
|
||||||
..\.github\workflows\winget-publish.yml = ..\.github\workflows\winget-publish.yml
|
..\.github\workflows\winget-publish.yml = ..\.github\workflows\winget-publish.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.Tests", "ServiceLib.Tests\ServiceLib.Tests.csproj", "{E0B6C5C7-ED48-42EB-947A-877779E9F555}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -58,6 +60,10 @@ Global
|
||||||
{CB3DE54F-3A26-AE02-1299-311132C32156}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{CB3DE54F-3A26-AE02-1299-311132C32156}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.Build.0 = Release|Any CPU
|
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E0B6C5C7-ED48-42EB-947A-877779E9F555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E0B6C5C7-ED48-42EB-947A-877779E9F555}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E0B6C5C7-ED48-42EB-947A-877779E9F555}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E0B6C5C7-ED48-42EB-947A-877779E9F555}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,30 @@
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
HorizontalAlignment="Left" />
|
HorizontalAlignment="Left" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtsendThrough"
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="200"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
Style="{StaticResource DefTextBox}"
|
||||||
|
materialDesign:HintAssist.Hint="0.0.0.0" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="21"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbSettingsSendThroughTip}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ public partial class OptionSettingWindow
|
||||||
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue