mirror of
https://github.com/netchx/netch.git
synced 2026-05-11 23:45:06 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d829e347d3 | ||
|
|
a01761d2e2 | ||
|
|
68d87e2ff2 | ||
|
|
04d6933319 | ||
|
|
e46eef17d0 | ||
|
|
d3c3958dab | ||
|
|
5ec8d38fd1 | ||
|
|
2a8754ecfb | ||
|
|
cbc6822bff | ||
|
|
96bd7473ca | ||
|
|
54b2b87dec | ||
|
|
42baed8b8f | ||
|
|
f68aae6795 | ||
|
|
8e2008077d | ||
|
|
5b4f0026ff | ||
|
|
89f9dccb87 | ||
|
|
3e377f2e9d | ||
|
|
635212f24d | ||
|
|
46d60babbc | ||
|
|
8f80f9abef | ||
|
|
e268f1838f | ||
|
|
d99229ad50 | ||
|
|
df85d5797d | ||
|
|
74856ccd61 | ||
|
|
0165d080c6 | ||
|
|
97fb20e326 | ||
|
|
4d71e2d12f | ||
|
|
0fa83eac3c | ||
|
|
aa6623b063 | ||
|
|
94335ad900 | ||
|
|
baf3b39dd3 | ||
|
|
c12122f7d0 | ||
|
|
3e5a4fc102 | ||
|
|
57dbd0193a | ||
|
|
5647a6c7ea | ||
|
|
773bad4845 | ||
|
|
3e943ec6b8 | ||
|
|
920b068a1e | ||
|
|
7eac7b0837 | ||
|
|
e1f3390787 | ||
|
|
a62f694908 | ||
|
|
98f3218e28 | ||
|
|
9da801dc19 | ||
|
|
ff7ae73156 | ||
|
|
6810bcc87f | ||
|
|
3462a3badf | ||
|
|
87a3581dff | ||
|
|
060c42efbc | ||
|
|
72ae9b7bf3 | ||
|
|
bf4b637940 | ||
|
|
f8b1c4acd6 | ||
|
|
88f3a4940b | ||
|
|
cb8dc2163f | ||
|
|
ee206f3df0 | ||
|
|
d5e1ef1a56 | ||
|
|
aeaef4e125 | ||
|
|
352602a7ed |
20
Netch.sln
20
Netch.sln
@@ -1,10 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29009.5
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31423.177
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Netch", "Netch\Netch.csproj", "{4B041B91-5790-4571-8C58-C63FFE4BC9F8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "UnitTest\UnitTest.csproj", "{38240783-9AD2-4A01-84C1-1A3E5F05720F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdditionalFiles", "AdditionalFiles", "{B7354F81-F79C-4C23-9067-C4DAE91B56F0}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
common.props = common.props
|
||||
global.json = global.json
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -15,6 +27,10 @@ Global
|
||||
{4B041B91-5790-4571-8C58-C63FFE4BC9F8}.Debug|x64.Build.0 = Debug|x64
|
||||
{4B041B91-5790-4571-8C58-C63FFE4BC9F8}.Release|x64.ActiveCfg = Release|x64
|
||||
{4B041B91-5790-4571-8C58-C63FFE4BC9F8}.Release|x64.Build.0 = Release|x64
|
||||
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Debug|x64.Build.0 = Debug|x64
|
||||
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Release|x64.ActiveCfg = Release|x64
|
||||
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
public const string AioDnsRuleFile = "bin\\aiodns.conf";
|
||||
public const string NFDriver = "bin\\nfdriver.sys";
|
||||
public const string NFCore = "bin\\core.bin";
|
||||
public const string STUNServersFile = "bin\\stun.txt";
|
||||
|
||||
public const string LogFile = "logging\\application.log";
|
||||
@@ -14,6 +15,8 @@
|
||||
public const string OutputTemplate = @"[{Timestamp:yyyy-MM-dd HH:mm:ss}][{Level}] {Message:lj}{NewLine}{Exception}";
|
||||
public const string EOF = "\r\n";
|
||||
|
||||
public const string DefaultGroup = "NONE";
|
||||
|
||||
public static class Parameter
|
||||
{
|
||||
public const string Show = "-show";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Interfaces;
|
||||
using static Netch.Interops.AioDNS;
|
||||
|
||||
@@ -9,12 +10,12 @@ namespace Netch.Controllers
|
||||
{
|
||||
public string Name => "DNS Service";
|
||||
|
||||
public void Stop()
|
||||
public async Task StopAsync()
|
||||
{
|
||||
Free();
|
||||
await FreeAsync();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
public async Task StartAsync()
|
||||
{
|
||||
MainController.PortCheck(Global.Settings.AioDNS.ListenPort, "DNS");
|
||||
|
||||
@@ -27,7 +28,7 @@ namespace Netch.Controllers
|
||||
Dial(NameList.TYPE_CDNS, $"{aioDnsConfig.ChinaDNS}");
|
||||
Dial(NameList.TYPE_ODNS, $"{aioDnsConfig.OtherDNS}");
|
||||
|
||||
if (!Init())
|
||||
if (!await InitAsync())
|
||||
throw new Exception("AioDNS start failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Enums;
|
||||
using Netch.Models;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
@@ -67,11 +68,11 @@ namespace Netch.Controllers
|
||||
Instance.Dispose();
|
||||
}
|
||||
|
||||
protected void StartGuard(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
|
||||
protected async Task StartGuardAsync(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
|
||||
{
|
||||
State = State.Starting;
|
||||
|
||||
_logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||
_logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
_logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true };
|
||||
|
||||
Instance.StartInfo.Arguments = argument;
|
||||
@@ -82,8 +83,8 @@ namespace Netch.Controllers
|
||||
|
||||
if (RedirectOutput)
|
||||
{
|
||||
Task.Run(() => ReadOutput(Instance.StandardOutput));
|
||||
Task.Run(() => ReadOutput(Instance.StandardError));
|
||||
Task.Run(() => ReadOutput(Instance.StandardOutput)).Forget();
|
||||
Task.Run(() => ReadOutput(Instance.StandardError)).Forget();
|
||||
|
||||
if (!StartedKeywords.Any())
|
||||
{
|
||||
@@ -95,20 +96,20 @@ namespace Netch.Controllers
|
||||
// wait ReadOutput change State
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
await Task.Delay(50);
|
||||
switch (State)
|
||||
{
|
||||
case State.Started:
|
||||
OnStarted();
|
||||
return;
|
||||
case State.Stopped:
|
||||
StopGuard();
|
||||
await StopGuardAsync();
|
||||
OnStartFailed();
|
||||
throw new MessageException($"{Name} 控制器启动失败");
|
||||
}
|
||||
}
|
||||
|
||||
StopGuard();
|
||||
await StopGuardAsync();
|
||||
throw new MessageException($"{Name} 控制器启动超时");
|
||||
}
|
||||
}
|
||||
@@ -136,12 +137,12 @@ namespace Netch.Controllers
|
||||
State = State.Stopped;
|
||||
}
|
||||
|
||||
public virtual void Stop()
|
||||
public virtual async Task StopAsync()
|
||||
{
|
||||
StopGuard();
|
||||
await StopGuardAsync();
|
||||
}
|
||||
|
||||
protected void StopGuard()
|
||||
protected async Task StopGuardAsync()
|
||||
{
|
||||
_logStreamWriter?.Close();
|
||||
_logFileStream?.Close();
|
||||
@@ -151,7 +152,7 @@ namespace Netch.Controllers
|
||||
if (Instance is { HasExited: false })
|
||||
{
|
||||
Instance.Kill();
|
||||
Instance.WaitForExit();
|
||||
await Instance.WaitForExitAsync();
|
||||
}
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Enums;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
@@ -11,41 +16,75 @@ namespace Netch.Controllers
|
||||
{
|
||||
public static class MainController
|
||||
{
|
||||
public static Mode? Mode { get; private set; }
|
||||
public static Socks5Server? Socks5Server { get; private set; }
|
||||
|
||||
public static readonly NTTController NTTController = new();
|
||||
public static Server? Server { get; private set; }
|
||||
|
||||
public static Mode? Mode { get; private set; }
|
||||
|
||||
public static IServerController? ServerController { get; private set; }
|
||||
|
||||
public static IModeController? ModeController { get; private set; }
|
||||
|
||||
public static ModeFeature ModeFeatures { get; private set; }
|
||||
|
||||
private static readonly AsyncSemaphore Lock = new(1);
|
||||
|
||||
public static async Task StartAsync(Server server, Mode mode)
|
||||
{
|
||||
Log.Information("启动主控制器: {Server} {Mode}", $"{server.Type}", $"[{(int) mode.Type}]{mode.Remark}");
|
||||
using var _ = await Lock.EnterAsync();
|
||||
|
||||
if (DnsUtils.Lookup(server.Hostname) == null)
|
||||
Log.Information("Start MainController: {Server} {Mode}", $"{server.Type}", $"[{(int)mode.Type}]{mode.Remark}");
|
||||
|
||||
if (await DnsUtils.LookupAsync(server.Hostname) == null)
|
||||
throw new MessageException(i18N.Translate("Lookup Server hostname failed"));
|
||||
|
||||
// TODO Disable NAT Type Test setting
|
||||
// cache STUN Server ip to prevent "Wrong STUN Server"
|
||||
DnsUtils.LookupAsync(Global.Settings.STUN_Server).Forget();
|
||||
|
||||
Server = server;
|
||||
Mode = mode;
|
||||
|
||||
await Task.WhenAll(
|
||||
Task.Run(NativeMethods.RefreshDNSCache),
|
||||
Task.Run(Firewall.AddNetchFwRules)
|
||||
);
|
||||
await Task.WhenAll(Task.Run(NativeMethods.RefreshDNSCache), Task.Run(Firewall.AddNetchFwRules));
|
||||
|
||||
if (Log.IsEnabled(LogEventLevel.Debug))
|
||||
Task.Run(() =>
|
||||
{
|
||||
// TODO log level setting
|
||||
Log.Debug("Running Processes: \n{Processes}", string.Join("\n", SystemInfo.Processes(false)));
|
||||
}).Forget();
|
||||
{
|
||||
// TODO log level setting
|
||||
Log.Debug("Running Processes: \n{Processes}", string.Join("\n", SystemInfo.Processes(false)));
|
||||
})
|
||||
.Forget();
|
||||
|
||||
try
|
||||
{
|
||||
if (!ModeHelper.SkipServerController(server, mode))
|
||||
server = await Task.Run(() => StartServer(server));
|
||||
(ModeController, ModeFeatures) = ModeHelper.GetModeControllerByType(mode.Type, out var modePort, out var portName);
|
||||
|
||||
await Task.Run(() => StartMode(server, mode));
|
||||
if (modePort != null)
|
||||
TryReleaseTcpPort((ushort)modePort, portName);
|
||||
|
||||
if (Server is Socks5Server socks5 && (!socks5.Auth() || ModeFeatures.HasFlag(ModeFeature.SupportSocks5Auth)))
|
||||
{
|
||||
Socks5Server = socks5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start Server Controller to get a local socks5 server
|
||||
Log.Debug("Server Information: {Data}", $"{server.Type} {server.MaskedData()}");
|
||||
|
||||
ServerController = ServerHelper.GetUtilByTypeName(server.Type).GetController();
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ServerController.Name));
|
||||
|
||||
TryReleaseTcpPort(ServerController.Socks5LocalPort(), "Socks5");
|
||||
Socks5Server = await ServerController.StartAsync(server);
|
||||
|
||||
StatusPortInfoText.Socks5Port = Socks5Server.Port;
|
||||
StatusPortInfoText.UpdateShareLan();
|
||||
}
|
||||
|
||||
// Start Mode Controller
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
|
||||
await ModeController.StartAsync(Socks5Server, mode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -59,54 +98,33 @@ namespace Netch.Controllers
|
||||
case MessageException:
|
||||
throw;
|
||||
default:
|
||||
Log.Error(e, "主控制器启动未处理异常");
|
||||
throw new MessageException($"未处理异常\n{e.Message}");
|
||||
Log.Error(e, "Unhandled Exception When Start MainController");
|
||||
Utils.Utils.Open(Constants.LogFile);
|
||||
throw new MessageException($"{i18N.Translate("Unhandled Exception")}\n{e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Server StartServer(Server server)
|
||||
{
|
||||
ServerController = ServerHelper.GetUtilByTypeName(server.Type).GetController();
|
||||
|
||||
TryReleaseTcpPort(ServerController.Socks5LocalPort(), "Socks5");
|
||||
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ServerController.Name));
|
||||
|
||||
Log.Debug($"{server.Type} {server.MaskedData()}");
|
||||
var socks5 = ServerController.Start(server);
|
||||
|
||||
StatusPortInfoText.Socks5Port = socks5.Port;
|
||||
StatusPortInfoText.UpdateShareLan();
|
||||
|
||||
return socks5;
|
||||
}
|
||||
|
||||
private static void StartMode(Server server, Mode mode)
|
||||
{
|
||||
ModeController = ModeHelper.GetModeControllerByType(mode.Type, out var port, out var portName);
|
||||
|
||||
if (port != null)
|
||||
TryReleaseTcpPort((ushort) port, portName);
|
||||
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
|
||||
|
||||
ModeController.Start(server, mode);
|
||||
}
|
||||
|
||||
public static async Task StopAsync()
|
||||
{
|
||||
if (Lock.CurrentCount == 0)
|
||||
{
|
||||
(await Lock.EnterAsync()).Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
using var _ = await Lock.EnterAsync();
|
||||
|
||||
if (ServerController == null && ModeController == null)
|
||||
return;
|
||||
|
||||
Log.Information("Stop Main Controller");
|
||||
StatusPortInfoText.Reset();
|
||||
|
||||
Task.Run(() => NTTController.Stop()).Forget();
|
||||
|
||||
var tasks = new[]
|
||||
{
|
||||
Task.Run(() => ServerController?.Stop()),
|
||||
Task.Run(() => ModeController?.Stop())
|
||||
Task.Run(() => ServerController?.StopAsync()),
|
||||
Task.Run(() => ModeController?.StopAsync())
|
||||
};
|
||||
|
||||
try
|
||||
@@ -115,11 +133,12 @@ namespace Netch.Controllers
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "主控制器停止未处理异常");
|
||||
Log.Error(e, "MainController Stop Error");
|
||||
}
|
||||
|
||||
ModeController = null;
|
||||
ServerController = null;
|
||||
ModeController = null;
|
||||
ModeFeatures = 0;
|
||||
}
|
||||
|
||||
public static void PortCheck(ushort port, string portName, PortType portType = PortType.Both)
|
||||
@@ -159,5 +178,30 @@ namespace Netch.Controllers
|
||||
|
||||
PortCheck(port, portName, PortType.TCP);
|
||||
}
|
||||
|
||||
public static async Task<NatTypeTestResult> DiscoveryNatTypeAsync(CancellationToken ctx = default)
|
||||
{
|
||||
Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null");
|
||||
return await Socks5ServerTestUtils.DiscoveryNatTypeAsync(Socks5Server, ctx);
|
||||
}
|
||||
|
||||
public static async Task<int?> HttpConnectAsync(CancellationToken ctx = default)
|
||||
{
|
||||
Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null");
|
||||
try
|
||||
{
|
||||
return await Socks5ServerTestUtils.HttpConnectAsync(Socks5Server, ctx);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning(e, "Unhandled Socks5ServerTestUtils.HttpConnectAsync Exception");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Interops;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
using Netch.Servers.Shadowsocks;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
using static Netch.Interops.Redirector;
|
||||
@@ -27,12 +27,13 @@ namespace Netch.Controllers
|
||||
|
||||
public string Name => "Redirector";
|
||||
|
||||
public void Start(Server server, Mode mode)
|
||||
public async Task StartAsync(Socks5Server server, Mode mode)
|
||||
{
|
||||
_server = server;
|
||||
_mode = mode;
|
||||
_rdrConfig = Global.Settings.Redirector;
|
||||
CheckDriver();
|
||||
CheckCore();
|
||||
|
||||
Dial(NameList.TYPE_FILTERLOOPBACK, "false");
|
||||
Dial(NameList.TYPE_FILTERICMP, "true");
|
||||
@@ -43,7 +44,7 @@ namespace Netch.Controllers
|
||||
// Server
|
||||
Dial(NameList.TYPE_FILTERUDP, _rdrConfig.FilterProtocol.HasFlag(PortType.UDP).ToString().ToLower());
|
||||
Dial(NameList.TYPE_FILTERTCP, _rdrConfig.FilterProtocol.HasFlag(PortType.TCP).ToString().ToLower());
|
||||
dial_Server(_rdrConfig.FilterProtocol, _server);
|
||||
await DialServerAsync(_rdrConfig.FilterProtocol, _server);
|
||||
|
||||
// Mode Rule
|
||||
dial_Name(_mode);
|
||||
@@ -51,13 +52,13 @@ namespace Netch.Controllers
|
||||
// Features
|
||||
Dial(NameList.TYPE_DNSHOST, _rdrConfig.DNSHijack ? _rdrConfig.DNSHijackHost : "");
|
||||
|
||||
if (!Init())
|
||||
if (!await InitAsync())
|
||||
throw new MessageException("Redirector start failed.");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public async Task StopAsync()
|
||||
{
|
||||
Free();
|
||||
await FreeAsync();
|
||||
}
|
||||
|
||||
#region CheckRule
|
||||
@@ -102,32 +103,25 @@ namespace Netch.Controllers
|
||||
|
||||
#endregion
|
||||
|
||||
private void dial_Server(PortType portType, in Server server)
|
||||
private async Task DialServerAsync(PortType portType, Server server)
|
||||
{
|
||||
if (portType == PortType.Both)
|
||||
{
|
||||
dial_Server(PortType.TCP, server);
|
||||
dial_Server(PortType.UDP, server);
|
||||
await DialServerAsync(PortType.TCP, server);
|
||||
await DialServerAsync(PortType.UDP, server);
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = portType == PortType.UDP ? UdpNameListOffset : 0;
|
||||
|
||||
if (server is Socks5 socks5)
|
||||
if (server is Socks5Server socks5)
|
||||
{
|
||||
Dial(NameList.TYPE_TCPTYPE + offset, "Socks5");
|
||||
Dial(NameList.TYPE_TCPHOST + offset, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
Dial(NameList.TYPE_TCPHOST + offset, $"{await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
|
||||
Dial(NameList.TYPE_TCPUSER + offset, socks5.Username ?? string.Empty);
|
||||
Dial(NameList.TYPE_TCPPASS + offset, socks5.Password ?? string.Empty);
|
||||
Dial(NameList.TYPE_TCPMETH + offset, string.Empty);
|
||||
}
|
||||
else if (server is Shadowsocks shadowsocks && !shadowsocks.HasPlugin() && _rdrConfig.RedirectorSS)
|
||||
{
|
||||
Dial(NameList.TYPE_TCPTYPE + offset, "Shadowsocks");
|
||||
Dial(NameList.TYPE_TCPHOST + offset, $"{shadowsocks.AutoResolveHostname()}:{shadowsocks.Port}");
|
||||
Dial(NameList.TYPE_TCPMETH + offset, shadowsocks.EncryptMethod);
|
||||
Dial(NameList.TYPE_TCPPASS + offset, shadowsocks.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Assert(false);
|
||||
@@ -159,6 +153,12 @@ namespace Netch.Controllers
|
||||
Dial(NameList.TYPE_BYPNAME, "^" + Global.NetchDir.ToRegexString() + @"((?!NTT\.exe).)*$");
|
||||
}
|
||||
|
||||
private void CheckCore()
|
||||
{
|
||||
if (!File.Exists(Constants.NFCore))
|
||||
throw new MessageException(i18N.Translate("\"Core.bin\" is missing. Please check your Antivirus software"));
|
||||
}
|
||||
|
||||
#region DriverUtil
|
||||
|
||||
private static void CheckDriver()
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Controllers
|
||||
{
|
||||
public class NTTController : Guard, IController
|
||||
{
|
||||
public NTTController() : base("NTT.exe")
|
||||
{
|
||||
}
|
||||
|
||||
public override string Name => "NTT";
|
||||
|
||||
/// <summary>
|
||||
/// 启动 NatTypeTester
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<(string? result, string? localEnd, string? publicEnd)> Start()
|
||||
{
|
||||
string? localEnd = null, publicEnd = null, result = null, bindingTest = null;
|
||||
|
||||
try
|
||||
{
|
||||
Instance.StartInfo.Arguments = $" {Global.Settings.STUN_Server} {Global.Settings.STUN_Server_Port}";
|
||||
Instance.Start();
|
||||
|
||||
var output = await Instance.StandardOutput.ReadToEndAsync();
|
||||
var error = await Instance.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(Path.Combine(Global.NetchDir, $"logging\\{Name}.log"), $"{output}\r\n{error}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning(e, "写入 {Name} 日志错误", Name);
|
||||
}
|
||||
|
||||
if (output.IsNullOrWhiteSpace())
|
||||
if (!error.IsNullOrWhiteSpace())
|
||||
{
|
||||
var errorFirst = error.GetLines().First();
|
||||
return (errorFirst.SplitTrimEntries(':').Last(), null, null);
|
||||
}
|
||||
|
||||
foreach (var line in output.Split('\n'))
|
||||
{
|
||||
var str = line.SplitTrimEntries(':');
|
||||
if (str.Length < 2)
|
||||
continue;
|
||||
|
||||
var key = str[0];
|
||||
var value = str[1];
|
||||
switch (key)
|
||||
{
|
||||
case "Other address is":
|
||||
case "Nat mapping behavior":
|
||||
case "Nat filtering behavior":
|
||||
break;
|
||||
case "Binding test":
|
||||
bindingTest = value;
|
||||
break;
|
||||
case "Local address":
|
||||
localEnd = value;
|
||||
break;
|
||||
case "Mapped address":
|
||||
publicEnd = value;
|
||||
break;
|
||||
case "result":
|
||||
result = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingTest == "Fail")
|
||||
result = "Fail";
|
||||
|
||||
return (result, localEnd, publicEnd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "{Name} 控制器启动异常", Name);
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return (null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Forms;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
@@ -29,7 +31,7 @@ namespace Netch.Controllers
|
||||
|
||||
public override string Name => "pcap2socks";
|
||||
|
||||
public void Start(Server server, Mode mode)
|
||||
public async Task StartAsync(Socks5Server server, Mode mode)
|
||||
{
|
||||
_server = server;
|
||||
_mode = mode;
|
||||
@@ -37,19 +39,19 @@ namespace Netch.Controllers
|
||||
var outboundNetworkInterface = NetworkInterfaceUtils.GetBest();
|
||||
|
||||
var argument = new StringBuilder($@"-i \Device\NPF_{outboundNetworkInterface.Id}");
|
||||
if (_server is Socks5 socks5 && !socks5.Auth())
|
||||
argument.Append($" --destination {socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
if (_server is Socks5Server socks5 && !socks5.Auth())
|
||||
argument.Append($" --destination {await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
|
||||
else
|
||||
argument.Append($" --destination 127.0.0.1:{Global.Settings.Socks5LocalPort}");
|
||||
Trace.Assert(false);
|
||||
|
||||
argument.Append($" {_mode.GetRules().FirstOrDefault() ?? "-P n"}");
|
||||
StartGuard(argument.ToString());
|
||||
await StartGuardAsync(argument.ToString());
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
public override async Task StopAsync()
|
||||
{
|
||||
Global.MainForm.Invoke(new Action(() => { _form.Close(); }));
|
||||
StopGuard();
|
||||
await StopGuardAsync();
|
||||
}
|
||||
|
||||
~PcapController()
|
||||
@@ -76,10 +78,11 @@ namespace Netch.Controllers
|
||||
if (new FileInfo(LogPath).Length == 0)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Utils.Utils.Open("https://github.com/zhxie/pcap2socks#dependencies");
|
||||
});
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Utils.Utils.Open("https://github.com/zhxie/pcap2socks#dependencies");
|
||||
})
|
||||
.Forget();
|
||||
|
||||
throw new MessageException("Pleases install pcap2socks's dependency");
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ namespace Netch.Controllers
|
||||
|
||||
public string Name => "tun2socks";
|
||||
|
||||
public void Start(Server server, Mode mode)
|
||||
public async Task StartAsync(Socks5Server server, Mode mode)
|
||||
{
|
||||
_mode = mode;
|
||||
_tunConfig = Global.Settings.TUNTAP;
|
||||
|
||||
if (server is Socks5Bridge socks5Bridge)
|
||||
_serverRemoteAddress = DnsUtils.Lookup(socks5Bridge.RemoteHostname);
|
||||
if (server is Socks5LocalServer socks5Bridge)
|
||||
_serverRemoteAddress = await DnsUtils.LookupAsync(socks5Bridge.RemoteHostname);
|
||||
|
||||
if (_serverRemoteAddress != null && IPAddress.IsLoopback(_serverRemoteAddress))
|
||||
_serverRemoteAddress = null;
|
||||
@@ -56,11 +56,11 @@ namespace Netch.Controllers
|
||||
Dial(NameList.TYPE_UDPREST, "");
|
||||
Dial(NameList.TYPE_UDPTYPE, "Socks5");
|
||||
|
||||
if (server is Socks5 socks5)
|
||||
if (server is Socks5Server socks5)
|
||||
{
|
||||
Dial(NameList.TYPE_TCPHOST, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
Dial(NameList.TYPE_TCPHOST, $"{await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
|
||||
|
||||
Dial(NameList.TYPE_UDPHOST, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
Dial(NameList.TYPE_UDPHOST, $"{await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
|
||||
|
||||
if (socks5.Auth())
|
||||
{
|
||||
@@ -86,7 +86,7 @@ namespace Netch.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
_aioDnsController.Start();
|
||||
await _aioDnsController.StartAsync();
|
||||
Dial(NameList.TYPE_DNSADDR, $"127.0.0.1:{Global.Settings.AioDNS.ListenPort}");
|
||||
}
|
||||
|
||||
@@ -106,16 +106,16 @@ namespace Netch.Controllers
|
||||
SetupRouteTable();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public async Task StopAsync()
|
||||
{
|
||||
var tasks = new[]
|
||||
{
|
||||
Task.Run(Free),
|
||||
FreeAsync(),
|
||||
Task.Run(ClearRouteTable),
|
||||
Task.Run(_aioDnsController.Stop)
|
||||
_aioDnsController.StopAsync()
|
||||
};
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private void CheckDriver()
|
||||
@@ -147,7 +147,6 @@ namespace Netch.Controllers
|
||||
private void SetupRouteTable()
|
||||
{
|
||||
Global.MainForm.StatusText(i18N.Translate("Setup Route Table Rule"));
|
||||
Log.Information("设置路由规则");
|
||||
|
||||
// Server Address
|
||||
if (_serverRemoteAddress != null)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Netch.Controllers
|
||||
public const string Name = @"Netch";
|
||||
public const string Copyright = @"Copyright © 2019 - 2021";
|
||||
|
||||
public const string AssemblyVersion = @"1.8.6";
|
||||
public const string AssemblyVersion = @"1.9.0";
|
||||
private const string Suffix = @"";
|
||||
|
||||
public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}";
|
||||
@@ -37,14 +37,14 @@ namespace Netch.Controllers
|
||||
|
||||
public static event EventHandler? NewVersionNotFound;
|
||||
|
||||
public static async Task Check(bool isPreRelease)
|
||||
public static async Task CheckAsync(bool isPreRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updater = new GitHubRelease(Owner, Repo);
|
||||
var url = updater.AllReleaseUrl;
|
||||
|
||||
var json = await WebUtil.DownloadStringAsync(WebUtil.CreateRequest(url));
|
||||
var (_, json) = await WebUtil.DownloadStringAsync(WebUtil.CreateRequest(url));
|
||||
|
||||
var releases = JsonSerializer.Deserialize<List<Release>>(json)!;
|
||||
LatestRelease = GetLatestRelease(releases, isPreRelease);
|
||||
@@ -52,12 +52,12 @@ namespace Netch.Controllers
|
||||
if (VersionUtil.CompareVersion(LatestRelease.tag_name, Version) > 0)
|
||||
{
|
||||
Log.Information("发现新版本");
|
||||
NewVersionFound?.Invoke(null, new EventArgs());
|
||||
NewVersionFound?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("目前是最新版本");
|
||||
NewVersionNotFound?.Invoke(null, new EventArgs());
|
||||
NewVersionNotFound?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -67,7 +67,7 @@ namespace Netch.Controllers
|
||||
else
|
||||
Log.Error(e, "获取新版本异常");
|
||||
|
||||
NewVersionFoundFailed?.Invoke(null, new EventArgs());
|
||||
NewVersionFoundFailed?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Netch.Models
|
||||
namespace Netch.Enums
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
13
Netch/Enums/ModeFeature.cs
Normal file
13
Netch/Enums/ModeFeature.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Netch.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum ModeFeature
|
||||
{
|
||||
SupportSocks5 = 0,
|
||||
SupportIPv4 = 0,
|
||||
SupportSocks5Auth = 0b_0001,
|
||||
SupportIPv6 = 0b_0100
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Netch.Models
|
||||
namespace Netch.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
@@ -7,5 +7,7 @@ namespace Netch
|
||||
public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10;
|
||||
|
||||
public static bool AlwaysShowNewVersionFound { get; set; }
|
||||
|
||||
public static bool NoSupport { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Netch.Properties;
|
||||
using Netch.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Netch.Forms
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using Vanara.PInvoke;
|
||||
using static Vanara.PInvoke.User32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Netch.Forms
|
||||
{
|
||||
@@ -34,21 +35,21 @@ namespace Netch.Forms
|
||||
|
||||
private void Parent_Activated(object? sender, EventArgs? e)
|
||||
{
|
||||
SetWindowPos(Handle,
|
||||
HWND.HWND_TOPMOST,
|
||||
SetWindowPos(new HWND(Handle),
|
||||
new HWND(-1),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_SHOWWINDOW);
|
||||
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW);
|
||||
|
||||
SetWindowPos(Handle,
|
||||
HWND.HWND_NOTOPMOST,
|
||||
SetWindowPos(new HWND(Handle),
|
||||
new HWND(-2),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_SHOWWINDOW);
|
||||
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW);
|
||||
}
|
||||
|
||||
private void richTextBox1_TextChanged(object? sender, EventArgs? e)
|
||||
|
||||
20
Netch/Forms/MainForm.Designer.cs
generated
20
Netch/Forms/MainForm.Designer.cs
generated
@@ -73,6 +73,7 @@
|
||||
this.DownloadSpeedLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.UploadSpeedLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.blankToolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.HttpStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.NatTypeStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.NatTypeStatusLightLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.ControlButton = new System.Windows.Forms.Button();
|
||||
@@ -521,6 +522,7 @@
|
||||
this.DownloadSpeedLabel,
|
||||
this.UploadSpeedLabel,
|
||||
this.blankToolStripStatusLabel,
|
||||
this.HttpStatusLabel,
|
||||
this.NatTypeStatusLabel,
|
||||
this.NatTypeStatusLightLabel});
|
||||
this.StatusStrip.Location = new System.Drawing.Point(0, 272);
|
||||
@@ -560,9 +562,18 @@
|
||||
// blankToolStripStatusLabel
|
||||
//
|
||||
this.blankToolStripStatusLabel.Name = "blankToolStripStatusLabel";
|
||||
this.blankToolStripStatusLabel.Size = new System.Drawing.Size(494, 17);
|
||||
this.blankToolStripStatusLabel.Size = new System.Drawing.Size(240, 17);
|
||||
this.blankToolStripStatusLabel.Spring = true;
|
||||
//
|
||||
// HttpStatusLabel
|
||||
//
|
||||
this.HttpStatusLabel.Name = "HttpStatusLabel";
|
||||
this.HttpStatusLabel.Size = new System.Drawing.Size(41, 17);
|
||||
this.HttpStatusLabel.Text = "HTTP:";
|
||||
this.HttpStatusLabel.TextAlign = System.Drawing.ContentAlignment.BottomLeft;
|
||||
this.HttpStatusLabel.Visible = false;
|
||||
this.HttpStatusLabel.Click += new System.EventHandler(this.TcpStatusLabel_Click);
|
||||
//
|
||||
// NatTypeStatusLabel
|
||||
//
|
||||
this.NatTypeStatusLabel.Name = "NatTypeStatusLabel";
|
||||
@@ -609,19 +620,19 @@
|
||||
this.ExitToolStripButton});
|
||||
this.NotifyMenu.Name = "NotifyMenu";
|
||||
this.NotifyMenu.ShowItemToolTips = false;
|
||||
this.NotifyMenu.Size = new System.Drawing.Size(181, 70);
|
||||
this.NotifyMenu.Size = new System.Drawing.Size(108, 48);
|
||||
//
|
||||
// ShowMainFormToolStripButton
|
||||
//
|
||||
this.ShowMainFormToolStripButton.Name = "ShowMainFormToolStripButton";
|
||||
this.ShowMainFormToolStripButton.Size = new System.Drawing.Size(180, 22);
|
||||
this.ShowMainFormToolStripButton.Size = new System.Drawing.Size(107, 22);
|
||||
this.ShowMainFormToolStripButton.Text = "Show";
|
||||
this.ShowMainFormToolStripButton.Click += new System.EventHandler(this.ShowMainFormToolStripButton_Click);
|
||||
//
|
||||
// ExitToolStripButton
|
||||
//
|
||||
this.ExitToolStripButton.Name = "ExitToolStripButton";
|
||||
this.ExitToolStripButton.Size = new System.Drawing.Size(180, 22);
|
||||
this.ExitToolStripButton.Size = new System.Drawing.Size(107, 22);
|
||||
this.ExitToolStripButton.Text = "Exit";
|
||||
this.ExitToolStripButton.Click += new System.EventHandler(this.ExitToolStripButton_Click);
|
||||
//
|
||||
@@ -792,5 +803,6 @@
|
||||
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
|
||||
private System.Windows.Forms.ContainerControl ButtomControlContainerControl;
|
||||
private System.Windows.Forms.ToolStripMenuItem ShowHideConsoleToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel HttpStatusLabel;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Microsoft.Win32;
|
||||
using Netch.Controllers;
|
||||
using Netch.Enums;
|
||||
@@ -19,7 +23,6 @@ using Netch.Properties;
|
||||
using Netch.Services;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace Netch.Forms
|
||||
{
|
||||
@@ -40,6 +43,9 @@ namespace Netch.Forms
|
||||
|
||||
#region i18N Translations
|
||||
|
||||
if (Flags.NoSupport)
|
||||
_mainFormText.Add(Name, new[] { "{0} ({1})", "Netch", "No Support" });
|
||||
|
||||
_mainFormText.Add(UninstallServiceToolStripMenuItem.Name, new[] { "Uninstall {0}", "NF Service" });
|
||||
|
||||
#endregion
|
||||
@@ -50,7 +56,8 @@ namespace Netch.Forms
|
||||
|
||||
private void AddAddServerToolStripMenuItems()
|
||||
{
|
||||
foreach (var serversUtil in ServerHelper.ServerUtils.Where(i => !string.IsNullOrEmpty(i.FullName)))
|
||||
foreach (var serversUtil in ServerHelper.ServerUtilDictionary.Values.OrderBy(i => i.Priority)
|
||||
.Where(i => !string.IsNullOrEmpty(i.FullName)))
|
||||
{
|
||||
var fullName = serversUtil.FullName;
|
||||
var control = new ToolStripMenuItem
|
||||
@@ -74,7 +81,7 @@ namespace Netch.Forms
|
||||
|
||||
LoadServers();
|
||||
SelectLastServer();
|
||||
ServerHelper.DelayTestHelper.UpdateInterval();
|
||||
DelayTestHelper.UpdateTick(true);
|
||||
|
||||
ModeHelper.InitWatcher();
|
||||
ModeHelper.Load();
|
||||
@@ -84,29 +91,23 @@ namespace Netch.Forms
|
||||
// 加载翻译
|
||||
TranslateControls();
|
||||
|
||||
// 隐藏 NatTypeStatusLabel
|
||||
NatTypeStatusText();
|
||||
// 隐藏 ConnectivityStatusLabel
|
||||
ConnectivityStatusVisible(false);
|
||||
|
||||
// 加载快速配置
|
||||
LoadProfiles();
|
||||
|
||||
BeginInvoke(new Action(async () =>
|
||||
{
|
||||
// 检查更新
|
||||
if (Global.Settings.CheckUpdateWhenOpened)
|
||||
await CheckUpdate();
|
||||
}));
|
||||
// 检查更新
|
||||
if (Global.Settings.CheckUpdateWhenOpened)
|
||||
CheckUpdateAsync().Forget();
|
||||
|
||||
BeginInvoke(new Action(async () =>
|
||||
{
|
||||
// 检查订阅更新
|
||||
if (Global.Settings.UpdateServersWhenOpened)
|
||||
await UpdateServersFromSubscribe();
|
||||
// 检查订阅更新
|
||||
if (Global.Settings.UpdateServersWhenOpened)
|
||||
UpdateServersFromSubscribeAsync().Forget();
|
||||
|
||||
// 打开软件时启动加速,产生开始按钮点击事件
|
||||
if (Global.Settings.StartWhenOpened)
|
||||
ControlButton_Click(null, null);
|
||||
}));
|
||||
// 打开软件时启动加速,产生开始按钮点击事件
|
||||
if (Global.Settings.StartWhenOpened)
|
||||
ControlButton.PerformClick();
|
||||
|
||||
Netch.SingleInstance.ListenForArgumentsFromSuccessiveInstances();
|
||||
}
|
||||
@@ -272,10 +273,10 @@ namespace Netch.Forms
|
||||
|
||||
private async void UpdateServersFromSubscribeLinksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
await UpdateServersFromSubscribe();
|
||||
await UpdateServersFromSubscribeAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateServersFromSubscribe()
|
||||
private async Task UpdateServersFromSubscribeAsync()
|
||||
{
|
||||
void DisableItems(bool v)
|
||||
{
|
||||
@@ -330,7 +331,7 @@ namespace Netch.Forms
|
||||
{
|
||||
UpdateChecker.NewVersionNotFound += OnNewVersionNotFound;
|
||||
UpdateChecker.NewVersionFoundFailed += OnNewVersionFoundFailed;
|
||||
await CheckUpdate();
|
||||
await CheckUpdateAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -391,9 +392,9 @@ namespace Netch.Forms
|
||||
|
||||
private void ShowHideConsoleToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var windowStyles = (User32.WindowStyles)User32.GetWindowLong(Netch.ConsoleHwnd, User32.WindowLongFlags.GWL_STYLE);
|
||||
var visible = windowStyles.HasFlag(User32.WindowStyles.WS_VISIBLE);
|
||||
User32.ShowWindow(Netch.ConsoleHwnd, visible ? ShowWindowCommand.SW_HIDE : ShowWindowCommand.SW_SHOWNOACTIVATE);
|
||||
var windowStyles = (WINDOW_STYLE)PInvoke.GetWindowLong(new HWND(Netch.ConsoleHwnd), WINDOW_LONG_PTR_INDEX.GWL_STYLE);
|
||||
var visible = windowStyles.HasFlag(WINDOW_STYLE.WS_VISIBLE);
|
||||
PInvoke.ShowWindow(Netch.ConsoleHwnd, visible ? SHOW_WINDOW_CMD.SW_HIDE : SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -464,7 +465,7 @@ namespace Netch.Forms
|
||||
}
|
||||
|
||||
ModeHelper.SuspendWatcher = true;
|
||||
await Stop();
|
||||
await StopAsync();
|
||||
await Configuration.SaveAsync();
|
||||
|
||||
// Update
|
||||
@@ -500,7 +501,7 @@ namespace Netch.Forms
|
||||
|
||||
private void fAQToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
Utils.Utils.Open("https://netch.org/#/docs/zh-CN/faq");
|
||||
Utils.Utils.Open("https://docs.netch.org");
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -511,11 +512,11 @@ namespace Netch.Forms
|
||||
{
|
||||
if (!IsWaiting())
|
||||
{
|
||||
await StopCore();
|
||||
await StopCoreAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await Configuration.SaveAsync();
|
||||
Configuration.SaveAsync().Forget();
|
||||
|
||||
// 服务器、模式 需选择
|
||||
if (ServerComboBox.SelectedItem is not Server server)
|
||||
@@ -547,28 +548,32 @@ namespace Netch.Forms
|
||||
State = State.Started;
|
||||
|
||||
Task.Run(Bandwidth.NetTraffic).Forget();
|
||||
Task.Run(NatTest).Forget();
|
||||
DiscoveryNatTypeAsync().Forget();
|
||||
HttpConnectAsync().Forget();
|
||||
|
||||
if (Global.Settings.MinimizeWhenStarted)
|
||||
Minimize();
|
||||
|
||||
// 自动检测延迟
|
||||
Task.Run(() =>
|
||||
async Task StartedPingAsync()
|
||||
{
|
||||
while (State == State.Started)
|
||||
{
|
||||
while (State == State.Started)
|
||||
if (Global.Settings.StartedPingInterval >= 0)
|
||||
{
|
||||
server.Test();
|
||||
ServerComboBox.Refresh();
|
||||
if (Global.Settings.StartedPingInterval >= 0)
|
||||
{
|
||||
await server.PingAsync();
|
||||
ServerComboBox.Refresh();
|
||||
|
||||
Thread.Sleep(Global.Settings.StartedPingInterval * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
})
|
||||
.Forget();
|
||||
await Task.Delay(Global.Settings.StartedPingInterval * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StartedPingAsync().Forget();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -591,7 +596,7 @@ namespace Netch.Forms
|
||||
}
|
||||
|
||||
if (oldSettings.DetectionTick != Global.Settings.DetectionTick)
|
||||
ServerHelper.DelayTestHelper.UpdateInterval();
|
||||
DelayTestHelper.UpdateTick(true);
|
||||
|
||||
if (oldSettings.ProfileCount != Global.Settings.ProfileCount)
|
||||
LoadProfiles();
|
||||
@@ -636,9 +641,6 @@ namespace Netch.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server.Valid())
|
||||
return;
|
||||
|
||||
Hide();
|
||||
ServerHelper.GetUtilByTypeName(server.Type).Edit(server);
|
||||
LoadServers();
|
||||
@@ -646,7 +648,7 @@ namespace Netch.Forms
|
||||
Show();
|
||||
}
|
||||
|
||||
private void SpeedPictureBox_Click(object sender, EventArgs e)
|
||||
private async void SpeedPictureBox_Click(object sender, EventArgs e)
|
||||
{
|
||||
void Enable()
|
||||
{
|
||||
@@ -660,19 +662,13 @@ namespace Netch.Forms
|
||||
|
||||
if (!IsWaiting() || ModifierKeys == Keys.Control)
|
||||
{
|
||||
(ServerComboBox.SelectedItem as Server)?.Test();
|
||||
(ServerComboBox.SelectedItem as Server)?.PingAsync();
|
||||
Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerHelper.DelayTestHelper.TestDelayFinished += OnTestDelayFinished;
|
||||
Task.Run(ServerHelper.DelayTestHelper.TestAllDelay).Forget();
|
||||
|
||||
void OnTestDelayFinished(object? o1, EventArgs? e1)
|
||||
{
|
||||
ServerHelper.DelayTestHelper.TestDelayFinished -= OnTestDelayFinished;
|
||||
Enable();
|
||||
}
|
||||
await DelayTestHelper.PerformTestAsync(true);
|
||||
Enable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,9 +681,6 @@ namespace Netch.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server.Valid())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
//听说巨硬BUG经常会炸,所以Catch一下 :D
|
||||
@@ -1001,12 +994,13 @@ namespace Netch.Forms
|
||||
EditServerPictureBox.Enabled = DeleteModePictureBox.Enabled = DeleteServerPictureBox.Enabled = enabled;
|
||||
|
||||
// 启动需要禁用的控件
|
||||
UninstallServiceToolStripMenuItem.Enabled = UpdateServersFromSubscribeLinksToolStripMenuItem.Enabled = enabled;
|
||||
ServerToolStripMenuItem.Enabled = ModeToolStripMenuItem.Enabled =
|
||||
SubscribeToolStripMenuItem.Enabled = UninstallServiceToolStripMenuItem.Enabled = enabled;
|
||||
}
|
||||
|
||||
_state = value;
|
||||
|
||||
ServerHelper.DelayTestHelper.Enabled = IsWaiting(_state);
|
||||
DelayTestHelper.Enabled = IsWaiting(_state);
|
||||
|
||||
StatusText();
|
||||
switch (value)
|
||||
@@ -1036,7 +1030,7 @@ namespace Netch.Forms
|
||||
|
||||
ProfileGroupBox.Enabled = false;
|
||||
BandwidthState(false);
|
||||
NatTypeStatusText();
|
||||
ConnectivityStatusVisible(false);
|
||||
break;
|
||||
case State.Stopped:
|
||||
ControlButton.Enabled = true;
|
||||
@@ -1053,29 +1047,28 @@ namespace Netch.Forms
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (IsWaiting())
|
||||
return;
|
||||
|
||||
await StopCore();
|
||||
await StopCoreAsync();
|
||||
}
|
||||
|
||||
private async Task StopCore()
|
||||
private async Task StopCoreAsync()
|
||||
{
|
||||
State = State.Stopping;
|
||||
_discoveryNatCts?.Cancel();
|
||||
_httpConnectCts?.Cancel();
|
||||
await MainController.StopAsync();
|
||||
State = State.Stopped;
|
||||
}
|
||||
|
||||
private bool IsWaiting()
|
||||
{
|
||||
return State == State.Waiting || State == State.Stopped;
|
||||
}
|
||||
private bool IsWaiting() => IsWaiting(_state);
|
||||
|
||||
private static bool IsWaiting(State state)
|
||||
{
|
||||
return state == State.Waiting || state == State.Stopped;
|
||||
return state is State.Waiting or State.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1091,9 +1084,10 @@ namespace Netch.Forms
|
||||
}
|
||||
|
||||
text ??= i18N.Translate(StateExtension.GetStatusString(State));
|
||||
StatusLabel.Text = i18N.Translate("Status", ": ") + text;
|
||||
if (_state == State.Started)
|
||||
StatusLabel.Text += StatusPortInfoText.Value;
|
||||
text += StatusPortInfoText.Value;
|
||||
|
||||
StatusLabel.Text = i18N.Translate("Status", ": ") + text;
|
||||
}
|
||||
|
||||
public void BandwidthState(bool state)
|
||||
@@ -1110,24 +1104,14 @@ namespace Netch.Forms
|
||||
UsedBandwidthLabel.Visible /*= UploadSpeedLabel.Visible*/ = DownloadSpeedLabel.Visible = state;
|
||||
}
|
||||
|
||||
public void NatTypeStatusText(string? text = null, string? country = null)
|
||||
private void UpdateNatTypeStatusLabelText(string? text, string? country = null)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action<string, string>(NatTypeStatusText), text, country);
|
||||
return;
|
||||
}
|
||||
|
||||
if (State != State.Started)
|
||||
{
|
||||
NatTypeStatusLabel.Text = "";
|
||||
NatTypeStatusLabel.Visible = NatTypeStatusLightLabel.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} {(!country.IsNullOrEmpty() ? $"[{country}]" : "")}";
|
||||
if (country == null)
|
||||
NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} ";
|
||||
else
|
||||
NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} [{country}]";
|
||||
|
||||
UpdateNatTypeLight(int.TryParse(text, out var natType) ? natType : -1);
|
||||
}
|
||||
@@ -1139,10 +1123,18 @@ namespace Netch.Forms
|
||||
NatTypeStatusLabel.Visible = true;
|
||||
}
|
||||
|
||||
private void ConnectivityStatusVisible(bool visible)
|
||||
{
|
||||
if (!visible)
|
||||
HttpStatusLabel.Text = NatTypeStatusLabel.Text = "";
|
||||
|
||||
HttpStatusLabel.Visible = NatTypeStatusLabel.Visible = NatTypeStatusLightLabel.Visible = visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 NAT指示灯颜色
|
||||
/// </summary>
|
||||
/// <param name="natType"></param>
|
||||
/// <param name="natType">NAT Type. keep default(-1) to Hide Light</param>
|
||||
private void UpdateNatTypeLight(int natType = -1)
|
||||
{
|
||||
if (natType > 0 && natType < 5)
|
||||
@@ -1165,46 +1157,81 @@ namespace Netch.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private async void NatTypeStatusLabel_Click(object sender, EventArgs e)
|
||||
private async void TcpStatusLabel_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (_state == State.Started && !Monitor.IsEntered(_natTestLock))
|
||||
await NatTest();
|
||||
await HttpConnectAsync();
|
||||
}
|
||||
|
||||
private bool _natTestLock = true;
|
||||
|
||||
/// <summary>
|
||||
/// 测试 NAT
|
||||
/// </summary>
|
||||
private async Task NatTest()
|
||||
private async void NatTypeStatusLabel_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!MainController.Mode!.TestNatRequired())
|
||||
return;
|
||||
await DiscoveryNatTypeAsync();
|
||||
}
|
||||
|
||||
if (!_natTestLock)
|
||||
return;
|
||||
private CancellationTokenSource? _discoveryNatCts;
|
||||
|
||||
_natTestLock = false;
|
||||
private async Task DiscoveryNatTypeAsync()
|
||||
{
|
||||
NatTypeStatusLabel.Enabled = false;
|
||||
UpdateNatTypeStatusLabelText("Testing NAT Type");
|
||||
|
||||
_discoveryNatCts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
NatTypeStatusText(i18N.Translate("Testing NAT"));
|
||||
var res = await MainController.DiscoveryNatTypeAsync(_discoveryNatCts.Token);
|
||||
if (_discoveryNatCts.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var (result, _, publicEnd) = await MainController.NTTController.Start();
|
||||
|
||||
if (!string.IsNullOrEmpty(publicEnd))
|
||||
if (!string.IsNullOrEmpty(res.PublicEnd))
|
||||
{
|
||||
var country = Utils.Utils.GetCityCode(publicEnd!);
|
||||
NatTypeStatusText(result, country);
|
||||
var country = await Utils.Utils.GetCityCodeAsync(res.PublicEnd);
|
||||
|
||||
UpdateNatTypeStatusLabelText(res.Result, country);
|
||||
if (int.TryParse(res.Result, out var natType))
|
||||
UpdateNatTypeLight(natType);
|
||||
else
|
||||
UpdateNatTypeLight();
|
||||
}
|
||||
else
|
||||
{
|
||||
NatTypeStatusText(result ?? "Error");
|
||||
UpdateNatTypeStatusLabelText(res.Result ?? "Error");
|
||||
NatTypeStatusLightLabel.Visible = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_natTestLock = true;
|
||||
_discoveryNatCts.Dispose();
|
||||
_discoveryNatCts = null;
|
||||
NatTypeStatusLabel.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource? _httpConnectCts;
|
||||
|
||||
private async Task HttpConnectAsync()
|
||||
{
|
||||
HttpStatusLabel.Enabled = false;
|
||||
|
||||
_httpConnectCts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
var res = await MainController.HttpConnectAsync(_httpConnectCts.Token);
|
||||
if (_httpConnectCts.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (res != null)
|
||||
HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ")}{res}ms";
|
||||
else
|
||||
HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ", "Timeout")}";
|
||||
|
||||
HttpStatusLabel.Visible = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_httpConnectCts.Dispose();
|
||||
_httpConnectCts = null;
|
||||
HttpStatusLabel.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1270,7 +1297,7 @@ namespace Netch.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
State = State.Terminating;
|
||||
// State = State.Terminating;
|
||||
NotifyIcon.Visible = false;
|
||||
Hide();
|
||||
|
||||
@@ -1281,7 +1308,7 @@ namespace Netch.Forms
|
||||
if (File.Exists(file))
|
||||
File.Delete(file);
|
||||
|
||||
await Stop();
|
||||
await StopAsync();
|
||||
|
||||
Dispose();
|
||||
Environment.Exit(Environment.ExitCode);
|
||||
@@ -1311,12 +1338,12 @@ namespace Netch.Forms
|
||||
|
||||
#region Updater
|
||||
|
||||
private async Task CheckUpdate()
|
||||
private async Task CheckUpdateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateChecker.NewVersionFound += OnUpdateCheckerOnNewVersionFound;
|
||||
await UpdateChecker.Check(Global.Settings.CheckBetaUpdate);
|
||||
await UpdateChecker.CheckAsync(Global.Settings.CheckBetaUpdate);
|
||||
if (Flags.AlwaysShowNewVersionFound)
|
||||
OnUpdateCheckerOnNewVersionFound(null!, null!);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Netch.Models;
|
||||
using Netch.Utils;
|
||||
using Netch.Utils;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Netch.Enums;
|
||||
|
||||
namespace Netch.Forms
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Netch.Forms
|
||||
AddSaveButton();
|
||||
i18N.TranslateForm(this);
|
||||
|
||||
ConfigurationGroupBox.Enabled = string.IsNullOrEmpty(Server.Remark);
|
||||
ConfigurationGroupBox.Enabled = !Server.IsInGroup();
|
||||
|
||||
ConfigurationGroupBox.ResumeLayout(false);
|
||||
ConfigurationGroupBox.PerformLayout();
|
||||
|
||||
47
Netch/Forms/SettingForm.Designer.cs
generated
47
Netch/Forms/SettingForm.Designer.cs
generated
@@ -39,7 +39,6 @@ namespace Netch.Forms
|
||||
this.HTTPPortLabel = new System.Windows.Forms.Label();
|
||||
this.HTTPPortTextBox = new System.Windows.Forms.TextBox();
|
||||
this.AllowDevicesCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.ResolveServerHostnameCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.ServerPingTypeLabel = new System.Windows.Forms.Label();
|
||||
this.ICMPingRadioBtn = new System.Windows.Forms.RadioButton();
|
||||
this.TCPingRadioBtn = new System.Windows.Forms.RadioButton();
|
||||
@@ -61,7 +60,6 @@ namespace Netch.Forms
|
||||
this.FilterICMPCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.ICMPDelayLabel = new System.Windows.Forms.Label();
|
||||
this.ICMPDelayTextBox = new System.Windows.Forms.TextBox();
|
||||
this.RedirectorSSCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.ChildProcessHandleCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.WinTUNTabPage = new System.Windows.Forms.TabPage();
|
||||
this.WinTUNGroupBox = new System.Windows.Forms.GroupBox();
|
||||
@@ -99,6 +97,7 @@ namespace Netch.Forms
|
||||
this.StopWhenExitedCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.StartWhenOpenedCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.MinimizeWhenStartedCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.NoSupportDialogCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.RunAtStartupCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.CheckUpdateWhenOpenedCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.CheckBetaUpdateCheckBox = new System.Windows.Forms.CheckBox();
|
||||
@@ -144,7 +143,6 @@ namespace Netch.Forms
|
||||
//
|
||||
this.GeneralTabPage.BackColor = System.Drawing.SystemColors.ButtonFace;
|
||||
this.GeneralTabPage.Controls.Add(this.PortGroupBox);
|
||||
this.GeneralTabPage.Controls.Add(this.ResolveServerHostnameCheckBox);
|
||||
this.GeneralTabPage.Controls.Add(this.ServerPingTypeLabel);
|
||||
this.GeneralTabPage.Controls.Add(this.ICMPingRadioBtn);
|
||||
this.GeneralTabPage.Controls.Add(this.TCPingRadioBtn);
|
||||
@@ -224,20 +222,10 @@ namespace Netch.Forms
|
||||
this.AllowDevicesCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
this.AllowDevicesCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// ResolveServerHostnameCheckBox
|
||||
//
|
||||
this.ResolveServerHostnameCheckBox.AutoSize = true;
|
||||
this.ResolveServerHostnameCheckBox.Location = new System.Drawing.Point(267, 15);
|
||||
this.ResolveServerHostnameCheckBox.Name = "ResolveServerHostnameCheckBox";
|
||||
this.ResolveServerHostnameCheckBox.Size = new System.Drawing.Size(176, 21);
|
||||
this.ResolveServerHostnameCheckBox.TabIndex = 1;
|
||||
this.ResolveServerHostnameCheckBox.Text = "Resolve Server Hostname";
|
||||
this.ResolveServerHostnameCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// ServerPingTypeLabel
|
||||
//
|
||||
this.ServerPingTypeLabel.AutoSize = true;
|
||||
this.ServerPingTypeLabel.Location = new System.Drawing.Point(267, 44);
|
||||
this.ServerPingTypeLabel.Location = new System.Drawing.Point(267, 15);
|
||||
this.ServerPingTypeLabel.Name = "ServerPingTypeLabel";
|
||||
this.ServerPingTypeLabel.Size = new System.Drawing.Size(86, 17);
|
||||
this.ServerPingTypeLabel.TabIndex = 2;
|
||||
@@ -246,7 +234,7 @@ namespace Netch.Forms
|
||||
// ICMPingRadioBtn
|
||||
//
|
||||
this.ICMPingRadioBtn.AutoSize = true;
|
||||
this.ICMPingRadioBtn.Location = new System.Drawing.Point(268, 63);
|
||||
this.ICMPingRadioBtn.Location = new System.Drawing.Point(268, 34);
|
||||
this.ICMPingRadioBtn.Name = "ICMPingRadioBtn";
|
||||
this.ICMPingRadioBtn.Size = new System.Drawing.Size(75, 21);
|
||||
this.ICMPingRadioBtn.TabIndex = 3;
|
||||
@@ -257,7 +245,7 @@ namespace Netch.Forms
|
||||
// TCPingRadioBtn
|
||||
//
|
||||
this.TCPingRadioBtn.AutoSize = true;
|
||||
this.TCPingRadioBtn.Location = new System.Drawing.Point(366, 64);
|
||||
this.TCPingRadioBtn.Location = new System.Drawing.Point(366, 35);
|
||||
this.TCPingRadioBtn.Name = "TCPingRadioBtn";
|
||||
this.TCPingRadioBtn.Size = new System.Drawing.Size(66, 21);
|
||||
this.TCPingRadioBtn.TabIndex = 4;
|
||||
@@ -361,7 +349,6 @@ namespace Netch.Forms
|
||||
this.NFTabPage.Controls.Add(this.FilterICMPCheckBox);
|
||||
this.NFTabPage.Controls.Add(this.ICMPDelayLabel);
|
||||
this.NFTabPage.Controls.Add(this.ICMPDelayTextBox);
|
||||
this.NFTabPage.Controls.Add(this.RedirectorSSCheckBox);
|
||||
this.NFTabPage.Controls.Add(this.ChildProcessHandleCheckBox);
|
||||
this.NFTabPage.Location = new System.Drawing.Point(4, 29);
|
||||
this.NFTabPage.Name = "NFTabPage";
|
||||
@@ -435,21 +422,11 @@ namespace Netch.Forms
|
||||
this.ICMPDelayTextBox.TabIndex = 6;
|
||||
this.ICMPDelayTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// RedirectorSSCheckBox
|
||||
//
|
||||
this.RedirectorSSCheckBox.AutoSize = true;
|
||||
this.RedirectorSSCheckBox.Location = new System.Drawing.Point(15, 140);
|
||||
this.RedirectorSSCheckBox.Name = "RedirectorSSCheckBox";
|
||||
this.RedirectorSSCheckBox.Size = new System.Drawing.Size(265, 21);
|
||||
this.RedirectorSSCheckBox.TabIndex = 7;
|
||||
this.RedirectorSSCheckBox.Text = "Redirector built-in Shadowsocks support";
|
||||
this.RedirectorSSCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// ChildProcessHandleCheckBox
|
||||
//
|
||||
this.ChildProcessHandleCheckBox.AutoSize = true;
|
||||
this.ChildProcessHandleCheckBox.Enabled = false;
|
||||
this.ChildProcessHandleCheckBox.Location = new System.Drawing.Point(15, 170);
|
||||
this.ChildProcessHandleCheckBox.Location = new System.Drawing.Point(15, 140);
|
||||
this.ChildProcessHandleCheckBox.Name = "ChildProcessHandleCheckBox";
|
||||
this.ChildProcessHandleCheckBox.Size = new System.Drawing.Size(150, 21);
|
||||
this.ChildProcessHandleCheckBox.TabIndex = 8;
|
||||
@@ -771,6 +748,7 @@ namespace Netch.Forms
|
||||
this.OtherTabPage.Controls.Add(this.StopWhenExitedCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.StartWhenOpenedCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.MinimizeWhenStartedCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.NoSupportDialogCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.RunAtStartupCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.CheckUpdateWhenOpenedCheckBox);
|
||||
this.OtherTabPage.Controls.Add(this.CheckBetaUpdateCheckBox);
|
||||
@@ -825,6 +803,16 @@ namespace Netch.Forms
|
||||
this.MinimizeWhenStartedCheckBox.Text = "Minimize when started";
|
||||
this.MinimizeWhenStartedCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// NoSupportDialogCheckBox
|
||||
//
|
||||
this.NoSupportDialogCheckBox.AutoSize = true;
|
||||
this.NoSupportDialogCheckBox.Location = new System.Drawing.Point(6, 72);
|
||||
this.NoSupportDialogCheckBox.Name = "NoSupportDialogCheckBox";
|
||||
this.NoSupportDialogCheckBox.Size = new System.Drawing.Size(174, 21);
|
||||
this.NoSupportDialogCheckBox.TabIndex = 4;
|
||||
this.NoSupportDialogCheckBox.Text = "Disable Support Warning";
|
||||
this.NoSupportDialogCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// RunAtStartupCheckBox
|
||||
//
|
||||
this.RunAtStartupCheckBox.AutoSize = true;
|
||||
@@ -1012,7 +1000,6 @@ namespace Netch.Forms
|
||||
private System.Windows.Forms.TextBox HTTPPortTextBox;
|
||||
private System.Windows.Forms.Label Socks5PortLabel;
|
||||
private System.Windows.Forms.TextBox Socks5PortTextBox;
|
||||
private System.Windows.Forms.CheckBox ResolveServerHostnameCheckBox;
|
||||
private System.Windows.Forms.GroupBox WinTUNGroupBox;
|
||||
private System.Windows.Forms.CheckBox ProxyDNSCheckBox;
|
||||
private System.Windows.Forms.CheckBox UseCustomDNSCheckBox;
|
||||
@@ -1070,7 +1057,6 @@ namespace Netch.Forms
|
||||
private System.Windows.Forms.TextBox OtherDNSTextBox;
|
||||
private System.Windows.Forms.TextBox ChinaDNSTextBox;
|
||||
private System.Windows.Forms.TextBox DNSHijackHostTextBox;
|
||||
private System.Windows.Forms.CheckBox RedirectorSSCheckBox;
|
||||
private System.Windows.Forms.Label ServerPingTypeLabel;
|
||||
private System.Windows.Forms.RadioButton TCPingRadioBtn;
|
||||
private System.Windows.Forms.RadioButton ICMPingRadioBtn;
|
||||
@@ -1080,5 +1066,6 @@ namespace Netch.Forms
|
||||
private System.Windows.Forms.CheckBox ChildProcessHandleCheckBox;
|
||||
private System.Windows.Forms.TextBox ICMPDelayTextBox;
|
||||
private System.Windows.Forms.Label ICMPDelayLabel;
|
||||
private System.Windows.Forms.CheckBox NoSupportDialogCheckBox;
|
||||
}
|
||||
}
|
||||
@@ -40,15 +40,13 @@ namespace Netch.Forms
|
||||
c => Global.Settings.LocalAddress = AllowDevicesCheckBox.Checked ? "0.0.0.0" : "127.0.0.1",
|
||||
Global.Settings.LocalAddress switch { "127.0.0.1" => false, "0.0.0.0" => true, _ => false });
|
||||
|
||||
BindCheckBox(ResolveServerHostnameCheckBox, c => Global.Settings.ResolveServerHostname = c, Global.Settings.ResolveServerHostname);
|
||||
|
||||
BindRadioBox(ICMPingRadioBtn, _ => { }, !Global.Settings.ServerTCPing);
|
||||
|
||||
BindRadioBox(TCPingRadioBtn, c => Global.Settings.ServerTCPing = c, Global.Settings.ServerTCPing);
|
||||
|
||||
BindTextBox<int>(ProfileCountTextBox, i => i > -1, i => Global.Settings.ProfileCount = i, Global.Settings.ProfileCount);
|
||||
BindTextBox<int>(DetectionTickTextBox,
|
||||
i => ServerHelper.DelayTestHelper.Range.InRange(i),
|
||||
i => DelayTestHelper.Range.InRange(i),
|
||||
i => Global.Settings.DetectionTick = i,
|
||||
Global.Settings.DetectionTick);
|
||||
|
||||
@@ -112,8 +110,6 @@ namespace Netch.Forms
|
||||
|
||||
BindTextBox(ICMPDelayTextBox, s => int.TryParse(s, out _), s => { }, Global.Settings.Redirector.ICMPDelay);
|
||||
|
||||
BindCheckBox(RedirectorSSCheckBox, s => Global.Settings.Redirector.RedirectorSS = s, Global.Settings.Redirector.RedirectorSS);
|
||||
|
||||
BindCheckBox(ChildProcessHandleCheckBox,
|
||||
s => Global.Settings.Redirector.ChildProcessHandle = s,
|
||||
Global.Settings.Redirector.ChildProcessHandle);
|
||||
@@ -205,6 +201,8 @@ namespace Netch.Forms
|
||||
|
||||
BindCheckBox(UpdateServersWhenOpenedCheckBox, b => Global.Settings.UpdateServersWhenOpened = b, Global.Settings.UpdateServersWhenOpened);
|
||||
|
||||
BindCheckBox(NoSupportDialogCheckBox, b => Global.Settings.NoSupportDialog = b, Global.Settings.NoSupportDialog);
|
||||
|
||||
#endregion
|
||||
|
||||
#region AioDNS
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
namespace Netch.Interfaces
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Netch.Interfaces
|
||||
{
|
||||
public interface IController
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public void Stop();
|
||||
public Task StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
|
||||
namespace Netch.Interfaces
|
||||
{
|
||||
public interface IModeController : IController
|
||||
{
|
||||
public void Start(Server server, Mode mode);
|
||||
public Task StartAsync(Socks5Server server, Mode mode);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Netch.Models;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
|
||||
namespace Netch.Interfaces
|
||||
@@ -9,7 +10,7 @@ namespace Netch.Interfaces
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public Socks5 Start(in Server s);
|
||||
public Task<Socks5LocalServer> StartAsync(Server s);
|
||||
}
|
||||
|
||||
public static class ServerControllerExtension
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Interops
|
||||
@@ -10,10 +11,20 @@ namespace Netch.Interops
|
||||
|
||||
public static bool Dial(NameList name, string value)
|
||||
{
|
||||
Log.Debug($"[aiodns] Dial {name}: {value}");
|
||||
Log.Verbose($"[aiodns] Dial {name}: {value}");
|
||||
return aiodns_dial(name, Encoding.UTF8.GetBytes(value));
|
||||
}
|
||||
|
||||
public static async Task<bool> InitAsync()
|
||||
{
|
||||
return await Task.Run(Init).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task FreeAsync()
|
||||
{
|
||||
await Task.Run(Free).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[DllImport(aiodns_bin, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aiodns_dial(NameList name, byte[] value);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Interops
|
||||
@@ -43,18 +44,18 @@ namespace Netch.Interops
|
||||
|
||||
public static bool Dial(NameList name, string value)
|
||||
{
|
||||
Log.Debug($"[Redirector] Dial {name}: {value}");
|
||||
Log.Verbose($"[Redirector] Dial {name}: {value}");
|
||||
return aio_dial(name, value);
|
||||
}
|
||||
|
||||
public static bool Init()
|
||||
public static async Task<bool> InitAsync()
|
||||
{
|
||||
return aio_init();
|
||||
return await Task.Run(aio_init).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static bool Free()
|
||||
public static async Task<bool> FreeAsync()
|
||||
{
|
||||
return aio_free();
|
||||
return await Task.Run(aio_free).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public const int UdpNameListOffset = (int)NameList.TYPE_UDPLISN - (int)NameList.TYPE_TCPLISN;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Interops
|
||||
@@ -36,19 +37,19 @@ namespace Netch.Interops
|
||||
|
||||
public static bool Dial(NameList name, string value)
|
||||
{
|
||||
Log.Debug( $"[tun2socks] Dial {name}: {value}");
|
||||
Log.Verbose( $"[tun2socks] Dial {name}: {value}");
|
||||
return tun_dial(name, Encoding.UTF8.GetBytes(value));
|
||||
}
|
||||
|
||||
public static bool Init()
|
||||
{
|
||||
Log.Debug("[tun2socks] init");
|
||||
Log.Verbose("[tun2socks] init");
|
||||
return tun_init();
|
||||
}
|
||||
|
||||
public static bool Free()
|
||||
public static async Task<bool> FreeAsync()
|
||||
{
|
||||
return tun_free();
|
||||
return await Task.Run(tun_free).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private const string tun2socks_bin = "tun2socks.bin";
|
||||
|
||||
49
Netch/Models/Arguments.cs
Normal file
49
Netch/Models/Arguments.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Models
|
||||
{
|
||||
public static class Arguments
|
||||
{
|
||||
public static string Format(IEnumerable<object?> a)
|
||||
{
|
||||
var arguments = a.ToList();
|
||||
if (arguments.Count % 2 != 0)
|
||||
throw new FormatException("missing last argument value");
|
||||
|
||||
var tokens = new List<string>();
|
||||
|
||||
for (var i = 0; i < arguments.Count; i += 2)
|
||||
{
|
||||
var keyObj = arguments[i];
|
||||
var valueObj = arguments[i + 1];
|
||||
|
||||
if (keyObj is not string key)
|
||||
throw new FormatException($"argument key at array index {i} is not string");
|
||||
|
||||
switch (valueObj)
|
||||
{
|
||||
case SpecialArgument.Flag:
|
||||
tokens.Add(key);
|
||||
break;
|
||||
case null:
|
||||
case string value when value.IsNullOrWhiteSpace():
|
||||
continue;
|
||||
default:
|
||||
tokens.Add(key);
|
||||
tokens.Add(valueObj.ToString()!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(' ', tokens);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SpecialArgument
|
||||
{
|
||||
Flag
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Netch.Models.GitHubRelease
|
||||
{
|
||||
|
||||
@@ -120,14 +120,4 @@ namespace Netch.Models
|
||||
return $"[{(int)Type + 1}] {i18N.Translate(Remark)}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModeExtension
|
||||
{
|
||||
/// 是否会转发 UDP
|
||||
public static bool TestNatRequired(this Mode mode)
|
||||
{
|
||||
return mode.Type is ModeType.Process && Global.Settings.Redirector.FilterProtocol.HasFlag(PortType.UDP) ||
|
||||
mode.Type is ModeType.BypassRuleIPs;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Netch/Models/NatTypeTestResult.cs
Normal file
9
Netch/Models/NatTypeTestResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Netch.Models
|
||||
{
|
||||
public struct NatTypeTestResult
|
||||
{
|
||||
public string? Result;
|
||||
public string? LocalEnd;
|
||||
public string? PublicEnd;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.Win32;
|
||||
|
||||
namespace Netch.Models
|
||||
{
|
||||
@@ -18,10 +18,10 @@ namespace Netch.Models
|
||||
|
||||
public static NetRoute GetBestRouteTemplate()
|
||||
{
|
||||
if (IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route) != 0)
|
||||
if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route) != 0)
|
||||
throw new MessageException("GetBestRoute 搜索失败");
|
||||
|
||||
var gateway = new IPAddress(route.dwForwardNextHop.S_un_b);
|
||||
var gateway = new IPAddress(route.dwForwardNextHop);
|
||||
return TemplateBuilder(gateway.ToString(), (int)route.dwForwardIfIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Netch.Models
|
||||
{
|
||||
public abstract class ParameterBase
|
||||
{
|
||||
// null value par
|
||||
|
||||
private readonly bool _full;
|
||||
|
||||
protected readonly string ParametersSeparate = " ";
|
||||
protected readonly string Separate = " ";
|
||||
protected readonly string VerbPrefix = "-";
|
||||
protected readonly string FullPrefix = "--";
|
||||
|
||||
protected ParameterBase()
|
||||
{
|
||||
_full = !GetType().IsDefined(typeof(VerbAttribute));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var parameters = GetType().GetProperties().Select(PropToParameter).Where(s => s != null).Cast<string>();
|
||||
return string.Join(ParametersSeparate, parameters).Trim();
|
||||
}
|
||||
|
||||
private string? PropToParameter(PropertyInfo p)
|
||||
{
|
||||
// prefix
|
||||
bool full;
|
||||
if (p.IsDefined(typeof(VerbAttribute)))
|
||||
full = false;
|
||||
else if (p.IsDefined(typeof(FullAttribute)))
|
||||
full = true;
|
||||
else
|
||||
full = _full;
|
||||
|
||||
var prefix = full ? FullPrefix : VerbPrefix;
|
||||
// key
|
||||
var key = p.GetCustomAttribute<RealNameAttribute>()?.Name ?? p.Name;
|
||||
|
||||
// build
|
||||
var value = p.GetValue(this);
|
||||
switch (value)
|
||||
{
|
||||
case bool b:
|
||||
return b ? $"{prefix}{key}" : null;
|
||||
default:
|
||||
if (string.IsNullOrWhiteSpace(value?.ToString()))
|
||||
return p.IsDefined(typeof(OptionalAttribute)) ? null : throw new RequiredArgumentValueInvalidException(p.Name, this, null);
|
||||
|
||||
if (p.IsDefined(typeof(QuoteAttribute)))
|
||||
value = $"\"{value}\"";
|
||||
|
||||
return $"{prefix}{key}{Separate}{value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public class VerbAttribute : Attribute
|
||||
{
|
||||
// Don't use verb and full both on one class or property
|
||||
// if you did, will take verb
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public class FullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OptionalAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class QuoteAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class RealNameAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public RealNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RequiredArgumentValueInvalidException : Exception
|
||||
{
|
||||
public string? ArgumentName { get; }
|
||||
|
||||
public object? ArgumentObject { get; }
|
||||
|
||||
private readonly string? _message;
|
||||
|
||||
private const string DefaultMessage = "{0}'s Argument \"{1}\" value invalid. A required argument's value can't be null or empty.";
|
||||
|
||||
public override string Message => _message ?? string.Format(DefaultMessage, ArgumentObject!.GetType(), ArgumentName);
|
||||
|
||||
public RequiredArgumentValueInvalidException()
|
||||
{
|
||||
_message = "Some Argument value invalid. A required argument value's can't be null or empty.";
|
||||
}
|
||||
|
||||
public RequiredArgumentValueInvalidException(string argumentName, object argumentObject, string? message)
|
||||
{
|
||||
ArgumentName = argumentName;
|
||||
ArgumentObject = argumentObject;
|
||||
_message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Utils;
|
||||
@@ -17,7 +17,7 @@ namespace Netch.Models
|
||||
/// <summary>
|
||||
/// 组
|
||||
/// </summary>
|
||||
public string Group { get; set; } = "None";
|
||||
public string Group { get; set; } = Constants.DefaultGroup;
|
||||
|
||||
/// <summary>
|
||||
/// 地址
|
||||
@@ -42,12 +42,7 @@ namespace Netch.Models
|
||||
/// <summary>
|
||||
/// 代理类型
|
||||
/// </summary>
|
||||
public virtual string Type { get; } = string.Empty;
|
||||
|
||||
[JsonExtensionData]
|
||||
// ReSharper disable once CollectionNeverUpdated.Global
|
||||
public Dictionary<string, object> ExtensionData { get; set; } = new();
|
||||
|
||||
public abstract string Type { get; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
@@ -62,56 +57,48 @@ namespace Netch.Models
|
||||
{
|
||||
var remark = string.IsNullOrWhiteSpace(Remark) ? $"{Hostname}:{Port}" : Remark;
|
||||
|
||||
if (Group.Equals("None") || Group.Equals(""))
|
||||
Group = "NONE";
|
||||
|
||||
string shortName;
|
||||
if (Type == string.Empty)
|
||||
{
|
||||
shortName = "WTF";
|
||||
}
|
||||
else
|
||||
{
|
||||
shortName = ServerHelper.GetUtilByTypeName(Type).ShortName;
|
||||
}
|
||||
var shortName = ServerHelper.GetUtilByTypeName(Type).ShortName;
|
||||
|
||||
return $"[{shortName}][{Group}] {remark}";
|
||||
}
|
||||
|
||||
public abstract string MaskedData();
|
||||
|
||||
/// <summary>
|
||||
/// 测试延迟
|
||||
/// </summary>
|
||||
/// <returns>延迟</returns>
|
||||
public int Test()
|
||||
public async Task<int> PingAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var destination = DnsUtils.Lookup(Hostname);
|
||||
var destination = await DnsUtils.LookupAsync(Hostname);
|
||||
if (destination == null)
|
||||
return Delay = -2;
|
||||
|
||||
var list = new Task<int>[3];
|
||||
for (var i = 0; i < 3; i++)
|
||||
list[i] = Task.Run(async () =>
|
||||
{
|
||||
async Task<int> PingCoreAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Global.Settings.ServerTCPing
|
||||
? await Utils.Utils.TCPingAsync(destination, Port)
|
||||
: Utils.Utils.ICMPing(destination, Port);
|
||||
: await Utils.Utils.ICMPingAsync(destination);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return -4;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Task.WaitAll(list[0], list[1], list[2]);
|
||||
list[i] = PingCoreAsync();
|
||||
}
|
||||
|
||||
var min = Math.Min(list[0].Result, list[1].Result);
|
||||
min = Math.Min(min, list[2].Result);
|
||||
return Delay = min;
|
||||
var resTask = await Task.WhenAny(list[0], list[1], list[2]);
|
||||
|
||||
return Delay = await resTask;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -122,22 +109,15 @@ namespace Netch.Models
|
||||
|
||||
public static class ServerExtension
|
||||
{
|
||||
public static string AutoResolveHostname(this Server server)
|
||||
public static async Task<string> AutoResolveHostnameAsync(this Server server, AddressFamily inet = AddressFamily.Unspecified)
|
||||
{
|
||||
return Global.Settings.ResolveServerHostname ? DnsUtils.Lookup(server.Hostname)!.ToString() : server.Hostname;
|
||||
// ! MainController cached
|
||||
return (await DnsUtils.LookupAsync(server.Hostname, inet))!.ToString();
|
||||
}
|
||||
|
||||
public static bool Valid(this Server server)
|
||||
public static bool IsInGroup(this Server server)
|
||||
{
|
||||
try
|
||||
{
|
||||
ServerHelper.GetTypeByTypeName(server.Type);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return server.Group is not Constants.DefaultGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Netch.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Models
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Netch.Models
|
||||
/// <summary>
|
||||
/// 使用自定义 DNS 设置
|
||||
/// </summary>
|
||||
public bool UseCustomDNS { get; set; } = true;
|
||||
public bool UseCustomDNS { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 全局绕过 IP 列表
|
||||
@@ -72,7 +72,7 @@ namespace Netch.Models
|
||||
|
||||
public bool V2rayNShareLink { get; set; } = true;
|
||||
|
||||
public bool XrayCone { get; set; } = false;
|
||||
public bool XrayCone { get; set; } = true;
|
||||
}
|
||||
|
||||
public class AioDNSConfig
|
||||
@@ -106,14 +106,10 @@ namespace Netch.Models
|
||||
|
||||
public bool FilterICMP { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用RDR内置SS
|
||||
/// </summary>
|
||||
public bool RedirectorSS { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否代理子进程
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool ChildProcessHandle { get; set; } = false;
|
||||
}
|
||||
|
||||
@@ -196,11 +192,6 @@ namespace Netch.Models
|
||||
/// </summary>
|
||||
public int RequestTimeout { get; set; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// 解析服务器主机名
|
||||
/// </summary>
|
||||
public bool ResolveServerHostname { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否开机启动软件
|
||||
/// </summary>
|
||||
@@ -263,6 +254,8 @@ namespace Netch.Models
|
||||
|
||||
public V2rayConfig V2RayConfig { get; set; } = new();
|
||||
|
||||
public bool NoSupportDialog { get; set; } = false;
|
||||
|
||||
public Setting Clone()
|
||||
{
|
||||
return (Setting)MemberwiseClone();
|
||||
|
||||
21
Netch/NativeMethods.txt
Normal file
21
Netch/NativeMethods.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
// IpHlpApi.dll
|
||||
GetBestRoute
|
||||
GetExtendedTcpTable
|
||||
MIB_TCPTABLE_OWNER_PID
|
||||
ADDRESS_FAMILY
|
||||
|
||||
// User32.dll
|
||||
SetWindowPos
|
||||
GetWindowLong
|
||||
ShowWindow
|
||||
WINDOW_STYLE
|
||||
|
||||
// Kernel32.dll
|
||||
AllocConsole
|
||||
GetConsoleWindow
|
||||
|
||||
// Ws2_32.dll
|
||||
ntohs
|
||||
|
||||
// Windows.h
|
||||
// WIN32_ERROR
|
||||
@@ -1,25 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Controllers;
|
||||
using Netch.Enums;
|
||||
using Netch.Forms;
|
||||
using Netch.Services;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Vanara.PInvoke;
|
||||
using SingleInstance;
|
||||
#if RELEASE
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
#endif
|
||||
|
||||
namespace Netch
|
||||
{
|
||||
public static class Netch
|
||||
{
|
||||
public static readonly SingleInstance.SingleInstanceService SingleInstance = new($"Global\\{nameof(Netch)}");
|
||||
public static readonly SingleInstanceService SingleInstance = new($"Global\\{nameof(Netch)}");
|
||||
|
||||
public static HWND ConsoleHwnd { get; private set; }
|
||||
internal static HWND ConsoleHwnd { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序的主入口点
|
||||
@@ -38,13 +48,15 @@ namespace Netch
|
||||
Updater.CleanOld(Global.NetchDir);
|
||||
|
||||
// 预创建目录
|
||||
var directories = new[] {"mode\\Custom", "data", "i18n", "logging"};
|
||||
var directories = new[] { "mode\\Custom", "data", "i18n", "logging" };
|
||||
foreach (var item in directories)
|
||||
if (!Directory.Exists(item))
|
||||
Directory.CreateDirectory(item);
|
||||
|
||||
// 加载配置
|
||||
#pragma warning disable VSTHRD002
|
||||
Configuration.LoadAsync().Wait();
|
||||
#pragma warning restore VSTHRD002
|
||||
|
||||
if (!SingleInstance.IsFirstInstance)
|
||||
{
|
||||
@@ -80,7 +92,9 @@ namespace Netch
|
||||
Environment.Exit(2);
|
||||
}
|
||||
|
||||
Task.Run(LogEnvironment);
|
||||
Task.Run(LogEnvironment).Forget();
|
||||
CheckClr();
|
||||
CheckOS();
|
||||
|
||||
// 绑定错误捕获
|
||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
@@ -92,22 +106,54 @@ namespace Netch
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(Global.MainForm);
|
||||
}
|
||||
|
||||
private static void LogEnvironment()
|
||||
{
|
||||
Log.Information("Netch Version: {Version}", $"{UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}");
|
||||
Log.Information("Environment: {OSVersion}", Environment.OSVersion);
|
||||
Log.Information("OS: {OSVersion}", Environment.OSVersion);
|
||||
Log.Information("SHA256: {Hash}", $"{Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}");
|
||||
Log.Information("System Language: {Language}", CultureInfo.CurrentCulture.Name);
|
||||
if (Log.IsEnabled(LogEventLevel.Debug))
|
||||
Log.Debug("Third-party Drivers:\n{Drivers}", string.Join("\n", SystemInfo.SystemDrivers(false)));
|
||||
}
|
||||
|
||||
private static void CheckClr()
|
||||
{
|
||||
var framework = Assembly.GetExecutingAssembly().GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
|
||||
if (framework == null)
|
||||
{
|
||||
Log.Warning("TargetFrameworkAttribute null");
|
||||
return;
|
||||
}
|
||||
|
||||
var frameworkName = new FrameworkName(framework);
|
||||
|
||||
if (frameworkName.Version.Major != Environment.Version.Major)
|
||||
{
|
||||
Log.Information("CLR: {Version}", Environment.Version);
|
||||
Flags.NoSupport = true;
|
||||
if(!Global.Settings.NoSupportDialog)
|
||||
MessageBoxX.Show(i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", "CLR " + Environment.Version), LogLevel.WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckOS()
|
||||
{
|
||||
if (Environment.OSVersion.Version.Build < 17763)
|
||||
{
|
||||
Flags.NoSupport = true;
|
||||
if(!Global.Settings.NoSupportDialog)
|
||||
MessageBoxX.Show(i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", Environment.OSVersion), LogLevel.WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitConsole()
|
||||
{
|
||||
Kernel32.AllocConsole();
|
||||
PInvoke.AllocConsole();
|
||||
|
||||
ConsoleHwnd = Kernel32.GetConsoleWindow();
|
||||
ConsoleHwnd = PInvoke.GetConsoleWindow();
|
||||
#if RELEASE
|
||||
User32.ShowWindow(ConsoleHwnd, ShowWindowCommand.SW_HIDE);
|
||||
PInvoke.ShowWindow(ConsoleHwnd, SHOW_WINDOW_CMD.SW_HIDE);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<StartupObject>Netch.Netch</StartupObject>
|
||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<ApplicationIcon>Resources\Netch.ico</ApplicationIcon>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>VSTHRD100</NoWarn>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<AnalysisMode>Default</AnalysisMode>
|
||||
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
@@ -38,8 +32,13 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HMBSbige.SingleInstance" Version="5.0.7" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.70" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.2.4089">
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.71" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="16.10.56" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.506-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.10.4539">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -48,49 +47,35 @@
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" Condition="'$(Configuration)'=='Debug'" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
<PackageReference Include="Stun.Net" Version="5.0.0" />
|
||||
<PackageReference Include="System.Management" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.9.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.3.10" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="3.3.10" />
|
||||
<PackageReference Include="WindowsFirewallHelper" Version="2.0.4.70-beta2" />
|
||||
<PackageReference Include="WindowsFirewallHelper" Version="2.1.4.81" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Forms\Mode\RouteForm.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Condition="'$(PublishSingleFile)' == 'true'" AfterTargets="_ComputeFilesToBundle" Name="RemoveDupeAssemblies">
|
||||
<ItemGroup>
|
||||
<_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\build\native\x86\**" />
|
||||
<!-- .NET 6 SDK fixes MSB4018 "Multiple entries with the same BundleRelativePath",
|
||||
if still retain the following properties,
|
||||
you will encounter a "lib/netstandard2.0/Dial2Lib.dll not found" error
|
||||
-->
|
||||
<_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\lib\netstandard1.6\Dia2Lib.dll" />
|
||||
<_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\lib\netstandard1.6\OSExtensions.dll" />
|
||||
<_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\lib\netstandard1.6\TraceReloggerLib.dll" />
|
||||
|
||||
26
Netch/Properties/Settings.Designer.cs
generated
26
Netch/Properties/Settings.Designer.cs
generated
@@ -1,26 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Netch.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
@@ -15,7 +15,7 @@
|
||||
"Stopping": "正在停止中",
|
||||
"Stopped": "已停止",
|
||||
"Starting {0}": "正在启动 {0}",
|
||||
"Testing NAT": "正在测试 NAT",
|
||||
"Testing NAT Type": "正在测试 NAT 类型",
|
||||
"Setup Route Table Rule": "配置路由规则",
|
||||
"Test failed": "测试失败",
|
||||
"Starting update subscription": "正在更新订阅",
|
||||
@@ -103,6 +103,7 @@
|
||||
"Please select a mode first": "请先选择一个模式",
|
||||
"Please enter a profile name first": "请先为该配置设置一个名称",
|
||||
"No saved profile here. Save a profile first by Ctrl+Click on the button": "当前按钮下没有保存配置,请先使用 CTRL + 左键 点击该按钮保存一个配置",
|
||||
"Lookup Server hostname failed": "解析服务器主机名失败",
|
||||
|
||||
"Used": "已使用",
|
||||
"Testing": "测试中",
|
||||
@@ -154,15 +155,14 @@
|
||||
"Handle process's DNS Hijack": "被代理进程 DNS 劫持",
|
||||
"Child Process Handle": "子进程代理",
|
||||
"ICMP Delay(ms)": "ICMP 延迟(毫秒)",
|
||||
"Redirector built-in Shadowsocks support": "Redirector 内建 Shadowsocks 支持",
|
||||
"Profile Count": "快捷配置数量",
|
||||
"Delay test after start(sec)": "启动后延迟测试(秒)",
|
||||
"Ping Protocol": "延迟测试协议",
|
||||
"Detection Tick(sec)": "检测心跳(秒)",
|
||||
"STUN Server": "STUN 服务器",
|
||||
"Language": "语言",
|
||||
"Resolve Server Hostname": "解析服务器主机名",
|
||||
"FullCone Support (Required Server Xray-core v1.3.0+)": "FullCone 支持(需服务端 Xray-core v1.3.0+)",
|
||||
"Disable Support Warning": "停用支持警告",
|
||||
|
||||
"Profile": "配置名",
|
||||
"Profiles": "配置",
|
||||
@@ -172,5 +172,9 @@
|
||||
"Exit": "退出",
|
||||
|
||||
"The {0} port is in use.": "{0} 端口已被占用",
|
||||
"The {0} port is reserved by system.": "{0} 端口是系统保留端口"
|
||||
"The {0} port is reserved by system.": "{0} 端口是系统保留端口",
|
||||
|
||||
"\"Core.bin\" is missing. Please check your Antivirus software": "找不到 \"Core.bin\" 文件!请检查你的杀毒软件。",
|
||||
"{0} won't get developers' support, Please do not report any issues or seek help from developers.": "{0} 将不会得到开发者的支持,请不要报告任何问题或寻求开发人员的帮助。",
|
||||
"No Support": "不受支持"
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks
|
||||
{
|
||||
public class SSController : Guard, IServerController
|
||||
{
|
||||
public SSController() : base("Shadowsocks.exe")
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> StartedKeywords => new[] { "listening at" };
|
||||
|
||||
protected override IEnumerable<string> FailedKeywords => new[] { "Invalid config path", "usage", "plugin service exit unexpectedly" };
|
||||
|
||||
public override string Name => "Shadowsocks";
|
||||
|
||||
public ushort? Socks5LocalPort { get; set; }
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public Socks5 Start(in Server s)
|
||||
{
|
||||
var server = (Shadowsocks)s;
|
||||
|
||||
var command = new SSParameter
|
||||
{
|
||||
s = server.AutoResolveHostname(),
|
||||
p = server.Port,
|
||||
b = this.LocalAddress(),
|
||||
l = this.Socks5LocalPort(),
|
||||
m = server.EncryptMethod,
|
||||
k = server.Password,
|
||||
u = true,
|
||||
plugin = server.Plugin,
|
||||
plugin_opts = server.PluginOption
|
||||
};
|
||||
|
||||
StartGuard(command.ToString());
|
||||
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private class SSParameter : ParameterBase
|
||||
{
|
||||
public string? s { get; set; }
|
||||
|
||||
public ushort? p { get; set; }
|
||||
|
||||
public string? b { get; set; }
|
||||
|
||||
public ushort? l { get; set; }
|
||||
|
||||
public string? m { get; set; }
|
||||
|
||||
public string? k { get; set; }
|
||||
|
||||
public bool u { get; set; }
|
||||
|
||||
[Full] [Optional] public string? plugin { get; set; }
|
||||
|
||||
[Full]
|
||||
[Optional]
|
||||
[RealName("plugin-opts")]
|
||||
public string? plugin_opts { get; set; }
|
||||
|
||||
[Full] [Quote] [Optional] public string? acl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Netch/Servers/Shadowsocks/ShadowsocksController.cs
Normal file
47
Netch/Servers/Shadowsocks/ShadowsocksController.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class ShadowsocksController : Guard, IServerController
|
||||
{
|
||||
public ShadowsocksController() : base("Shadowsocks.exe")
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> StartedKeywords => new[] { "listening at" };
|
||||
|
||||
protected override IEnumerable<string> FailedKeywords => new[] { "Invalid config path", "usage", "plugin service exit unexpectedly" };
|
||||
|
||||
public override string Name => "Shadowsocks";
|
||||
|
||||
public ushort? Socks5LocalPort { get; set; }
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public async Task<Socks5LocalServer> StartAsync(Server s)
|
||||
{
|
||||
var server = (ShadowsocksServer)s;
|
||||
|
||||
var arguments = new object?[]
|
||||
{
|
||||
"-s", await server.AutoResolveHostnameAsync(),
|
||||
"-p", server.Port,
|
||||
"-b", this.LocalAddress(),
|
||||
"-l", this.Socks5LocalPort(),
|
||||
"-m", server.EncryptMethod,
|
||||
"-k", server.Password,
|
||||
"-u", SpecialArgument.Flag,
|
||||
"--plugin", server.Plugin,
|
||||
"--plugin-opts", server.PluginOption
|
||||
};
|
||||
|
||||
await StartGuardAsync(Arguments.Format(arguments));
|
||||
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using Netch.Forms;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks.Form
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class ShadowsocksForm : ServerForm
|
||||
{
|
||||
public ShadowsocksForm(Shadowsocks? server = default)
|
||||
public ShadowsocksForm(ShadowsocksServer? server = default)
|
||||
{
|
||||
server ??= new Shadowsocks();
|
||||
server ??= new ShadowsocksServer();
|
||||
Server = server;
|
||||
CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password);
|
||||
CreateComboBox("EncryptMethod", "Encrypt Method", SSGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class Shadowsocks : Server
|
||||
public class ShadowsocksServer : Server
|
||||
{
|
||||
public override string Type { get; } = "SS";
|
||||
public override string MaskedData()
|
||||
@@ -6,14 +6,12 @@ using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Shadowsocks.Form;
|
||||
using Netch.Servers.Shadowsocks.Models.SSD;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class SSUtil : IServerUtil
|
||||
public class ShadowsocksUtil : IServerUtil
|
||||
{
|
||||
public ushort Priority { get; } = 1;
|
||||
|
||||
@@ -25,11 +23,11 @@ namespace Netch.Servers.Shadowsocks
|
||||
|
||||
public string[] UriScheme { get; } = { "ss", "ssd" };
|
||||
|
||||
public Type ServerType { get; } = typeof(Shadowsocks);
|
||||
public Type ServerType { get; } = typeof(ShadowsocksServer);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new ShadowsocksForm((Shadowsocks)s).ShowDialog();
|
||||
new ShadowsocksForm((ShadowsocksServer)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
@@ -39,7 +37,7 @@ namespace Netch.Servers.Shadowsocks
|
||||
|
||||
public string GetShareLink(Server s)
|
||||
{
|
||||
var server = (Shadowsocks)s;
|
||||
var server = (ShadowsocksServer)s;
|
||||
// ss://method:password@server:port#Remark
|
||||
return "ss://" + ShareLink.URLSafeBase64Encode($"{server.EncryptMethod}:{server.Password}@{server.Hostname}:{server.Port}") + "#" +
|
||||
HttpUtility.UrlEncode(server.Remark);
|
||||
@@ -47,7 +45,7 @@ namespace Netch.Servers.Shadowsocks
|
||||
|
||||
public IServerController GetController()
|
||||
{
|
||||
return new SSController();
|
||||
return new ShadowsocksController();
|
||||
}
|
||||
|
||||
public IEnumerable<Server> ParseUri(string text)
|
||||
@@ -63,7 +61,7 @@ namespace Netch.Servers.Shadowsocks
|
||||
|
||||
public bool CheckServer(Server s)
|
||||
{
|
||||
var server = (Shadowsocks)s;
|
||||
var server = (ShadowsocksServer)s;
|
||||
if (!SSGlobal.EncryptMethods.Contains(server.EncryptMethod))
|
||||
{
|
||||
Log.Warning("不支持的 SS 加密方式:{Method}", server.EncryptMethod);
|
||||
@@ -75,9 +73,9 @@ namespace Netch.Servers.Shadowsocks
|
||||
|
||||
public IEnumerable<Server> ParseSsdUri(string s)
|
||||
{
|
||||
var json = JsonSerializer.Deserialize<Main>(ShareLink.URLSafeBase64Decode(s.Substring(6)))!;
|
||||
var json = JsonSerializer.Deserialize<SSDJObject>(ShareLink.URLSafeBase64Decode(s.Substring(6)))!;
|
||||
|
||||
return json.servers.Select(server => new Shadowsocks
|
||||
return json.servers.Select(server => new ShadowsocksServer
|
||||
{
|
||||
Remark = server.remarks,
|
||||
Hostname = server.server,
|
||||
@@ -92,9 +90,9 @@ namespace Netch.Servers.Shadowsocks
|
||||
.Where(CheckServer);
|
||||
}
|
||||
|
||||
public Shadowsocks ParseSsUri(string text)
|
||||
public ShadowsocksServer ParseSsUri(string text)
|
||||
{
|
||||
var data = new Shadowsocks();
|
||||
var data = new ShadowsocksServer();
|
||||
|
||||
text = text.Replace("/?", "?");
|
||||
if (text.Contains("#"))
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks.Models.SSD
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class Main
|
||||
public class SSDJObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 机场名
|
||||
@@ -38,6 +38,6 @@ namespace Netch.Servers.Shadowsocks.Models.SSD
|
||||
/// <summary>
|
||||
/// 服务器数组
|
||||
/// </summary>
|
||||
public List<SSDServer> servers;
|
||||
public List<SSDServerJObject> servers;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
namespace Netch.Servers.Shadowsocks.Models.SSD
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class SSDServer
|
||||
public class SSDServerJObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 加密方式
|
||||
@@ -1,6 +1,10 @@
|
||||
#nullable disable
|
||||
namespace Netch.Servers.Shadowsocks.Models
|
||||
namespace Netch.Servers
|
||||
{
|
||||
/// <summary>
|
||||
/// Import Shadowsocks Server from Json Configuration
|
||||
/// <see cref="Utils.ShareLink.ParseText"/>
|
||||
/// </summary>
|
||||
public class ShadowsocksConfig
|
||||
{
|
||||
public string server { get; set; }
|
||||
@@ -1,87 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers.ShadowsocksR
|
||||
{
|
||||
public class SSRController : Guard, IServerController
|
||||
{
|
||||
public SSRController() : base("ShadowsocksR.exe")
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> StartedKeywords => new[] { "listening at" };
|
||||
|
||||
protected override IEnumerable<string> FailedKeywords => new[] { "Invalid config path", "usage" };
|
||||
|
||||
public override string Name => "ShadowsocksR";
|
||||
|
||||
public ushort? Socks5LocalPort { get; set; }
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public Socks5 Start(in Server s)
|
||||
{
|
||||
var server = (ShadowsocksR)s;
|
||||
|
||||
var command = new SSRParameter
|
||||
{
|
||||
s = server.AutoResolveHostname(),
|
||||
p = server.Port,
|
||||
k = server.Password,
|
||||
m = server.EncryptMethod,
|
||||
t = "120",
|
||||
O = server.Protocol,
|
||||
G = server.ProtocolParam,
|
||||
o = server.OBFS,
|
||||
g = server.OBFSParam,
|
||||
b = this.LocalAddress(),
|
||||
l = this.Socks5LocalPort(),
|
||||
u = true
|
||||
};
|
||||
|
||||
StartGuard(command.ToString());
|
||||
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(),server.Hostname);
|
||||
}
|
||||
|
||||
[Verb]
|
||||
class SSRParameter : ParameterBase
|
||||
{
|
||||
public string? s { get; set; }
|
||||
|
||||
public ushort? p { get; set; }
|
||||
|
||||
[Quote]
|
||||
public string? k { get; set; }
|
||||
|
||||
public string? m { get; set; }
|
||||
|
||||
public string? t { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? O { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? G { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? o { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? g { get; set; }
|
||||
|
||||
public string? b { get; set; }
|
||||
|
||||
public ushort? l { get; set; }
|
||||
|
||||
public bool u { get; set; }
|
||||
|
||||
[Full]
|
||||
[Quote]
|
||||
[Optional]
|
||||
public string? acl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Netch/Servers/ShadowsocksR/ShadowsocksRController.cs
Normal file
50
Netch/Servers/ShadowsocksR/ShadowsocksRController.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class ShadowsocksRController : Guard, IServerController
|
||||
{
|
||||
public ShadowsocksRController() : base("ShadowsocksR.exe")
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> StartedKeywords => new[] { "listening at" };
|
||||
|
||||
protected override IEnumerable<string> FailedKeywords => new[] { "Invalid config path", "usage" };
|
||||
|
||||
public override string Name => "ShadowsocksR";
|
||||
|
||||
public ushort? Socks5LocalPort { get; set; }
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public async Task<Socks5LocalServer> StartAsync(Server s)
|
||||
{
|
||||
var server = (ShadowsocksRServer)s;
|
||||
|
||||
var arguments = new object?[]
|
||||
{
|
||||
"-s", await server.AutoResolveHostnameAsync(),
|
||||
"-p", server.Port,
|
||||
"-k", server.Password,
|
||||
"-m", server.EncryptMethod,
|
||||
"-t", 120,
|
||||
"-O", server.Protocol,
|
||||
"-G", server.ProtocolParam,
|
||||
"-o", server.OBFS,
|
||||
"-g", server.OBFSParam,
|
||||
"-b", this.LocalAddress(),
|
||||
"-l", this.Socks5LocalPort(),
|
||||
"-u", SpecialArgument.Flag
|
||||
};
|
||||
|
||||
await StartGuardAsync(Arguments.Format(arguments));
|
||||
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using Netch.Forms;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers.ShadowsocksR.Form
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class ShadowsocksRForm : ServerForm
|
||||
{
|
||||
public ShadowsocksRForm(ShadowsocksR? server = default)
|
||||
public ShadowsocksRForm(ShadowsocksRServer? server = default)
|
||||
{
|
||||
server ??= new ShadowsocksR();
|
||||
server ??= new ShadowsocksRServer();
|
||||
Server = server;
|
||||
CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password);
|
||||
CreateComboBox("EncryptMethod", "Encrypt Method", SSRGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers.ShadowsocksR
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class ShadowsocksR : Server
|
||||
public class ShadowsocksRServer : Server
|
||||
{
|
||||
public override string Type { get; } = "SSR";
|
||||
public override string MaskedData()
|
||||
@@ -3,14 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Shadowsocks;
|
||||
using Netch.Servers.ShadowsocksR.Form;
|
||||
using Netch.Utils;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Servers.ShadowsocksR
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class SSRUtil : IServerUtil
|
||||
public class ShadowsocksRUtil : IServerUtil
|
||||
{
|
||||
public ushort Priority { get; } = 1;
|
||||
|
||||
@@ -22,11 +20,11 @@ namespace Netch.Servers.ShadowsocksR
|
||||
|
||||
public string[] UriScheme { get; } = { "ssr" };
|
||||
|
||||
public Type ServerType { get; } = typeof(ShadowsocksR);
|
||||
public Type ServerType { get; } = typeof(ShadowsocksRServer);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new ShadowsocksRForm((ShadowsocksR)s).ShowDialog();
|
||||
new ShadowsocksRForm((ShadowsocksRServer)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
@@ -36,7 +34,7 @@ namespace Netch.Servers.ShadowsocksR
|
||||
|
||||
public string GetShareLink(Server s)
|
||||
{
|
||||
var server = (ShadowsocksR)s;
|
||||
var server = (ShadowsocksRServer)s;
|
||||
|
||||
// https://github.com/shadowsocksr-backup/shadowsocks-rss/wiki/SSR-QRcode-scheme
|
||||
// ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0)
|
||||
@@ -50,7 +48,7 @@ namespace Netch.Servers.ShadowsocksR
|
||||
|
||||
public IServerController GetController()
|
||||
{
|
||||
return new SSRController();
|
||||
return new ShadowsocksRController();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,7 +111,7 @@ namespace Netch.Servers.ShadowsocksR
|
||||
if (SSGlobal.EncryptMethods.Contains(method) && protocol == "origin" && obfs == "plain")
|
||||
return new[]
|
||||
{
|
||||
new Shadowsocks.Shadowsocks
|
||||
new ShadowsocksServer
|
||||
{
|
||||
Hostname = serverAddr,
|
||||
Port = serverPort,
|
||||
@@ -126,7 +124,7 @@ namespace Netch.Servers.ShadowsocksR
|
||||
|
||||
return new[]
|
||||
{
|
||||
new ShadowsocksR
|
||||
new ShadowsocksRServer
|
||||
{
|
||||
Hostname = serverAddr,
|
||||
Port = serverPort,
|
||||
@@ -144,7 +142,7 @@ namespace Netch.Servers.ShadowsocksR
|
||||
|
||||
public bool CheckServer(Server s)
|
||||
{
|
||||
var server = (ShadowsocksR)s;
|
||||
var server = (ShadowsocksRServer)s;
|
||||
if (!SSRGlobal.EncryptMethods.Contains(server.EncryptMethod))
|
||||
{
|
||||
Log.Error("不支持的 SSR 加密方式:{Method}", server.EncryptMethod);
|
||||
@@ -1,19 +0,0 @@
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class S5Controller : V2rayController
|
||||
{
|
||||
public override string Name { get; } = "Socks5";
|
||||
|
||||
public override Socks5 Start(in Server s)
|
||||
{
|
||||
var server = (Socks5)s;
|
||||
if (server.Auth())
|
||||
base.Start(s);
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Netch/Servers/Socks5/Socks5Controller.cs
Normal file
20
Netch/Servers/Socks5/Socks5Controller.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class Socks5Controller : V2rayController
|
||||
{
|
||||
public override string Name { get; } = "Socks5";
|
||||
|
||||
public override async Task<Socks5LocalServer> StartAsync(Server s)
|
||||
{
|
||||
var server = (Socks5Server)s;
|
||||
if (!server.Auth())
|
||||
throw new ArgumentException();
|
||||
|
||||
return await base.StartAsync(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ namespace Netch.Servers
|
||||
{
|
||||
public class Socks5Form : ServerForm
|
||||
{
|
||||
public Socks5Form(Socks5? server = default)
|
||||
public Socks5Form(Socks5Server? server = default)
|
||||
{
|
||||
server ??= new Socks5();
|
||||
server ??= new Socks5Server();
|
||||
Server = server;
|
||||
CreateTextBox("Username", "Username", s => true, s => server.Username = s, server.Username);
|
||||
CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password);
|
||||
@@ -5,10 +5,12 @@
|
||||
/// Encrypted proxy client's local socks5 server
|
||||
/// (<see cref="RemoteHostname"/> property is used for saving remote address/hostname for special use)
|
||||
/// </summary>
|
||||
public class Socks5Bridge : Socks5
|
||||
public class Socks5LocalServer : Socks5Server
|
||||
{
|
||||
public Socks5Bridge(string hostname, ushort port, string remoteHostname) : base(hostname, port)
|
||||
public Socks5LocalServer(string hostname, ushort port, string remoteHostname)
|
||||
{
|
||||
Hostname = hostname;
|
||||
Port = port;
|
||||
RemoteHostname = remoteHostname;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class Socks5 : Server
|
||||
public class Socks5Server : Server
|
||||
{
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string? Password;
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string? Username;
|
||||
public string? Username { get; set; }
|
||||
|
||||
public override string Type { get; } = "Socks5";
|
||||
|
||||
@@ -21,17 +21,17 @@ namespace Netch.Servers
|
||||
return $"Auth: {Auth()}";
|
||||
}
|
||||
|
||||
public Socks5()
|
||||
public Socks5Server()
|
||||
{
|
||||
}
|
||||
|
||||
public Socks5(string hostname, ushort port)
|
||||
public Socks5Server(string hostname, ushort port)
|
||||
{
|
||||
Hostname = hostname;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public Socks5(string hostname, ushort port, string username, string password) : this(hostname, port)
|
||||
public Socks5Server(string hostname, ushort port, string username, string password) : this(hostname, port)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
@@ -6,7 +6,7 @@ using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class S5Util : IServerUtil
|
||||
public class Socks5Util : IServerUtil
|
||||
{
|
||||
public ushort Priority { get; } = 0;
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Netch.Servers
|
||||
|
||||
public string[] UriScheme { get; } = { };
|
||||
|
||||
public Type ServerType { get; } = typeof(Socks5);
|
||||
public Type ServerType { get; } = typeof(Socks5Server);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new Socks5Form((Socks5)s).ShowDialog();
|
||||
new Socks5Form((Socks5Server)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
@@ -32,7 +32,7 @@ namespace Netch.Servers
|
||||
|
||||
public string GetShareLink(Server s)
|
||||
{
|
||||
var server = (Socks5)s;
|
||||
var server = (Socks5Server)s;
|
||||
// https://t.me/socks?server=1.1.1.1&port=443
|
||||
return $"https://t.me/socks?server={server.Hostname}&port={server.Port}" +
|
||||
$"{(!string.IsNullOrWhiteSpace(server.Username) ? $"&user={server.Username}" : "")}" +
|
||||
@@ -41,7 +41,7 @@ namespace Netch.Servers
|
||||
|
||||
public IServerController GetController()
|
||||
{
|
||||
return new S5Controller();
|
||||
return new Socks5Controller();
|
||||
}
|
||||
|
||||
public IEnumerable<Server> ParseUri(string text)
|
||||
@@ -55,7 +55,7 @@ namespace Netch.Servers
|
||||
if (!dict.ContainsKey("server") || !dict.ContainsKey("port"))
|
||||
throw new FormatException();
|
||||
|
||||
var data = new Socks5
|
||||
var data = new Socks5Server
|
||||
{
|
||||
Hostname = dict["server"],
|
||||
Port = ushort.Parse(dict["port"])
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Netch.Servers.Models
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class TrojanConfig
|
||||
{
|
||||
@@ -2,10 +2,10 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Models;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers
|
||||
@@ -26,14 +26,14 @@ namespace Netch.Servers
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public Socks5 Start(in Server s)
|
||||
public async Task<Socks5LocalServer> StartAsync(Server s)
|
||||
{
|
||||
var server = (Trojan)s;
|
||||
var server = (TrojanServer)s;
|
||||
var trojanConfig = new TrojanConfig
|
||||
{
|
||||
local_addr = this.LocalAddress(),
|
||||
local_port = this.Socks5LocalPort(),
|
||||
remote_addr = server.AutoResolveHostname(),
|
||||
remote_addr = await server.AutoResolveHostnameAsync(),
|
||||
remote_port = server.Port,
|
||||
password = new List<string>
|
||||
{
|
||||
@@ -41,17 +41,17 @@ namespace Netch.Servers
|
||||
},
|
||||
ssl = new TrojanSSL
|
||||
{
|
||||
sni = server.Host.ValueOrDefault() ?? (Global.Settings.ResolveServerHostname ? server.Hostname : "")
|
||||
sni = server.Host.ValueOrDefault() ?? server.Hostname
|
||||
}
|
||||
};
|
||||
|
||||
using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write))
|
||||
await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
JsonSerializer.SerializeAsync(fileStream, trojanConfig, Global.NewCustomJsonSerializerOptions()).Wait();
|
||||
await JsonSerializer.SerializeAsync(fileStream, trojanConfig, Global.NewCustomJsonSerializerOptions());
|
||||
}
|
||||
|
||||
StartGuard("-c ..\\data\\last.json");
|
||||
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
|
||||
await StartGuardAsync("-c ..\\data\\last.json");
|
||||
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using Netch.Forms;
|
||||
|
||||
namespace Netch.Servers.Form
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class TrojanForm : ServerForm
|
||||
{
|
||||
public TrojanForm(Trojan? server = default)
|
||||
public TrojanForm(TrojanServer? server = default)
|
||||
{
|
||||
server ??= new Trojan();
|
||||
server ??= new TrojanServer();
|
||||
Server = server;
|
||||
CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password);
|
||||
CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host);
|
||||
@@ -2,7 +2,7 @@ using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class Trojan : Server
|
||||
public class TrojanServer : Server
|
||||
{
|
||||
public override string Type { get; } = "Trojan";
|
||||
public override string MaskedData()
|
||||
@@ -4,7 +4,6 @@ using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Form;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
@@ -20,11 +19,11 @@ namespace Netch.Servers
|
||||
|
||||
public string[] UriScheme { get; } = { "trojan" };
|
||||
|
||||
public Type ServerType { get; } = typeof(Trojan);
|
||||
public Type ServerType { get; } = typeof(TrojanServer);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new TrojanForm((Trojan)s).ShowDialog();
|
||||
new TrojanForm((TrojanServer)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
@@ -34,7 +33,7 @@ namespace Netch.Servers
|
||||
|
||||
public string GetShareLink(Server s)
|
||||
{
|
||||
var server = (Trojan)s;
|
||||
var server = (TrojanServer)s;
|
||||
return $"trojan://{HttpUtility.UrlEncode(server.Password)}@{server.Hostname}:{server.Port}#{server.Remark}";
|
||||
}
|
||||
|
||||
@@ -45,7 +44,7 @@ namespace Netch.Servers
|
||||
|
||||
public IEnumerable<Server> ParseUri(string text)
|
||||
{
|
||||
var data = new Trojan();
|
||||
var data = new TrojanServer();
|
||||
|
||||
text = text.Replace("/?", "?");
|
||||
if (text.Contains("#"))
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
namespace Netch.Servers.Models
|
||||
namespace Netch.Servers
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 v2rayN 定义的 VMess 链接格式
|
||||
/// </summary>
|
||||
public class V2rayNSharing
|
||||
public class V2rayNJObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 链接版本
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Netch.Servers.V2ray.Models
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public struct V2rayConfig
|
||||
{
|
||||
@@ -1,13 +1,14 @@
|
||||
using Netch.Models;
|
||||
using Netch.Servers.V2ray.Models;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Netch.Utils;
|
||||
using V2rayConfig = Netch.Servers.V2ray.Models.V2rayConfig;
|
||||
|
||||
namespace Netch.Servers.Utils
|
||||
#pragma warning disable VSTHRD200
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public static class V2rayConfigUtils
|
||||
{
|
||||
public static V2rayConfig GenerateClientConfig(Server server)
|
||||
public static async Task<V2rayConfig> GenerateClientConfigAsync(Server server)
|
||||
{
|
||||
var v2rayConfig = new V2rayConfig
|
||||
{
|
||||
@@ -26,12 +27,12 @@ namespace Netch.Servers.Utils
|
||||
}
|
||||
};
|
||||
|
||||
v2rayConfig.outbounds = new[] { outbound(server) };
|
||||
v2rayConfig.outbounds = new[] { await outbound(server) };
|
||||
|
||||
return v2rayConfig;
|
||||
}
|
||||
|
||||
private static Outbound outbound(Server server)
|
||||
private static async Task<Outbound> outbound(Server server)
|
||||
{
|
||||
var outbound = new Outbound
|
||||
{
|
||||
@@ -41,14 +42,14 @@ namespace Netch.Servers.Utils
|
||||
|
||||
switch (server)
|
||||
{
|
||||
case Socks5 socks5:
|
||||
case Socks5Server socks5:
|
||||
{
|
||||
outbound.protocol = "socks";
|
||||
outbound.settings.servers = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
address = server.AutoResolveHostname(),
|
||||
address = await server.AutoResolveHostnameAsync(),
|
||||
port = server.Port,
|
||||
users = socks5.Auth()
|
||||
? new[]
|
||||
@@ -68,14 +69,14 @@ namespace Netch.Servers.Utils
|
||||
outbound.mux.concurrency = -1;
|
||||
break;
|
||||
}
|
||||
case VLESS vless:
|
||||
case VLESSServer vless:
|
||||
{
|
||||
outbound.protocol = "vless";
|
||||
outbound.settings.vnext = new[]
|
||||
{
|
||||
new VnextItem
|
||||
{
|
||||
address = server.AutoResolveHostname(),
|
||||
address = await server.AutoResolveHostnameAsync(),
|
||||
port = server.Port,
|
||||
users = new[]
|
||||
{
|
||||
@@ -104,14 +105,14 @@ namespace Netch.Servers.Utils
|
||||
|
||||
break;
|
||||
}
|
||||
case VMess vmess:
|
||||
case VMessServer vmess:
|
||||
{
|
||||
outbound.protocol = "vmess";
|
||||
outbound.settings.vnext = new[]
|
||||
{
|
||||
new VnextItem
|
||||
{
|
||||
address = server.AutoResolveHostname(),
|
||||
address = await server.AutoResolveHostnameAsync(),
|
||||
port = server.Port,
|
||||
users = new[]
|
||||
{
|
||||
@@ -136,7 +137,7 @@ namespace Netch.Servers.Utils
|
||||
return outbound;
|
||||
}
|
||||
|
||||
private static StreamSettings boundStreamSettings(VMess server)
|
||||
private static StreamSettings boundStreamSettings(VMessServer server)
|
||||
{
|
||||
// https://xtls.github.io/config/transports
|
||||
|
||||
@@ -2,10 +2,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Controllers;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Utils;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
@@ -27,15 +27,15 @@ namespace Netch.Servers
|
||||
|
||||
public string? LocalAddress { get; set; }
|
||||
|
||||
public virtual Socks5 Start(in Server s)
|
||||
public virtual async Task<Socks5LocalServer> StartAsync(Server s)
|
||||
{
|
||||
using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write))
|
||||
await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
JsonSerializer.SerializeAsync(fileStream, V2rayConfigUtils.GenerateClientConfig(s), Global.NewCustomJsonSerializerOptions()).Wait();
|
||||
await JsonSerializer.SerializeAsync(fileStream, await V2rayConfigUtils.GenerateClientConfigAsync(s), Global.NewCustomJsonSerializerOptions());
|
||||
}
|
||||
|
||||
StartGuard("-config ..\\data\\last.json");
|
||||
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname);
|
||||
await StartGuardAsync("-config ..\\data\\last.json");
|
||||
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Netch.Servers
|
||||
public static IEnumerable<Server> ParseVUri(string text)
|
||||
{
|
||||
var scheme = ShareLink.GetUriScheme(text).ToLower();
|
||||
var server = scheme switch { "vmess" => new VMess(), "vless" => new VLESS(), _ => throw new ArgumentOutOfRangeException() };
|
||||
var server = scheme switch { "vmess" => new VMessServer(), "vless" => new VLESSServer(), _ => throw new ArgumentOutOfRangeException() };
|
||||
if (text.Contains("#"))
|
||||
{
|
||||
server.Remark = Uri.UnescapeDataString(text.Split('#')[1]);
|
||||
@@ -58,7 +58,7 @@ namespace Netch.Servers
|
||||
{
|
||||
server.ServerName = parameter.Get("sni") ?? "";
|
||||
if (server.TLSSecureType == "xtls")
|
||||
((VLESS)server).Flow = parameter.Get("flow") ?? "";
|
||||
((VLESSServer)server).Flow = parameter.Get("flow") ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Netch.Servers
|
||||
public static string GetVShareLink(Server s, string scheme = "vmess")
|
||||
{
|
||||
// https://github.com/XTLS/Xray-core/issues/91
|
||||
var server = (VMess)s;
|
||||
var server = (VMessServer)s;
|
||||
var parameter = new Dictionary<string, string>();
|
||||
// protocol-specific fields
|
||||
parameter.Add("type", server.TransferProtocol);
|
||||
@@ -138,7 +138,7 @@ namespace Netch.Servers
|
||||
|
||||
if (server.TLSSecureType == "xtls")
|
||||
{
|
||||
var flow = ((VLESS)server).Flow;
|
||||
var flow = ((VLESSServer)server).Flow;
|
||||
if (!flow.IsNullOrWhiteSpace())
|
||||
parameter.Add("flow", flow!.Replace("-udp443", ""));
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Netch.Forms;
|
||||
|
||||
namespace Netch.Servers.VLESSForm
|
||||
namespace Netch.Servers
|
||||
{
|
||||
internal class VLESSForm : ServerForm
|
||||
{
|
||||
public VLESSForm(VLESS? server = default)
|
||||
public VLESSForm(VLESSServer? server = default)
|
||||
{
|
||||
server ??= new VLESS();
|
||||
server ??= new VLESSServer();
|
||||
Server = server;
|
||||
CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName);
|
||||
CreateTextBox("UUID", "UUID", s => true, s => server.UserID = s, server.UserID);
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class VLESS : VMess
|
||||
public class VLESSServer : VMessServer
|
||||
{
|
||||
public override string Type { get; } = "VLESS";
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
@@ -18,16 +17,16 @@ namespace Netch.Servers
|
||||
|
||||
public string[] UriScheme { get; } = { "vless" };
|
||||
|
||||
public Type ServerType { get; } = typeof(VLESS);
|
||||
public Type ServerType { get; } = typeof(VLESSServer);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new VLESSForm.VLESSForm((VLESS)s).ShowDialog();
|
||||
new VLESSForm((VLESSServer)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
{
|
||||
new VLESSForm.VLESSForm().ShowDialog();
|
||||
new VLESSForm().ShowDialog();
|
||||
}
|
||||
|
||||
public string GetShareLink(Server s)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Netch.Forms;
|
||||
|
||||
namespace Netch.Servers.Form
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class VMessForm : ServerForm
|
||||
{
|
||||
public VMessForm(VMess? server = default)
|
||||
public VMessForm(VMessServer? server = default)
|
||||
{
|
||||
server ??= new VMess();
|
||||
server ??= new VMessServer();
|
||||
Server = server;
|
||||
CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName);
|
||||
CreateTextBox("UserId", "User ID", s => true, s => server.UserID = s, server.UserID);
|
||||
@@ -3,7 +3,7 @@ using Netch.Models;
|
||||
|
||||
namespace Netch.Servers
|
||||
{
|
||||
public class VMess : Server
|
||||
public class VMessServer : Server
|
||||
{
|
||||
private string _tlsSecureType = VMessGlobal.TLSSecure[0];
|
||||
|
||||
@@ -5,8 +5,6 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Form;
|
||||
using Netch.Servers.Models;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers
|
||||
@@ -23,11 +21,11 @@ namespace Netch.Servers
|
||||
|
||||
public string[] UriScheme { get; } = { "vmess" };
|
||||
|
||||
public Type ServerType { get; } = typeof(VMess);
|
||||
public Type ServerType { get; } = typeof(VMessServer);
|
||||
|
||||
public void Edit(Server s)
|
||||
{
|
||||
new VMessForm((VMess)s).ShowDialog();
|
||||
new VMessForm((VMessServer)s).ShowDialog();
|
||||
}
|
||||
|
||||
public void Create()
|
||||
@@ -39,9 +37,9 @@ namespace Netch.Servers
|
||||
{
|
||||
if (Global.Settings.V2RayConfig.V2rayNShareLink)
|
||||
{
|
||||
var server = (VMess)s;
|
||||
var server = (VMessServer)s;
|
||||
|
||||
var vmessJson = JsonSerializer.Serialize(new V2rayNSharing
|
||||
var vmessJson = JsonSerializer.Serialize(new V2rayNJObject
|
||||
{
|
||||
v = 2,
|
||||
ps = server.Remark,
|
||||
@@ -75,7 +73,7 @@ namespace Netch.Servers
|
||||
|
||||
public IEnumerable<Server> ParseUri(string text)
|
||||
{
|
||||
var data = new VMess();
|
||||
var data = new VMessServer();
|
||||
|
||||
string s;
|
||||
try
|
||||
@@ -87,7 +85,7 @@ namespace Netch.Servers
|
||||
return V2rayUtils.ParseVUri(text);
|
||||
}
|
||||
|
||||
V2rayNSharing vmess = JsonSerializer.Deserialize<V2rayNSharing>(s,
|
||||
V2rayNJObject vmess = JsonSerializer.Deserialize<V2rayNJObject>(s,
|
||||
new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString })!;
|
||||
|
||||
data.Remark = vmess.ps;
|
||||
|
||||
@@ -5,8 +5,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Controllers;
|
||||
using Netch.Models;
|
||||
using Netch.Enums;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Utils
|
||||
@@ -52,69 +53,68 @@ namespace Netch.Utils
|
||||
var counterLock = new object();
|
||||
//int sent = 0;
|
||||
|
||||
//var processList = Process.GetProcessesByName(ProcessName).Select(p => p.Id).ToHashSet();
|
||||
var instances = new List<Process>();
|
||||
var processes = new List<Process>();
|
||||
switch (MainController.ServerController)
|
||||
{
|
||||
case null:
|
||||
break;
|
||||
case Guard guard:
|
||||
instances.Add(guard.Instance);
|
||||
|
||||
processes.Add(guard.Instance);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!instances.Any())
|
||||
if (!processes.Any())
|
||||
switch (MainController.ModeController)
|
||||
{
|
||||
case null:
|
||||
break;
|
||||
case NFController:
|
||||
instances.Add(Process.GetCurrentProcess());
|
||||
case NFController or TUNController:
|
||||
processes.Add(Process.GetCurrentProcess());
|
||||
break;
|
||||
case Guard guard:
|
||||
instances.Add(guard.Instance);
|
||||
processes.Add(guard.Instance);
|
||||
break;
|
||||
}
|
||||
|
||||
var processList = instances.Select(instance => instance.Id).ToHashSet();
|
||||
var pidHastSet = processes.Select(instance => instance.Id).ToHashSet();
|
||||
|
||||
Log.Information("流量统计进程: {Processes}", string.Join(',', instances.Select(v => $"({v.Id}){v.ProcessName}")));
|
||||
Log.Information("流量统计进程: {Processes}", string.Join(',', processes.Select(v => $"({v.Id}){v.ProcessName}")));
|
||||
|
||||
received = 0;
|
||||
|
||||
if (!instances.Any())
|
||||
if (!processes.Any())
|
||||
return;
|
||||
|
||||
Global.MainForm.BandwidthState(true);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
tSession = new TraceEventSession("KernelAndClrEventsSession");
|
||||
tSession.EnableKernelProvider(KernelTraceEventParser.Keywords.NetworkTCPIP);
|
||||
|
||||
//这玩意儿上传和下载得到的data是一样的:)
|
||||
//所以暂时没办法区分上传下载流量
|
||||
tSession.Source.Kernel.TcpIpRecv += data =>
|
||||
{
|
||||
if (processList.Contains(data.ProcessID))
|
||||
lock (counterLock)
|
||||
received += (ulong)data.size;
|
||||
tSession = new TraceEventSession("KernelAndClrEventsSession");
|
||||
tSession.EnableKernelProvider(KernelTraceEventParser.Keywords.NetworkTCPIP);
|
||||
|
||||
// Debug.WriteLine($"TcpIpRecv: {ToByteSize(data.size)}");
|
||||
};
|
||||
//这玩意儿上传和下载得到的data是一样的:)
|
||||
//所以暂时没办法区分上传下载流量
|
||||
tSession.Source.Kernel.TcpIpRecv += data =>
|
||||
{
|
||||
if (pidHastSet.Contains(data.ProcessID))
|
||||
lock (counterLock)
|
||||
received += (ulong)data.size;
|
||||
|
||||
tSession.Source.Kernel.UdpIpRecv += data =>
|
||||
{
|
||||
if (processList.Contains(data.ProcessID))
|
||||
lock (counterLock)
|
||||
received += (ulong)data.size;
|
||||
// Debug.WriteLine($"TcpIpRecv: {ToByteSize(data.size)}");
|
||||
};
|
||||
|
||||
// Debug.WriteLine($"UdpIpRecv: {ToByteSize(data.size)}");
|
||||
};
|
||||
tSession.Source.Kernel.UdpIpRecv += data =>
|
||||
{
|
||||
if (pidHastSet.Contains(data.ProcessID))
|
||||
lock (counterLock)
|
||||
received += (ulong)data.size;
|
||||
|
||||
tSession.Source.Process();
|
||||
});
|
||||
// Debug.WriteLine($"UdpIpRecv: {ToByteSize(data.size)}");
|
||||
};
|
||||
|
||||
tSession.Source.Process();
|
||||
})
|
||||
.Forget();
|
||||
|
||||
while (Global.MainForm.State != State.Stopped)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Models;
|
||||
using Serilog;
|
||||
|
||||
@@ -24,6 +25,8 @@ namespace Netch.Utils
|
||||
|
||||
private const string BackupFileName = "settings.json.bak";
|
||||
|
||||
private static readonly AsyncReaderWriterLock _lock = new(null);
|
||||
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions();
|
||||
|
||||
static Configuration()
|
||||
@@ -42,11 +45,13 @@ namespace Netch.Utils
|
||||
return;
|
||||
}
|
||||
|
||||
if (await LoadAsyncCore(FileFullName))
|
||||
await using var _ = await _lock.ReadLockAsync();
|
||||
|
||||
if (await LoadCoreAsync(FileFullName))
|
||||
return;
|
||||
|
||||
Log.Information("尝试加载备份配置文件 {FileName}", BackupFileFullName);
|
||||
await LoadAsyncCore(BackupFileFullName);
|
||||
await LoadCoreAsync(BackupFileFullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -55,19 +60,20 @@ namespace Netch.Utils
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<bool> LoadAsyncCore(string filename)
|
||||
private static async ValueTask<bool> LoadCoreAsync(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
|
||||
await using (fs.ConfigureAwait(false))
|
||||
{
|
||||
var settings = (await JsonSerializer.DeserializeAsync<Setting>(fs, JsonSerializerOptions).ConfigureAwait(false))!;
|
||||
Setting settings;
|
||||
|
||||
CheckSetting(settings);
|
||||
Global.Settings = settings;
|
||||
return true;
|
||||
await using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||
{
|
||||
settings = (await JsonSerializer.DeserializeAsync<Setting>(fs, JsonSerializerOptions).ConfigureAwait(false))!;
|
||||
}
|
||||
|
||||
CheckSetting(settings);
|
||||
Global.Settings = settings;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -93,18 +99,25 @@ namespace Netch.Utils
|
||||
/// </summary>
|
||||
public static async Task SaveAsync()
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await using var _ = await _lock.WriteLockAsync();
|
||||
Log.Verbose("Save Configuration");
|
||||
|
||||
if (!Directory.Exists(DataDirectoryFullName))
|
||||
Directory.CreateDirectory(DataDirectoryFullName);
|
||||
|
||||
var tempFile = Path.Combine(DataDirectoryFullName, FileFullName + ".tmp");
|
||||
var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
|
||||
await using (fileStream.ConfigureAwait(false))
|
||||
await using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
|
||||
{
|
||||
await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions).ConfigureAwait(false);
|
||||
await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions);
|
||||
}
|
||||
|
||||
await EnsureConfigFileExistsAsync();
|
||||
|
||||
File.Replace(tempFile, FileFullName, BackupFileFullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -112,5 +125,13 @@ namespace Netch.Utils
|
||||
Log.Error(e, "保存配置异常");
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask EnsureConfigFileExistsAsync()
|
||||
{
|
||||
if (!File.Exists(FileFullName))
|
||||
{
|
||||
await File.Create(FileFullName).DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Netch/Utils/DelayTestHelper.cs
Normal file
95
Netch/Utils/DelayTestHelper.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Netch.Models;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
public static class DelayTestHelper
|
||||
{
|
||||
private static readonly Timer Timer;
|
||||
|
||||
private static readonly AsyncSemaphore Lock = new(1);
|
||||
|
||||
private static readonly AsyncSemaphore PoolLock = new(16);
|
||||
|
||||
public static readonly NumberRange Range = new(0, int.MaxValue / 1000);
|
||||
|
||||
private static bool _enabled = true;
|
||||
|
||||
static DelayTestHelper()
|
||||
{
|
||||
Timer = new Timer
|
||||
{
|
||||
Interval = 10000,
|
||||
AutoReset = true
|
||||
};
|
||||
|
||||
Timer.Elapsed += (_, _) => PerformTestAsync().Forget();
|
||||
}
|
||||
|
||||
public static bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
UpdateTick();
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="waitFinish">if does not get lock, block until last release</param>
|
||||
public static async Task PerformTestAsync(bool waitFinish = false)
|
||||
{
|
||||
if (Lock.CurrentCount == 0)
|
||||
{
|
||||
if (waitFinish)
|
||||
(await Lock.EnterAsync()).Dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var _ = await Lock.EnterAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var tasks = Global.Settings.Server.Select(async s =>
|
||||
{
|
||||
using (await PoolLock.EnterAsync())
|
||||
{
|
||||
await s.PingAsync();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateTick(bool performTestAtOnce = false)
|
||||
{
|
||||
UpdateTick(Global.Settings.DetectionTick, performTestAtOnce);
|
||||
}
|
||||
|
||||
/// <param name="interval">interval(seconds), 0 disable, MaxValue <c>int.MaxValue/1000</c></param>
|
||||
/// <param name="performTestAtOnce"></param>
|
||||
private static void UpdateTick(int interval, bool performTestAtOnce = false)
|
||||
{
|
||||
Timer.Stop();
|
||||
|
||||
var enable = Enabled && interval > 0 && Range.InRange(interval);
|
||||
if (enable)
|
||||
{
|
||||
Timer.Interval = interval * 1000;
|
||||
Timer.Start();
|
||||
if (performTestAtOnce)
|
||||
PerformTestAsync().Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -12,31 +16,64 @@ namespace Netch.Utils
|
||||
/// 缓存
|
||||
/// </summary>
|
||||
private static readonly Hashtable Cache = new();
|
||||
private static readonly Hashtable Cache6 = new();
|
||||
|
||||
public static IPAddress? Lookup(string hostname, int timeout = 3000)
|
||||
public static async Task<IPAddress?> LookupAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Cache.Contains(hostname))
|
||||
return Cache[hostname] as IPAddress;
|
||||
var cacheResult = inet switch
|
||||
{
|
||||
AddressFamily.Unspecified => (IPAddress?)(Cache[hostname] ?? Cache6[hostname]),
|
||||
AddressFamily.InterNetwork => (IPAddress?)Cache[hostname],
|
||||
AddressFamily.InterNetworkV6 => (IPAddress?)Cache6[hostname],
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var task = Dns.GetHostAddressesAsync(hostname);
|
||||
if (!task.Wait(timeout))
|
||||
return null;
|
||||
if (cacheResult != null)
|
||||
return cacheResult;
|
||||
|
||||
if (task.Result.Length == 0)
|
||||
return null;
|
||||
|
||||
Cache.Add(hostname, task.Result[0]);
|
||||
|
||||
return task.Result[0];
|
||||
return await LookupNoCacheAsync(hostname, inet, timeout);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Verbose(e, "Lookup hostname {Hostname} failed", hostname);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IPAddress?> LookupNoCacheAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000)
|
||||
{
|
||||
using var task = Dns.GetHostAddressesAsync(hostname);
|
||||
using var resTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
|
||||
|
||||
if (resTask == task)
|
||||
{
|
||||
var addresses = await task;
|
||||
|
||||
var result = addresses.FirstOrDefault(i => inet == AddressFamily.Unspecified || inet == i.AddressFamily);
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
switch (result.AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
Cache.Add(hostname, result);
|
||||
break;
|
||||
case AddressFamily.InterNetworkV6:
|
||||
Cache6.Add(hostname, result);
|
||||
break;
|
||||
default:
|
||||
Trace.Assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询
|
||||
/// </summary>
|
||||
@@ -45,6 +82,7 @@ namespace Netch.Utils
|
||||
public static void ClearCache()
|
||||
{
|
||||
Cache.Clear();
|
||||
Cache6.Clear();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Split(string dns)
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Netch.Utils
|
||||
/// </summary>
|
||||
public static void AddNetchFwRules()
|
||||
{
|
||||
if (!FirewallWAS.IsSupported)
|
||||
if (!FirewallWAS.IsLocallySupported)
|
||||
{
|
||||
Log.Warning("不支持防火墙");
|
||||
return;
|
||||
@@ -47,7 +47,7 @@ namespace Netch.Utils
|
||||
/// </summary>
|
||||
public static void RemoveNetchFwRules()
|
||||
{
|
||||
if (!FirewallWAS.IsSupported)
|
||||
if (!FirewallWAS.IsLocallySupported)
|
||||
return;
|
||||
|
||||
try
|
||||
|
||||
@@ -6,8 +6,6 @@ using Netch.Controllers;
|
||||
using Netch.Enums;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
using Netch.Servers.Shadowsocks;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Utils
|
||||
@@ -126,38 +124,20 @@ namespace Netch.Utils
|
||||
File.Delete(mode.FullName);
|
||||
}
|
||||
|
||||
public static bool SkipServerController(Server server, Mode mode)
|
||||
{
|
||||
switch (mode.Type)
|
||||
{
|
||||
case ModeType.Process:
|
||||
return server switch
|
||||
{
|
||||
Socks5 => true,
|
||||
Shadowsocks shadowsocks when !shadowsocks.HasPlugin() && Global.Settings.Redirector.RedirectorSS => true,
|
||||
_ => false
|
||||
};
|
||||
case ModeType.ProxyRuleIPs:
|
||||
case ModeType.BypassRuleIPs:
|
||||
return server is Socks5;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IModeController GetModeControllerByType(ModeType type, out ushort? port, out string portName)
|
||||
public static (IModeController, ModeFeature) GetModeControllerByType(ModeType type, out ushort? port, out string portName)
|
||||
{
|
||||
port = null;
|
||||
portName = string.Empty;
|
||||
switch (type)
|
||||
{
|
||||
case ModeType.Process:
|
||||
return new NFController();
|
||||
return (new NFController(), ModeFeature.SupportIPv6 | ModeFeature.SupportSocks5Auth);
|
||||
case ModeType.ProxyRuleIPs:
|
||||
return (new TUNController(), ModeFeature.SupportSocks5Auth);
|
||||
case ModeType.BypassRuleIPs:
|
||||
return new TUNController();
|
||||
return (new TUNController(), ModeFeature.SupportSocks5Auth);
|
||||
case ModeType.Pcap2Socks:
|
||||
return new PcapController();
|
||||
return (new PcapController(), 0);
|
||||
default:
|
||||
Log.Error("未知模式类型");
|
||||
throw new MessageException("未知模式类型");
|
||||
|
||||
@@ -5,9 +5,8 @@ using System.Management;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.Win32;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -15,14 +14,18 @@ namespace Netch.Utils
|
||||
{
|
||||
public static NetworkInterface GetBest(AddressFamily addressFamily = AddressFamily.InterNetwork)
|
||||
{
|
||||
var ipAddress = addressFamily switch
|
||||
string ipAddress;
|
||||
if (addressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
AddressFamily.InterNetwork => "114.114.114.114",
|
||||
AddressFamily.InterNetworkV6 => throw new NotImplementedException(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(addressFamily), addressFamily, null)
|
||||
};
|
||||
ipAddress = "114.114.114.114";
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Assert(addressFamily == AddressFamily.InterNetworkV6);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0), 0, out var route) != 0)
|
||||
if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0), 0, out var route) != 0)
|
||||
throw new MessageException("GetBestRoute 搜索失败");
|
||||
|
||||
return Get((int)route.dwForwardIfIndex);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.NetworkManagement.IpHelper;
|
||||
using Netch.Models;
|
||||
using Serilog;
|
||||
using static Vanara.PInvoke.IpHlpApi;
|
||||
using static Vanara.PInvoke.Ws2_32;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -29,14 +32,38 @@ namespace Netch.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Process> GetProcessByUsedTcpPort(ushort port)
|
||||
internal static IEnumerable<Process> GetProcessByUsedTcpPort(ushort port, AddressFamily inet = AddressFamily.InterNetwork)
|
||||
{
|
||||
if (port == 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
var row = GetTcpTable2().Where(r => ntohs((ushort)r.dwLocalPort) == port).Where(r => r.dwOwningPid is not (0 or 4));
|
||||
if (inet != AddressFamily.InterNetwork)
|
||||
Trace.Assert(inet == AddressFamily.InterNetworkV6);
|
||||
|
||||
return row.Select(r => Process.GetProcessById((int)r.dwOwningPid));
|
||||
var process = new List<Process>();
|
||||
unsafe
|
||||
{
|
||||
uint err;
|
||||
uint size = 0;
|
||||
PInvoke.GetExtendedTcpTable(default, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0); // get size
|
||||
var tcpTable = (MIB_TCPTABLE_OWNER_PID*)Marshal.AllocHGlobal((int)size);
|
||||
|
||||
if ((err = PInvoke.GetExtendedTcpTable(tcpTable, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0)) != 0)
|
||||
throw new Win32Exception((int)err);
|
||||
|
||||
for (var i = 0; i < tcpTable -> dwNumEntries; i++)
|
||||
{
|
||||
var row = tcpTable -> table.ReadOnlyItemRef(i);
|
||||
|
||||
if (row.dwOwningPid is 0 or 4)
|
||||
continue;
|
||||
|
||||
if (PInvoke.ntohs((ushort)row.dwLocalPort) == port)
|
||||
process.Add(Process.GetProcessById((int)row.dwOwningPid));
|
||||
}
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void GetReservedPortRange(PortType portType, ref List<NumberRange> targetList)
|
||||
@@ -109,7 +136,8 @@ namespace Netch.Utils
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
Trace.Assert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +163,8 @@ namespace Netch.Utils
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
Trace.Assert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,8 +195,8 @@ namespace Netch.Utils
|
||||
[Flags]
|
||||
public enum PortType
|
||||
{
|
||||
TCP = 0x01,
|
||||
UDP = 0x10,
|
||||
TCP = 0b_01,
|
||||
UDP = 0b_10,
|
||||
Both = TCP | UDP
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ namespace Netch.Utils
|
||||
|
||||
public static bool CreateRoute(NetRoute o)
|
||||
{
|
||||
Log.Verbose("CreateRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}",
|
||||
AddressFamily.InterNetwork,
|
||||
o.Network,
|
||||
o.Cidr,
|
||||
o.Gateway,
|
||||
(ulong)o.InterfaceIndex,
|
||||
o.Metric);
|
||||
|
||||
return RouteHelper.CreateRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric);
|
||||
}
|
||||
|
||||
@@ -41,7 +49,7 @@ namespace Netch.Utils
|
||||
{
|
||||
if (!TryParseIPNetwork(rule, out var network, out var cidr))
|
||||
{
|
||||
Log.Warning("invalid rule {Rule}",rule);
|
||||
Log.Warning("invalid rule {Rule}", rule);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -50,6 +58,14 @@ namespace Netch.Utils
|
||||
|
||||
public static bool DeleteRoute(NetRoute o)
|
||||
{
|
||||
Log.Verbose("DeleteRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}",
|
||||
AddressFamily.InterNetwork,
|
||||
o.Network,
|
||||
o.Cidr,
|
||||
o.Gateway,
|
||||
(ulong)o.InterfaceIndex,
|
||||
o.Metric);
|
||||
|
||||
return RouteHelper.DeleteRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,18 +11,9 @@ namespace Netch.Utils
|
||||
|
||||
public override Server Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var jsonElement = JsonSerializer.Deserialize<JsonElement>(ref reader)!;
|
||||
|
||||
try
|
||||
{
|
||||
var type = ServerHelper.GetTypeByTypeName(jsonElement.GetProperty("Type").GetString()!);
|
||||
return (Server)JsonSerializer.Deserialize(jsonElement.GetRawText(), type)!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Unsupported Server Type
|
||||
return JsonSerializer.Deserialize<Server>(jsonElement.GetRawText(), new JsonSerializerOptions())!;
|
||||
}
|
||||
var jsonElement = JsonSerializer.Deserialize<JsonElement>(ref reader);
|
||||
var type = ServerHelper.GetTypeByTypeName(jsonElement.GetProperty("Type").GetString()!);
|
||||
return (Server)JsonSerializer.Deserialize(jsonElement.GetRawText(), type)!;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Server value, JsonSerializerOptions options)
|
||||
|
||||
@@ -2,11 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Interfaces;
|
||||
using Netch.Models;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -18,115 +14,24 @@ namespace Netch.Utils
|
||||
.GetExportedTypes()
|
||||
.Where(type => type.GetInterfaces().Contains(typeof(IServerUtil)));
|
||||
|
||||
ServerUtils = serversUtilsTypes.Select(t => (IServerUtil)Activator.CreateInstance(t)!).OrderBy(util => util.Priority);
|
||||
ServerUtilDictionary = serversUtilsTypes.Select(t => (IServerUtil)Activator.CreateInstance(t)!).ToDictionary(util => util.TypeName);
|
||||
}
|
||||
|
||||
public static Dictionary<string, IServerUtil> ServerUtilDictionary { get; }
|
||||
|
||||
public static IServerUtil GetUtilByTypeName(string typeName)
|
||||
{
|
||||
return ServerUtilDictionary.GetValueOrDefault(typeName) ?? throw new NotSupportedException("Specified server type is not supported.");
|
||||
}
|
||||
|
||||
public static IServerUtil? GetUtilByUriScheme(string scheme)
|
||||
{
|
||||
return ServerUtilDictionary.Values.SingleOrDefault(i => i.UriScheme.Any(s => s.Equals(scheme)));
|
||||
}
|
||||
|
||||
public static Type GetTypeByTypeName(string typeName)
|
||||
{
|
||||
return ServerUtils.Single(i => i.TypeName.Equals(typeName)).ServerType;
|
||||
return GetUtilByTypeName(typeName).ServerType;
|
||||
}
|
||||
|
||||
#region Delay
|
||||
|
||||
public static class DelayTestHelper
|
||||
{
|
||||
private static readonly Timer Timer;
|
||||
private static readonly object TestAllLock = new();
|
||||
|
||||
public static readonly NumberRange Range = new(0, int.MaxValue / 1000);
|
||||
|
||||
static DelayTestHelper()
|
||||
{
|
||||
Timer = new Timer
|
||||
{
|
||||
Interval = 10000,
|
||||
AutoReset = true
|
||||
};
|
||||
|
||||
Timer.Elapsed += (_, _) => TestAllDelay();
|
||||
}
|
||||
|
||||
public static bool Enabled
|
||||
{
|
||||
get => Timer.Enabled;
|
||||
set
|
||||
{
|
||||
if (!ValueIsEnabled(Global.Settings.DetectionTick))
|
||||
return;
|
||||
|
||||
Timer.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Interval => (int)(Timer.Interval / 1000);
|
||||
|
||||
private static bool ValueIsEnabled(int value)
|
||||
{
|
||||
return value != 0 && Range.InRange(value);
|
||||
}
|
||||
|
||||
public static event EventHandler? TestDelayFinished;
|
||||
|
||||
public static void TestAllDelay()
|
||||
{
|
||||
if (!Monitor.TryEnter(TestAllLock))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(Global.Settings.Server, new ParallelOptions { MaxDegreeOfParallelism = 16 }, server => { server.Test(); });
|
||||
TestDelayFinished?.Invoke(null, new EventArgs());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(TestAllLock);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateInterval()
|
||||
{
|
||||
Timer.Stop();
|
||||
|
||||
if (!ValueIsEnabled(Global.Settings.DetectionTick))
|
||||
return;
|
||||
|
||||
Timer.Interval = Global.Settings.DetectionTick * 1000;
|
||||
Task.Run(TestAllDelay);
|
||||
Timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Handler
|
||||
|
||||
public static readonly IEnumerable<IServerUtil> ServerUtils;
|
||||
|
||||
public static IServerUtil GetUtilByTypeName(string typeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName))
|
||||
throw new ArgumentNullException();
|
||||
|
||||
return ServerUtils.Single(i => i.TypeName.Equals(typeName));
|
||||
}
|
||||
|
||||
public static IServerUtil GetUtilByFullName(string fullName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
throw new ArgumentNullException();
|
||||
|
||||
return ServerUtils.Single(i => i.FullName.Equals(fullName));
|
||||
}
|
||||
|
||||
public static IServerUtil? GetUtilByUriScheme(string typeName)
|
||||
{
|
||||
return ServerUtils.SingleOrDefault(i => i.UriScheme.Any(s => s.Equals(typeName)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Shadowsocks;
|
||||
using Netch.Servers.Shadowsocks.Models;
|
||||
using Netch.Servers;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Utils
|
||||
@@ -33,7 +32,7 @@ namespace Netch.Utils
|
||||
|
||||
try
|
||||
{
|
||||
list.AddRange(JsonSerializer.Deserialize<List<ShadowsocksConfig>>(text)!.Select(server => new Shadowsocks
|
||||
list.AddRange(JsonSerializer.Deserialize<List<ShadowsocksConfig>>(text)!.Select(server => new ShadowsocksServer
|
||||
{
|
||||
Hostname = server.server,
|
||||
Port = server.server_port,
|
||||
|
||||
110
Netch/Utils/Socks5ServerTestUtils.cs
Normal file
110
Netch/Utils/Socks5ServerTestUtils.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Netch.Servers;
|
||||
using Socks5.Models;
|
||||
using STUN.Client;
|
||||
using STUN.Enums;
|
||||
using STUN.Proxy;
|
||||
using STUN.StunResult;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
public static class Socks5ServerTestUtils
|
||||
{
|
||||
public static async Task<NatTypeTestResult> DiscoveryNatTypeAsync(Socks5Server socks5, CancellationToken ctx = default)
|
||||
{
|
||||
var stunServer = Global.Settings.STUN_Server;
|
||||
var port = (ushort)Global.Settings.STUN_Server_Port;
|
||||
var local = new IPEndPoint(IPAddress.Any, 0);
|
||||
|
||||
var socks5Option = new Socks5CreateOption
|
||||
{
|
||||
Address = await DnsUtils.LookupAsync(socks5.Hostname),
|
||||
Port = socks5.Port,
|
||||
UsernamePassword = new UsernamePassword
|
||||
{
|
||||
UserName = socks5.Username,
|
||||
Password = socks5.Password
|
||||
}
|
||||
};
|
||||
|
||||
var ip = await DnsUtils.LookupAsync(stunServer);
|
||||
if (ip == null)
|
||||
{
|
||||
return new NatTypeTestResult { Result = "Wrong STUN Server!" };
|
||||
}
|
||||
|
||||
using IUdpProxy proxy = ProxyFactory.CreateProxy(ProxyType.Socks5, new IPEndPoint(IPAddress.Loopback, 0), socks5Option);
|
||||
using var client = new StunClient5389UDP(new IPEndPoint(ip, port), local, proxy);
|
||||
|
||||
await client.ConnectProxyAsync(ctx);
|
||||
try
|
||||
{
|
||||
await client.QueryAsync(ctx);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await client.CloseProxyAsync(ctx);
|
||||
}
|
||||
|
||||
var res = client.State;
|
||||
var result = GetSimpleResult(res);
|
||||
|
||||
return new NatTypeTestResult
|
||||
{
|
||||
Result = result,
|
||||
LocalEnd = res.LocalEndPoint?.ToString(),
|
||||
PublicEnd = res.PublicEndPoint?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetSimpleResult(StunResult5389 res)
|
||||
{
|
||||
switch (res.BindingTestResult, res.MappingBehavior, res.FilteringBehavior)
|
||||
{
|
||||
case (BindingTestResult.Fail, _, _):
|
||||
return "NoUDP";
|
||||
case (not BindingTestResult.Success, _, _):
|
||||
return res.BindingTestResult.ToString();
|
||||
case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, FilteringBehavior.EndpointIndependent):
|
||||
return "1";
|
||||
case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, _):
|
||||
return "2";
|
||||
case (_, MappingBehavior.AddressDependent or MappingBehavior.AddressAndPortDependent, _):
|
||||
return "3";
|
||||
case (_, MappingBehavior.Fail, _):
|
||||
return MappingBehavior.Fail.ToString();
|
||||
default:
|
||||
return res.FilteringBehavior.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<int?> HttpConnectAsync(Socks5Server socks5, CancellationToken ctx)
|
||||
{
|
||||
var socks5Option = new Socks5CreateOption
|
||||
{
|
||||
Address = await DnsUtils.LookupAsync(socks5.Hostname),
|
||||
Port = socks5.Port,
|
||||
UsernamePassword = new UsernamePassword
|
||||
{
|
||||
UserName = socks5.Username,
|
||||
Password = socks5.Password
|
||||
}
|
||||
};
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
var result = await Socks5.Utils.Socks5TestUtils.Socks5ConnectAsync(socks5Option, token: ctx);
|
||||
|
||||
stopwatch.Stop();
|
||||
if (result)
|
||||
return Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,9 @@ namespace Netch.Utils
|
||||
return value.Split(separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public static string? ValueOrDefault(this string? value)
|
||||
public static string? ValueOrDefault(this string? value, string? defaultValue = default)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
return string.IsNullOrWhiteSpace(value) ? defaultValue : value;
|
||||
}
|
||||
|
||||
public static string[]? SplitOrDefault(this string? value)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Netch.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace Netch.Utils
|
||||
@@ -14,10 +14,10 @@ namespace Netch.Utils
|
||||
|
||||
public static async Task UpdateServersAsync(string? proxyServer = default)
|
||||
{
|
||||
await Task.WhenAll(Global.Settings.SubscribeLink.Select(item => Task.Run(() => UpdateServer(item, proxyServer))).ToArray());
|
||||
await Task.WhenAll(Global.Settings.SubscribeLink.Select(item => UpdateServerCoreAsync(item, proxyServer)));
|
||||
}
|
||||
|
||||
public static void UpdateServer(SubscribeLink item, string? proxyServer)
|
||||
private static async Task UpdateServerCoreAsync(SubscribeLink item, string? proxyServer)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -34,11 +34,11 @@ namespace Netch.Utils
|
||||
|
||||
List<Server> servers;
|
||||
|
||||
var result = WebUtil.DownloadString(request, out var rep);
|
||||
if (rep.StatusCode == HttpStatusCode.OK)
|
||||
var (code, result) = await WebUtil.DownloadStringAsync(request);
|
||||
if (code == HttpStatusCode.OK)
|
||||
servers = ShareLink.ParseText(result);
|
||||
else
|
||||
throw new Exception($"{item.Remark} Response Status Code: {rep.StatusCode}");
|
||||
throw new Exception($"{item.Remark} Response Status Code: {code}");
|
||||
|
||||
foreach (var server in servers)
|
||||
server.Group = item.Remark;
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace Netch.Utils
|
||||
var mc = new ManagementClass("Win32_SystemDriver");
|
||||
foreach (var obj in mc.GetInstances().Cast<ManagementObject>())
|
||||
{
|
||||
if (!(bool) obj["Started"])
|
||||
if (!(bool)obj["Started"])
|
||||
continue;
|
||||
|
||||
var path = obj["PathName"].ToString();
|
||||
if (path == null)
|
||||
continue;
|
||||
|
||||
var vendorExclude = new[] {"microsoft", "intel", "amd", "nvidia", "realtek"};
|
||||
var vendorExclude = new[] { "microsoft", "intel", "amd", "nvidia", "realtek" };
|
||||
var vendorName = FileVersionInfo.GetVersionInfo(path).LegalCopyright ?? string.Empty;
|
||||
if (!allDriver && vendorExclude.Any(s => vendorName.Contains(s, StringComparison.OrdinalIgnoreCase)))
|
||||
continue;
|
||||
@@ -32,6 +32,7 @@ namespace Netch.Utils
|
||||
public static IEnumerable<string> Processes(bool mask)
|
||||
{
|
||||
var hashset = new HashSet<string>();
|
||||
var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
foreach (var process in Process.GetProcesses())
|
||||
{
|
||||
try
|
||||
@@ -39,8 +40,10 @@ namespace Netch.Utils
|
||||
if (process.Id is 0 or 4)
|
||||
continue;
|
||||
|
||||
if (process.MainModule!.FileName!.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.Windows), StringComparison.OrdinalIgnoreCase))
|
||||
// ! NT Kernel & System
|
||||
if (process.MainModule!.FileName!.StartsWith(windowsFolder, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var path = process.MainModule.FileName;
|
||||
|
||||
if (mask)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
public static class TplExtensions
|
||||
{
|
||||
public static void Forget(this Task? task)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,33 +57,34 @@ namespace Netch.Utils
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public static int ICMPing(IPAddress ip, int timeout = 1000)
|
||||
public static async Task<int> ICMPingAsync(IPAddress ip, int timeout = 1000)
|
||||
{
|
||||
var reply = new Ping().Send(ip, timeout);
|
||||
var reply = await new Ping().SendPingAsync(ip, timeout);
|
||||
|
||||
if (reply?.Status == IPStatus.Success)
|
||||
if (reply.Status == IPStatus.Success)
|
||||
return Convert.ToInt32(reply.RoundtripTime);
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public static string GetCityCode(string Hostname)
|
||||
public static async Task<string> GetCityCodeAsync(string address)
|
||||
{
|
||||
if (Hostname.Contains(":"))
|
||||
Hostname = Hostname.Split(':')[0];
|
||||
var i = address.IndexOf(':');
|
||||
if (i != -1)
|
||||
address = address[..i];
|
||||
|
||||
string? country = null;
|
||||
try
|
||||
{
|
||||
var databaseReader = new DatabaseReader("bin\\GeoLite2-Country.mmdb");
|
||||
|
||||
if (IPAddress.TryParse(Hostname, out _))
|
||||
if (IPAddress.TryParse(address, out _))
|
||||
{
|
||||
country = databaseReader.Country(Hostname).Country.IsoCode;
|
||||
country = databaseReader.Country(address).Country.IsoCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dnsResult = DnsUtils.Lookup(Hostname);
|
||||
var dnsResult = await DnsUtils.LookupAsync(address);
|
||||
|
||||
if (dnsResult != null)
|
||||
country = databaseReader.Country(dnsResult).Country.IsoCode;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -37,9 +38,9 @@ namespace Netch.Utils
|
||||
/// <returns></returns>
|
||||
public static async Task<byte[]> DownloadBytesAsync(HttpWebRequest req)
|
||||
{
|
||||
using var webResponse = req.GetResponseAsync();
|
||||
using var webResponse = await req.GetResponseAsync();
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await using var input = webResponse.Result.GetResponseStream();
|
||||
await using var input = webResponse.GetResponseStream();
|
||||
|
||||
await input.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
@@ -52,14 +53,14 @@ namespace Netch.Utils
|
||||
/// <param name="rep"></param>
|
||||
/// <param name="encoding">编码,默认UTF-8</param>
|
||||
/// <returns></returns>
|
||||
public static string DownloadString(HttpWebRequest req, out HttpWebResponse rep, Encoding? encoding = null)
|
||||
public static (HttpStatusCode, string) DownloadString(HttpWebRequest req, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
rep = (HttpWebResponse)req.GetResponse();
|
||||
using var rep = (HttpWebResponse)req.GetResponse();
|
||||
using var responseStream = rep.GetResponseStream();
|
||||
using var streamReader = new StreamReader(responseStream, encoding);
|
||||
|
||||
return streamReader.ReadToEnd();
|
||||
return (rep.StatusCode, streamReader.ReadToEnd());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,14 +69,14 @@ namespace Netch.Utils
|
||||
/// <param name="req"></param>
|
||||
/// <param name="encoding">编码,默认UTF-8</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> DownloadStringAsync(HttpWebRequest req, Encoding? encoding = null)
|
||||
public static async Task<(HttpStatusCode, string)> DownloadStringAsync(HttpWebRequest req, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
using var webResponse = await req.GetResponseAsync();
|
||||
using var webResponse = (HttpWebResponse)await req.GetResponseAsync();
|
||||
await using var responseStream = webResponse.GetResponseStream();
|
||||
using var streamReader = new StreamReader(responseStream, encoding);
|
||||
|
||||
return await streamReader.ReadToEndAsync();
|
||||
return (webResponse.StatusCode, await streamReader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(string address, string fileFullPath, IProgress<int>? progress = null)
|
||||
@@ -91,7 +92,7 @@ namespace Netch.Utils
|
||||
using (var downloadTask = input.CopyToAsync(fileStream))
|
||||
{
|
||||
if (progress != null)
|
||||
ReportProgress(webResponse.ContentLength, downloadTask, fileStream, progress, 200).Forget();
|
||||
ReportProgressAsync(webResponse.ContentLength, downloadTask, fileStream, progress, 200).Forget();
|
||||
|
||||
await downloadTask;
|
||||
}
|
||||
@@ -99,7 +100,7 @@ namespace Netch.Utils
|
||||
progress?.Report(100);
|
||||
}
|
||||
|
||||
private static async Task ReportProgress(long total, IAsyncResult downloadTask, Stream stream, IProgress<int> progress, int interval)
|
||||
private static async Task ReportProgressAsync(long total, IAsyncResult downloadTask, Stream stream, IProgress<int> progress, int interval)
|
||||
{
|
||||
var n = 0;
|
||||
while (!downloadTask.IsCompleted)
|
||||
|
||||
3
Netch/runtimeconfig.template.json
Normal file
3
Netch/runtimeconfig.template.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"rollForward": "Major"
|
||||
}
|
||||
3
UnitTest/.gitignore
vendored
Normal file
3
UnitTest/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/bin
|
||||
/obj
|
||||
/*.csproj.user
|
||||
23
UnitTest/UnitTest.csproj
Normal file
23
UnitTest/UnitTest.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.6" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.6" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Netch\Netch.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
13
UnitTest/UnitTest1.cs
Normal file
13
UnitTest/UnitTest1.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class UnitTest1
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMethod1()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user