mirror of
https://github.com/2dust/v2rayN.git
synced 2026-04-14 11:35:44 +00:00
fix(send-through): limit sendThrough to remote egress outbounds
This commit is contained in:
parent
bb5563b02e
commit
68146a42e5
10 changed files with 292 additions and 19 deletions
|
|
@ -12,6 +12,7 @@
|
|||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
||||
<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="MaterialDesignThemes" Version="5.3.1" />
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
||||
|
|
@ -26,7 +27,9 @@
|
|||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<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="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
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>
|
||||
9
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
9
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
|
@ -4014,15 +4014,6 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 e.g. 192.168.1.10 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsSendThroughHint {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsSendThroughHint", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Local outbound address (SendThrough) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1323,9 +1323,6 @@
|
|||
<data name="TbSettingsSendThrough" xml:space="preserve">
|
||||
<value>Local outbound address (SendThrough)</value>
|
||||
</data>
|
||||
<data name="TbSettingsSendThroughHint" xml:space="preserve">
|
||||
<value>e.g. 192.168.1.10</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>
|
||||
|
|
|
|||
|
|
@ -1320,9 +1320,6 @@
|
|||
<data name="TbSettingsSendThrough" xml:space="preserve">
|
||||
<value>本地出站地址 (SendThrough)</value>
|
||||
</data>
|
||||
<data name="TbSettingsSendThroughHint" xml:space="preserve">
|
||||
<value>例如 192.168.1.10</value>
|
||||
</data>
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>仅对 Xray 生效,填写本机 IPv4;留空则不设置。</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -415,7 +415,37 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
outbound.sendThrough = sendThrough.IsNullOrEmpty() ? null : sendThrough;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@
|
|||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="{x:Static resx:ResUI.TbSettingsSendThroughHint}" />
|
||||
Watermark="0.0.0.0" />
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="2"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Action", "GitHub Act
|
|||
..\.github\workflows\winget-publish.yml = ..\.github\workflows\winget-publish.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.Tests", "ServiceLib.Tests\ServiceLib.Tests.csproj", "{E0B6C5C7-ED48-42EB-947A-877779E9F555}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@
|
|||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefTextBox}"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.TbSettingsSendThroughHint}" />
|
||||
materialDesign:HintAssist.Hint="0.0.0.0" />
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="2"
|
||||
|
|
|
|||
Loading…
Reference in a new issue