mirror of
https://github.com/2dust/v2rayN.git
synced 2026-07-26 18:02:05 +03:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a236fab27c | ||
|
|
f8e1186095 | ||
|
|
ca186e2636 | ||
|
|
1e8ba9ad9b | ||
|
|
31e32f174e | ||
|
|
bf376a8fac | ||
|
|
5a9a9b7ab1 | ||
|
|
e7a9aa66dc | ||
|
|
709b9a0958 | ||
|
|
191fe48257 | ||
|
|
48d58d482a |
4
.github/workflows/build-linux.yml
vendored
4
.github/workflows/build-linux.yml
vendored
@@ -214,7 +214,7 @@ jobs:
|
||||
QCOW2_IMAGE: debian13-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
@@ -364,7 +364,7 @@ jobs:
|
||||
QCOW2_IMAGE: fedora43-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
|
||||
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
|
||||
|
||||
- name: Setup .NET 10.0.1xx
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: 10.0.1xx
|
||||
|
||||
|
||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
case(
|
||||
inputs.target == 'macos', 'macos-latest',
|
||||
inputs.target == 'linux', 'ubuntu-24.04',
|
||||
inputs.target == 'windows', 'windows-latest',
|
||||
'ubuntu-latest'
|
||||
)
|
||||
}}
|
||||
@@ -68,15 +69,17 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: '10.0.1xx'
|
||||
|
||||
- name: Build v2rayN
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
|
||||
|
||||
- name: Build AmazTool
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "v2rayN/GlobalHotKeys"]
|
||||
path = v2rayN/GlobalHotKeys
|
||||
url = https://github.com/2dust/GlobalHotKeys
|
||||
[submodule "v2rayN/NetBridge"]
|
||||
path = v2rayN/NetBridge
|
||||
url = https://github.com/2dust/NetBridge
|
||||
|
||||
@@ -23,6 +23,13 @@ Download the latest release here:
|
||||
|
||||
[https://github.com/2dust/v2rayN/releases](https://github.com/2dust/v2rayN/releases)
|
||||
|
||||
|
||||
> [!TIP]
|
||||
> v2rayN is the desktop version. For the mobile version, please visit the v2rayNG \
|
||||
> v2rayN 是电脑版,手机版请访问 v2rayNG
|
||||
>
|
||||
> https://github.com/2dust/v2rayNG
|
||||
|
||||
---
|
||||
|
||||
## Documentation / 使用文档
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.23.0</Version>
|
||||
<Version>7.23.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
||||
<PackageVersion Include="Downloader" Version="5.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.2" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<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.0.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<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.0.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Submodule v2rayN/NetBridge deleted from e9b16cd187
@@ -1,8 +1,10 @@
|
||||
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;
|
||||
|
||||
@@ -557,4 +559,37 @@ public class CoreConfigSingboxServiceTests
|
||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
|
||||
{
|
||||
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";
|
||||
var node = Hysteria2Fmt.ResolveRealm(shareLink, out _);
|
||||
node.Should().NotBeNull();
|
||||
node!.CoreType = ECoreType.sing_box;
|
||||
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
config.CoreTypeItem =
|
||||
[
|
||||
new CoreTypeItem { ConfigType = EConfigType.Hysteria2, CoreType = ECoreType.sing_box }
|
||||
];
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box);
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
||||
|
||||
proxy.type.Should().Be("hysteria2");
|
||||
proxy.realm.Should().NotBeNull();
|
||||
proxy.realm!.server_url.Should().StartWith("https://");
|
||||
proxy.realm.server_url.Should().Contain("realm.hy2.io");
|
||||
proxy.realm.token.Should().Be("public");
|
||||
proxy.realm.realm_id.Should().Be("my-realm-id");
|
||||
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||
proxy.server.Should().BeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Handler.Fmt;
|
||||
using ServiceLib.Models.Dto;
|
||||
using Xunit;
|
||||
|
||||
namespace ServiceLib.Tests.Fmt;
|
||||
@@ -58,4 +60,32 @@ public class HyRealmTests
|
||||
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
||||
uri.Should().EndWith("#remark");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToServerUrl_ShouldIncludeSchemeForSingbox()
|
||||
{
|
||||
var realm = new HyRealm(
|
||||
IsHttp: false,
|
||||
Token: "public",
|
||||
RendezvousHost: "realm.hy2.io",
|
||||
RendezvousPort: 443,
|
||||
RealmName: "my-realm-id",
|
||||
StunList: ["turn.cloudflare.com:3478"]
|
||||
);
|
||||
|
||||
realm.ToServerUrl().Should().Be("https://realm.hy2.io:443");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 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 resolved = Hysteria2Fmt.ResolveRealm(str, out _);
|
||||
resolved.Should().NotBeNull();
|
||||
|
||||
HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue();
|
||||
realm!.ToServerUrl().Should().StartWith("https://");
|
||||
realm.ToServerUrl().Should().Contain("realm.hy2.io");
|
||||
realm.StunList.Should().Contain("turn.cloudflare.com:3478");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ public class Global
|
||||
public const string LinuxBash = "/bin/bash";
|
||||
public const string StringTrue = "true";
|
||||
public const string StringFalse = "false";
|
||||
public const int SqliteMaxBatchSize = 10000;
|
||||
|
||||
public const string SingboxDirectDNSTag = "direct_dns";
|
||||
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||
|
||||
@@ -160,11 +160,7 @@ public static class ConfigHandler
|
||||
config.ClashUIItem.ConnectionsColumnItem ??= [];
|
||||
config.SystemProxyItem ??= new();
|
||||
config.WebDavItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.Fragment4RayItem ??= new()
|
||||
{
|
||||
Packets = "tlshello",
|
||||
|
||||
@@ -110,6 +110,7 @@ public class Hysteria2Fmt : BaseFmt
|
||||
Hy2RealmUrl = realm.ToUri(),
|
||||
});
|
||||
|
||||
msg = string.Empty;
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,9 +244,26 @@ public sealed class AppManager
|
||||
{
|
||||
return [];
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => ids.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
|
||||
if (ids.Count <= Global.SqliteMaxBatchSize)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => ids.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
var items = new List<ProfileItem>();
|
||||
for (var size = 0; size < ids.Count; size += Global.SqliteMaxBatchSize)
|
||||
{
|
||||
var chunk = ids.Skip(size).Take(Global.SqliteMaxBatchSize).ToList();
|
||||
var chunkItems = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => chunk.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
|
||||
items.AddRange(chunkItems);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
|
||||
@@ -257,18 +274,11 @@ public sealed class AppManager
|
||||
|
||||
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
||||
{
|
||||
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
if (idList.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => idList.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
var items = await GetProfileItemsByIndexIds(ids);
|
||||
var itemMap = items.ToDictionary(it => it.IndexId);
|
||||
|
||||
return idList.Select(itemMap.GetValueOrDefault)
|
||||
return ids.Select(itemMap.GetValueOrDefault)
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public class CoreManager
|
||||
}
|
||||
|
||||
await CoreStart(mainContext);
|
||||
await WaitForProxyPort(preContext);
|
||||
await CoreStartPreService(preContext);
|
||||
|
||||
AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType;
|
||||
@@ -213,6 +214,75 @@ public class CoreManager
|
||||
await _updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
private static async Task WaitForProxyPort(CoreConfigContext? preContext, int timeoutMs = 5000)
|
||||
{
|
||||
if (preContext is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!preContext.AppConfig.TunModeItem.EnableTun)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var rootCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMs));
|
||||
var rootToken = rootCts.Token;
|
||||
|
||||
var port = preContext.Node.Port;
|
||||
// SOCKS5 client greeting: VER=5, NMETHODS=1, METHOD=0x00 (no auth)
|
||||
ReadOnlyMemory<byte> greeting = new byte[] { 0x05, 0x01, 0x00 };
|
||||
var buf = new byte[2];
|
||||
|
||||
while (!rootToken.IsCancellationRequested)
|
||||
{
|
||||
using var tcp = new TcpClient();
|
||||
using var attemptCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(rootToken, attemptCts.Token);
|
||||
var linkedToken = linkedCts.Token;
|
||||
try
|
||||
{
|
||||
await tcp.ConnectAsync(Global.Loopback, port, linkedToken);
|
||||
var stream = tcp.GetStream();
|
||||
|
||||
await stream.WriteAsync(greeting, linkedToken);
|
||||
|
||||
var read = await stream.ReadAsync(buf.AsMemory(0, 2), linkedToken);
|
||||
|
||||
// Server selection: VER=5, METHOD=0x00 — proxy is fully ready
|
||||
if (read == 2 && buf[0] == 0x05)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (!rootToken.IsCancellationRequested)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||
return;
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
|
||||
{
|
||||
// Connection refused, proxy not ready yet, wait 50ms before retrying
|
||||
try
|
||||
{
|
||||
await Task.Delay(50, rootToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore other exceptions and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
|
||||
#region Process
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
using NetBridgeLib.Services;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
public sealed class NetBridgeManager
|
||||
{
|
||||
private static readonly Lazy<NetBridgeManager> _instance = new(() => new());
|
||||
public static NetBridgeManager Instance => _instance.Value;
|
||||
private readonly Config _config = AppManager.Instance.Config;
|
||||
private NetBridgeService? _netBridgeService;
|
||||
private bool _isProxyRunning;
|
||||
private bool _isInitialized;
|
||||
private List<NetBridgeRuleConfig> _ruleConfigs = [];
|
||||
private Func<bool, string, Task>? _updateFunc;
|
||||
private uint _proxyConfigId;
|
||||
|
||||
public async Task Init(Func<bool, string, Task>? updateFunc = null)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updateFunc = updateFunc;
|
||||
|
||||
try
|
||||
{
|
||||
_netBridgeService = new NetBridgeService();
|
||||
_netBridgeService.LogReceived += msg =>
|
||||
{
|
||||
var message = $"NetBridge Log: {msg}";
|
||||
_ = _updateFunc?.Invoke(false, message);
|
||||
};
|
||||
|
||||
_netBridgeService.ConnectionReceived += (processName, pid, destIp, destPort, proxyInfo) =>
|
||||
{
|
||||
var message = $"NetBridge Connection: {processName} (PID: {pid}) -> {destIp}:{destPort} -> {proxyInfo}";
|
||||
_ = _updateFunc?.Invoke(false, message);
|
||||
};
|
||||
|
||||
_ruleConfigs = BuildRuleConfigs(_config.NetBridgeItem?.RuleProcess);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to initialize NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Start()
|
||||
{
|
||||
if (_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var started = _netBridgeService.Start();
|
||||
if (!started)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_isProxyRunning = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to start NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Stop()
|
||||
{
|
||||
if (!_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var stopped = _netBridgeService.Stop();
|
||||
if (!stopped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_isProxyRunning = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to stop NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRoutes(string? ruleProcess)
|
||||
{
|
||||
var newRuleConfigs = BuildRuleConfigs(ruleProcess);
|
||||
|
||||
_ruleConfigs = newRuleConfigs;
|
||||
|
||||
if (!_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await ApplyRoutesInternal();
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateProxyConfig(string proxyHost, int proxyPort)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var proxyType = "SOCKS5";
|
||||
var username = "";
|
||||
var password = "";
|
||||
|
||||
if (_proxyConfigId > 0)
|
||||
{
|
||||
var edited = _netBridgeService.EditProxyConfig(_proxyConfigId, proxyType, proxyHost, (ushort)proxyPort, username, password);
|
||||
if (!edited)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_proxyConfigId = _netBridgeService.AddProxyConfig(proxyType, proxyHost, (ushort)proxyPort, username, password);
|
||||
if (_proxyConfigId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to update proxy config: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetDnsViaProxy(bool enable)
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_netBridgeService.SetDnsViaProxy(enable);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private async Task<bool> ApplyRoutesInternal()
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<NetBridgeRuleConfig> rules;
|
||||
|
||||
rules = _ruleConfigs.Select(JsonUtils.DeepCopy).ToList();
|
||||
|
||||
foreach (var rule in rules.Where(x => x.RuleId > 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _netBridgeService.DeleteRule(rule.RuleId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < rules.Count; i++)
|
||||
{
|
||||
var rule = rules[i];
|
||||
var newRuleId = _netBridgeService.AddRule(rule.ProcessName, rule.TargetHosts, rule.TargetPorts, rule.Protocol, rule.Action, rule.ProxyConfigId);
|
||||
if (newRuleId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rules[i].RuleId = newRuleId;
|
||||
}
|
||||
|
||||
_ruleConfigs = rules;
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static List<NetBridgeRuleConfig> BuildRuleConfigs(string? ruleProcess)
|
||||
{
|
||||
if (ruleProcess.IsNullOrEmpty())
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
var processNames = Utils.String2List(Utils.Convert2Comma(ruleProcess));
|
||||
return processNames.Select(processName => new NetBridgeRuleConfig
|
||||
{
|
||||
ProcessName = processName,
|
||||
TargetHosts = "*",
|
||||
TargetPorts = "*",
|
||||
Protocol = "BOTH",
|
||||
Action = "PROXY",
|
||||
ProxyConfigId = 0
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ public class Config
|
||||
public SystemProxyItem SystemProxyItem { get; set; }
|
||||
public WebDavItem WebDavItem { get; set; }
|
||||
public CheckUpdateItem CheckUpdateItem { get; set; }
|
||||
public NetBridgeItem NetBridgeItem { get; set; }
|
||||
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
||||
public List<InItem> Inbound { get; set; }
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
|
||||
@@ -278,10 +278,3 @@ public class SimpleDNSItem
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NetBridgeItem
|
||||
{
|
||||
public string? RuleProcess { get; set; }
|
||||
public bool EnableDnsViaProxy { get; set; }
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ public class HyRealm4Sbox
|
||||
public string? server_url { get; set; }
|
||||
public string? token { get; set; }
|
||||
public string? realm_id { get; set; }
|
||||
public List<string>? stun_servers { get; set; }
|
||||
}
|
||||
|
||||
public class Server4Sbox : BaseServer4Sbox
|
||||
|
||||
@@ -289,16 +289,7 @@ public class BalancersStrategySettings4Ray
|
||||
{
|
||||
public int? expected { get; set; }
|
||||
public string? maxRTT { get; set; }
|
||||
public float? tolerance { get; set; }
|
||||
public List<string>? baselines { get; set; }
|
||||
public List<BalancersStrategySettingsCosts4Ray>? costs { get; set; }
|
||||
}
|
||||
|
||||
public class BalancersStrategySettingsCosts4Ray
|
||||
{
|
||||
public bool? regexp { get; set; }
|
||||
public string? match { get; set; }
|
||||
public float? value { get; set; }
|
||||
public double? tolerance { get; set; }
|
||||
}
|
||||
|
||||
public class Observatory4Ray
|
||||
|
||||
@@ -15,6 +15,15 @@ public record HyRealm(
|
||||
{
|
||||
public string RendezvousHostPort => RendezvousPort > 0 ? $"{RendezvousHost}:{RendezvousPort}" : RendezvousHost;
|
||||
|
||||
/// <summary>
|
||||
/// sing-box realm.server_url requires a scheme (https:// or http://).
|
||||
/// </summary>
|
||||
public string ToServerUrl()
|
||||
{
|
||||
var scheme = IsHttp ? "http" : "https";
|
||||
return $"{scheme}://{RendezvousHostPort}";
|
||||
}
|
||||
|
||||
public static bool TryParse(string? str, out HyRealm? realm)
|
||||
{
|
||||
realm = null;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
public sealed class NetBridgeRuleConfig
|
||||
{
|
||||
public uint RuleId { get; set; }
|
||||
public string ProcessName { get; set; }
|
||||
public string TargetHosts { get; set; }
|
||||
public string TargetPorts { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Action { get; set; }
|
||||
public uint ProxyConfigId { get; set; }
|
||||
}
|
||||
38
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
38
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -1329,15 +1329,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Process traffic hijacking (experimental) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuNetBridge {
|
||||
get {
|
||||
return ResourceManager.GetString("menuNetBridge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 New Update 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2328,15 +2319,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string NetBridgeRuleTips {
|
||||
get {
|
||||
return ResourceManager.GetString("NetBridgeRuleTips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3069,15 +3051,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableNetBridge {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableNetBridge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 DNS via Bridge 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3753,15 +3726,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Save rules 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSaveNetBridgeRule {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSaveNetBridgeRule", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3898,7 +3862,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode 的本地化字符串。
|
||||
/// 查找类似 For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsBindInterfaceTip {
|
||||
get {
|
||||
|
||||
@@ -1750,7 +1750,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
|
||||
<value>For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled.</value>
|
||||
</data>
|
||||
<data name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>PreSharedKey</value>
|
||||
@@ -1794,21 +1794,9 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall.</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly.</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>Process traffic hijacking (experimental)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS via Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>Save rules</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.</value>
|
||||
</data>
|
||||
|
||||
@@ -1747,7 +1747,7 @@
|
||||
<value>Привязать интерфейс</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме TUN</value>
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для исходящих подключений. На Linux/macOS работает только при включённом TUN.</value>
|
||||
</data>
|
||||
<data name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>Общий ключ (PSK)</value>
|
||||
|
||||
@@ -1791,21 +1791,9 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>启用进程流量劫持(实验性),类似 Proxifer 功能;可以和系统代理并存;和 TUN 模式冲突,请不要同时开启;请在防火墙中允许 v2rayN 使用专用网络</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>需要代理的进程名,多个进程请用逗号分隔;不在列表中的进程也会被劫持,但是会直连</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>进程流量劫持(实验性)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 通过 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存规则</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>检测到不安全配置:AllowInsecure 已启用,但未提供证书。这可能会导致中间人攻击。</value>
|
||||
</data>
|
||||
|
||||
@@ -1791,19 +1791,7 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>啟用進程流量劫持(實驗性),類似 Proxifer 功能;可以和系統代理並存;和 TUN 模式衝突,請不要同時開啟;請在防火牆中允許 v2rayN 使用專用網絡</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>需要代理程式的進程名,多個進程請用逗號分隔;不在清單中的進程也會被劫持,但是會直連</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>進程流量劫持(實驗性)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 透過 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存規則</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -90,7 +90,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NetBridge\src\NetBridgeLib\NetBridgeLib.csproj" />
|
||||
<ProjectReference Include="..\ServiceLib.UdpTest\ServiceLib.UdpTest.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -275,9 +275,10 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
var realm4Sbox = new HyRealm4Sbox()
|
||||
{
|
||||
server_url = realm.RendezvousHostPort,
|
||||
server_url = realm.ToServerUrl(),
|
||||
token = realm.Token,
|
||||
realm_id = realm.RealmName,
|
||||
stun_servers = realm.StunList?.Count > 0 ? realm.StunList : null,
|
||||
};
|
||||
outbound.realm = realm4Sbox;
|
||||
outbound.server = null;
|
||||
|
||||
@@ -89,22 +89,25 @@ public partial class CoreConfigV2rayService
|
||||
EMultipleLoad.RoundRobin => "roundRobin",
|
||||
EMultipleLoad.LeastPing => "leastPing",
|
||||
EMultipleLoad.LeastLoad => "leastLoad",
|
||||
EMultipleLoad.Fallback => "leastLoad",
|
||||
_ => "roundRobin",
|
||||
};
|
||||
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
||||
var balancer = new BalancersItem4Ray
|
||||
{
|
||||
selector = [selector],
|
||||
strategy = new()
|
||||
strategy = strategyType == "leastLoad" ? new()
|
||||
{
|
||||
type = strategyType,
|
||||
settings = new()
|
||||
{
|
||||
expected = 1,
|
||||
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||
},
|
||||
},
|
||||
} : null,
|
||||
tag = balancerTag,
|
||||
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
|
||||
fallbackTag = _coreConfig.outbounds?.FirstOrDefault(o => o.tag.StartsWith(selector))?.tag,
|
||||
};
|
||||
_coreConfig.routing.balancers ??= [];
|
||||
_coreConfig.routing.balancers.Add(balancer);
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class NetBridgeViewModel : MyReactiveObject
|
||||
{
|
||||
[Reactive]
|
||||
public bool EnableNetBridge { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnabletDnsViaProxy { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RuleProcess { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveRulesCmd { get; }
|
||||
|
||||
public NetBridgeViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveRulesCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveRulesAsync();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnableNetBridge)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleNetBridgeAsync(enabled);
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnabletDnsViaProxy)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleDnsViaProxyAsync(enabled);
|
||||
});
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
EnabletDnsViaProxy = _config.NetBridgeItem.EnableDnsViaProxy;
|
||||
|
||||
_config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
|
||||
EnableNetBridge = false;
|
||||
if (_config.NetBridgeItem.RuleProcess.IsNullOrEmpty())
|
||||
{
|
||||
_config.NetBridgeItem.RuleProcess = "Chrome.exe";
|
||||
}
|
||||
RuleProcess = _config.NetBridgeItem.RuleProcess;
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ToggleNetBridgeAsync(bool enabled)
|
||||
{
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Start();
|
||||
if (succeed)
|
||||
{
|
||||
await NetBridgeManager.Instance.UpdateProxyConfig(Global.Loopback, AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
|
||||
await NetBridgeManager.Instance.UpdateRoutes(RuleProcess);
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(EnabletDnsViaProxy);
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Stop();
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleDnsViaProxyAsync(bool enabled)
|
||||
{
|
||||
_config.NetBridgeItem.EnableDnsViaProxy = enabled;
|
||||
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(enabled);
|
||||
}
|
||||
|
||||
/// </summary>
|
||||
private async Task SaveRulesAsync()
|
||||
{
|
||||
_config.NetBridgeItem ??= new();
|
||||
|
||||
var normalizedRuleProcess = RuleProcess;
|
||||
_config.NetBridgeItem.RuleProcess = normalizedRuleProcess;
|
||||
RuleProcess = normalizedRuleProcess;
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) != 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
if (EnableNetBridge)
|
||||
{
|
||||
var routesUpdated = await NetBridgeManager.Instance.UpdateRoutes(normalizedRuleProcess);
|
||||
NoticeManager.Instance.Enqueue(routesUpdated ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(bool isError, string msg)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(msg);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,6 @@
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="menuBackupAndRestore" Header="{x:Static resx:ResUI.menuBackupAndRestore}" />
|
||||
<MenuItem x:Name="menuOpenTheFileLocation" Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
|
||||
<MenuItem x:Name="menuNetBridge" Header="{x:Static resx:ResUI.menuNetBridge}" />
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem x:Name="menuHelp" Header="{x:Static resx:ResUI.menuHelp}">
|
||||
|
||||
@@ -12,7 +12,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
private readonly WindowNotificationManager? _manager;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
private NetBridgeView? _netBridgeView;
|
||||
private bool _blCloseByUser = false;
|
||||
|
||||
public MainWindow()
|
||||
@@ -29,7 +28,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||
menuClose.Click += MenuClose_Click;
|
||||
menuNetBridge.Click += MenuNetBridge_Click;
|
||||
|
||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||
|
||||
@@ -167,7 +165,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
Title = $"{Utils.GetVersion()}";
|
||||
menuAddServerViaScan.IsVisible = false;
|
||||
menuNetBridge.IsVisible = false;
|
||||
}
|
||||
|
||||
if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
|
||||
@@ -382,19 +379,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
DialogHost.Show(_backupAndRestoreView);
|
||||
}
|
||||
|
||||
private void MenuNetBridge_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
_netBridgeView ??= new NetBridgeView();
|
||||
DialogHost.Show(_netBridgeView);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.RunAsAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuClose_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (await UI.ShowYesNo(this, ResUI.menuExitTips) != ButtonResult.Yes)
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="v2rayN.Desktop.Views.NetBridgeView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
d:DesignHeight="400"
|
||||
d:DesignWidth="600"
|
||||
x:DataType="vms:NetBridgeViewModel"
|
||||
mc:Ignorable="d">
|
||||
<Border
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleSwitch
|
||||
x:Name="togEnableNetBridge"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEnableNetBridge}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleSwitch
|
||||
x:Name="togEnabletDnsViaProxy"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEnabletDnsViaProxy}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<DockPanel>
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.NetBridgeRuleTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbSaveNetBridgeRule}"
|
||||
IsDefault="True" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRuleProcess"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,25 +0,0 @@
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class NetBridgeView : ReactiveUserControl<NetBridgeViewModel>
|
||||
{
|
||||
public NetBridgeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new NetBridgeViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel, vm => vm.SaveRulesCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableNetBridge, v => v.togEnableNetBridge.IsChecked).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.RuleProcess, v => v.txtRuleProcess.Text).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.Tests", "Service
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.UdpTest", "ServiceLib.UdpTest\ServiceLib.UdpTest.csproj", "{CE9D9298-0289-4718-2522-B236489F2912}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetBridgeLib", "NetBridge\src\NetBridgeLib\NetBridgeLib.csproj", "{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -71,10 +69,6 @@ Global
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</Folder>
|
||||
<Project Path="AmazTool/AmazTool.csproj" />
|
||||
<Project Path="GlobalHotKeys/src/GlobalHotKeys/GlobalHotKeys.csproj" />
|
||||
<Project Path="NetBridge/src/NetBridgeLib/NetBridgeLib.csproj" />
|
||||
<Project Path="ServiceLib.Tests/ServiceLib.Tests.csproj" />
|
||||
<Project Path="ServiceLib.UdpTest/ServiceLib.UdpTest.csproj" Id="77930428-4dc4-4130-9031-6b9e714f5d20" />
|
||||
<Project Path="ServiceLib/ServiceLib.csproj" />
|
||||
|
||||
@@ -233,10 +233,6 @@
|
||||
x:Name="menuOpenTheFileLocation"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
|
||||
<MenuItem
|
||||
x:Name="menuNetBridge"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuNetBridge}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Separator />
|
||||
|
||||
@@ -10,7 +10,6 @@ public partial class MainWindow
|
||||
private static Config _config;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
private NetBridgeView? _netBridgeView;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -28,7 +27,6 @@ public partial class MainWindow
|
||||
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
||||
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||
menuNetBridge.Click += MenuNetBridge_Click;
|
||||
|
||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||
|
||||
@@ -378,19 +376,6 @@ public partial class MainWindow
|
||||
DialogHost.Show(_backupAndRestoreView, "RootDialog");
|
||||
}
|
||||
|
||||
private void MenuNetBridge_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
_netBridgeView ??= new NetBridgeView();
|
||||
DialogHost.Show(_netBridgeView, "RootDialog");
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.RunAsAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Event
|
||||
|
||||
#region UI
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
<reactiveui:ReactiveUserControl
|
||||
x:Class="v2rayN.Views.NetBridgeView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:v2rayN.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Height="400"
|
||||
d:DesignHeight="400"
|
||||
d:DesignWidth="600"
|
||||
x:TypeArguments="vms:NetBridgeViewModel"
|
||||
Style="{StaticResource ViewGlobal}"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleButton
|
||||
x:Name="togEnableNetBridge"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbEnableNetBridge}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleButton
|
||||
x:Name="togEnabletDnsViaProxy"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbEnabletDnsViaProxy}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel>
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.NetBridgeRuleTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbSaveNetBridgeRule}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRuleProcess"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Style="{StaticResource DefTextBox}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveUserControl>
|
||||
@@ -1,26 +0,0 @@
|
||||
namespace v2rayN.Views;
|
||||
|
||||
public partial class NetBridgeView
|
||||
{
|
||||
public NetBridgeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new NetBridgeViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel, vm => vm.SaveRulesCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableNetBridge, v => v.togEnableNetBridge.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnabletDnsViaProxy, v => v.togEnabletDnsViaProxy.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RuleProcess, v => v.txtRuleProcess.Text).DisposeWith(disposables);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user