Compare commits

..

26 Commits
1.8.7 ... 1.8.8

Author SHA1 Message Date
ChsBuffer
8e2008077d Bump version to 1.8.8 2021-09-11 01:32:41 +08:00
ChsBuffer
5b4f0026ff Feature: framework rollForward Major
this allows you to run .NET application targeting 5.x.x running on .NET 6.x.x runtime, but it's unsupported, May behave unexpectedly.
2021-09-11 01:28:27 +08:00
ChsBuffer
89f9dccb87 Fix virtual adapter mode NAT type test result "Wrong STUN server" 2021-09-11 01:19:01 +08:00
ChsBuffer
3e377f2e9d Feature: Server http connect time Test 2021-09-11 01:12:28 +08:00
ChsBuffer
635212f24d Replace NTTController with NatTypetester(Stun.Net)
Enable NAT Type Test for all mode types
Remove Shadowsocks SS
2021-09-10 23:56:25 +08:00
ChsBuffer
46d60babbc Refactor: Update Netch.Servers naming 2021-08-31 11:48:49 +08:00
ChsBuffer
8f80f9abef Update Nuget Packages 2021-08-29 03:54:19 +08:00
Netch
e268f1838f Update MainForm.cs 2021-08-14 09:18:27 +08:00
ChsBuffer
d99229ad50 Fix Netch.csproj loses ApplicationManifest property 2021-08-06 14:01:50 +08:00
ChsBuffer
df85d5797d Feature: remind will get no support from developers if OS is Windows 10 1809 below or CLR version is different from target framework 2021-08-06 06:59:19 +08:00
ChsBuffer
74856ccd61 Update UnitTest.csproj import common.props 2021-08-06 06:59:19 +08:00
ChsBuffer
0165d080c6 Feature: NFController.CheckCore check Core.bin file 2021-08-06 05:05:18 +08:00
ChsBuffer
97fb20e326 Update MainForm.State.StartDisableItems() 2021-08-06 04:55:16 +08:00
ChsBuffer
4d71e2d12f Bump Microsoft.Diagnostics.Tracing.TraceEvent from 2.0.70 to 2.0.71
Remove System.Drawing.Common nuget package
2021-08-06 04:45:59 +08:00
ChsBuffer
0fa83eac3c fix a typo 2021-08-06 02:13:32 +08:00
ChsBuffer
aa6623b063 Create common.props 2021-08-01 02:51:23 +08:00
ChsBuffer
94335ad900 Remove Properties\Settings.settings 2021-08-01 02:50:19 +08:00
ChsBuffer
baf3b39dd3 Fix Socks5 username and password not being saved to the configuration file 2021-08-01 02:49:00 +08:00
ChsBuffer
c12122f7d0 Refactor: Create ModeFeature Enum 2021-07-23 00:44:16 +08:00
ChsBuffer
3e5a4fc102 Update NatTestLock 2021-07-23 00:44:16 +08:00
ChsBuffer
57dbd0193a Move Netch.Models.State Netch.Models.LogLevel to Netch.Enums Namespace 2021-07-23 00:44:16 +08:00
ChsBuffer
5647a6c7ea Refactor SS,SSR Controller Generate Argument 2021-07-23 00:44:16 +08:00
ChsBuffer
773bad4845 Update ServerHelper.cs
Extract and Refactor DelayTestHelper
2021-07-23 00:44:15 +08:00
ChsBuffer
3e943ec6b8 Fix PcapController assert socks5 server false 2021-07-20 08:13:27 +08:00
ChsBuffer
920b068a1e Update DnsUtils 2021-07-19 08:09:57 +08:00
ChsBuffer
7eac7b0837 Fixup Touch Configuration File 2021-07-16 05:40:21 +08:00
82 changed files with 863 additions and 944 deletions

View File

@@ -11,9 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdditionalFiles", "Addition
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
common.props = common.props
global.json = global.json
LICENSE = LICENSE
README.md = README.md
global.json = global.json
EndProjectSection
EndProject
Global
@@ -26,8 +27,8 @@ 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|Any CPU
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Debug|x64.Build.0 = Debug|Any CPU
{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|Any CPU
{38240783-9AD2-4A01-84C1-1A3E5F05720F}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection

View File

@@ -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";

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Netch.Enums;
using Netch.Models;
using Netch.Utils;
using Serilog;

View File

@@ -1,9 +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;
@@ -12,27 +16,33 @@ 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; }
public static async Task StartAsync(Server server, Mode mode)
{
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(() =>
@@ -44,10 +54,39 @@ namespace Netch.Controllers
try
{
if (!ModeHelper.SkipServerController(server, mode))
server = await StartServerAsync(server);
(ModeController, ModeFeatures) = ModeHelper.GetModeControllerByType(mode.Type, out var modePort, out var portName);
await StartModeAsync(server, mode);
if (modePort != null)
TryReleaseTcpPort((ushort)modePort, portName);
switch (Server)
{
case Socks5Server socks5 when !socks5.Auth():
case Socks5Server socks5B when socks5B.Auth() && ModeFeatures.HasFlag(ModeFeature.SupportSocks5Auth):
// Directly Start ModeController
Socks5Server = (Socks5Server)Server;
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
await ModeController.StartAsync(Socks5Server, mode);
break;
default:
// 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);
break;
}
}
catch (Exception e)
{
@@ -68,35 +107,6 @@ namespace Netch.Controllers
}
}
private static async Task<Server> StartServerAsync(Server server)
{
ServerController = ServerHelper.GetUtilByTypeName(server.Type).GetController();
TryReleaseTcpPort(ServerController.Socks5LocalPort(), "Socks5");
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ServerController.Name));
Log.Debug("Server Information: {Data}", $"{server.Type} {server.MaskedData()}");
var socks5 = await ServerController.StartAsync(server);
StatusPortInfoText.Socks5Port = socks5.Port;
StatusPortInfoText.UpdateShareLan();
return socks5;
}
private static async Task StartModeAsync(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));
await ModeController.StartAsync(server, mode);
}
public static async Task StopAsync()
{
if (ServerController == null && ModeController == null)
@@ -105,8 +115,6 @@ namespace Netch.Controllers
Log.Information("Stop Main Controller");
StatusPortInfoText.Reset();
Task.Run(() => NTTController.StopAsync()).Forget();
var tasks = new[]
{
Task.Run(() => ServerController?.StopAsync()),
@@ -122,8 +130,9 @@ namespace Netch.Controllers
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)
@@ -163,5 +172,17 @@ 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");
return await Socks5ServerTestUtils.HttpConnectAsync(Socks5Server, ctx);
}
}
}

View File

@@ -9,7 +9,6 @@ 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;
@@ -28,12 +27,13 @@ namespace Netch.Controllers
public string Name => "Redirector";
public async Task StartAsync(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");
@@ -114,7 +114,7 @@ namespace Netch.Controllers
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, $"{await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
@@ -122,13 +122,6 @@ namespace Netch.Controllers
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, $"{await shadowsocks.AutoResolveHostnameAsync()}:{shadowsocks.Port}");
Dial(NameList.TYPE_TCPMETH + offset, shadowsocks.EncryptMethod);
Dial(NameList.TYPE_TCPPASS + offset, shadowsocks.Password);
}
else
{
Trace.Assert(false);
@@ -160,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()

View File

@@ -1,106 +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)> StartAsync()
{
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())
{
Log.Warning("NTT no output");
return (null, null, null);
}
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
{
await StopAsync();
}
catch
{
// ignored
}
return (null, null, null);
}
}
}
}

View File

@@ -31,7 +31,7 @@ namespace Netch.Controllers
public override string Name => "pcap2socks";
public async Task StartAsync(Server server, Mode mode)
public async Task StartAsync(Socks5Server server, Mode mode)
{
_server = server;
_mode = mode;
@@ -39,7 +39,7 @@ namespace Netch.Controllers
var outboundNetworkInterface = NetworkInterfaceUtils.GetBest();
var argument = new StringBuilder($@"-i \Device\NPF_{outboundNetworkInterface.Id}");
if (_server is Socks5Bridge socks5)
if (_server is Socks5Server socks5 && !socks5.Auth())
argument.Append($" --destination {await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");
else
Trace.Assert(false);

View File

@@ -30,12 +30,12 @@ namespace Netch.Controllers
public string Name => "tun2socks";
public async Task StartAsync(Server server, Mode mode)
public async Task StartAsync(Socks5Server server, Mode mode)
{
_mode = mode;
_tunConfig = Global.Settings.TUNTAP;
if (server is Socks5Bridge socks5Bridge)
if (server is Socks5LocalServer socks5Bridge)
_serverRemoteAddress = await DnsUtils.LookupAsync(socks5Bridge.RemoteHostname);
if (_serverRemoteAddress != null && IPAddress.IsLoopback(_serverRemoteAddress))
@@ -56,7 +56,7 @@ 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, $"{await socks5.AutoResolveHostnameAsync()}:{socks5.Port}");

View File

@@ -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.7";
public const string AssemblyVersion = @"1.8.8";
private const string Suffix = @"";
public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}";

View File

@@ -1,4 +1,4 @@
namespace Netch.Models
namespace Netch.Enums
{
public enum LogLevel
{

View File

@@ -0,0 +1,13 @@
using System;
namespace Netch.Enums
{
[Flags]
public enum ModeFeature
{
SupportSocks5 = 0,
SupportIPv4 = 0,
SupportSocks5Auth = 0b_0001,
SupportIPv6 = 0b_0100
}
}

View File

@@ -1,4 +1,4 @@
namespace Netch.Models
namespace Netch.Enums
{
/// <summary>
/// 状态

View File

@@ -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; }
}
}

View File

@@ -1,7 +1,6 @@
using Netch.Properties;
using Netch.Utils;
using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace Netch.Forms

View File

@@ -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.TcpStatusLabel = 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.TcpStatusLabel,
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;
//
// TcpStatusLabel
//
this.TcpStatusLabel.Name = "TcpStatusLabel";
this.TcpStatusLabel.Size = new System.Drawing.Size(33, 17);
this.TcpStatusLabel.Text = "TCP:";
this.TcpStatusLabel.TextAlign = System.Drawing.ContentAlignment.BottomLeft;
this.TcpStatusLabel.Visible = false;
this.TcpStatusLabel.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 TcpStatusLabel;
}
}

View File

@@ -43,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
@@ -78,7 +81,7 @@ namespace Netch.Forms
LoadServers();
SelectLastServer();
ServerHelper.DelayTestHelper.UpdateInterval();
DelayTestHelper.UpdateTick(true);
ModeHelper.InitWatcher();
ModeHelper.Load();
@@ -88,8 +91,8 @@ namespace Netch.Forms
// 加载翻译
TranslateControls();
// 隐藏 NatTypeStatusLabel
NatTypeStatusText();
// 隐藏 ConnectivityStatusLabel
ConnectivityStatusVisible(false);
// 加载快速配置
LoadProfiles();
@@ -498,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
@@ -545,7 +548,8 @@ namespace Netch.Forms
State = State.Started;
Task.Run(Bandwidth.NetTraffic).Forget();
NatTestAsync().Forget();
DiscoveryNatTypeAsync().Forget();
HttpConnectAsync().Forget();
if (Global.Settings.MinimizeWhenStarted)
Minimize();
@@ -592,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();
@@ -663,7 +667,7 @@ namespace Netch.Forms
}
else
{
await ServerHelper.DelayTestHelper.TestAllDelayAsync();
await DelayTestHelper.PerformTestAsync(true);
Enable();
}
}
@@ -990,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)
@@ -1025,7 +1030,7 @@ namespace Netch.Forms
ProfileGroupBox.Enabled = false;
BandwidthState(false);
NatTypeStatusText();
ConnectivityStatusVisible(false);
break;
case State.Stopped:
ControlButton.Enabled = true;
@@ -1057,14 +1062,11 @@ namespace Netch.Forms
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>
@@ -1099,18 +1101,14 @@ namespace Netch.Forms
UsedBandwidthLabel.Visible /*= UploadSpeedLabel.Visible*/ = DownloadSpeedLabel.Visible = state;
}
private void NatTypeStatusText(string? text = null, string? country = null)
private void UpdateNatTypeStatusLabelText(string? text, string? country = null)
{
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);
}
@@ -1122,10 +1120,18 @@ namespace Netch.Forms
NatTypeStatusLabel.Visible = true;
}
private void ConnectivityStatusVisible(bool visible)
{
if (!visible)
TcpStatusLabel.Text = NatTypeStatusLabel.Text = "";
TcpStatusLabel.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)
@@ -1148,46 +1154,73 @@ 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 NatTestAsync();
await HttpConnectAsync();
}
private bool _natTestLock = true;
/// <summary>
/// 测试 NAT
/// </summary>
private async Task NatTestAsync()
private async void NatTypeStatusLabel_Click(object sender, EventArgs e)
{
if (!MainController.Mode!.TestNatRequired())
return;
await DiscoveryNatTypeAsync();
}
if (!_natTestLock)
return;
private async Task DiscoveryNatTypeAsync()
{
NatTypeStatusLabel.Enabled = false;
NatTypeStatusLabel.Text = i18N.Translate("Testing NAT Type");
_natTestLock = false;
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
var discoveryNatTypeAsync = MainController.DiscoveryNatTypeAsync(cts.Token);
try
{
NatTypeStatusText(i18N.Translate("Testing NAT"));
var res = await discoveryNatTypeAsync;
var (result, _, publicEnd) = await MainController.NTTController.StartAsync();
if (!string.IsNullOrEmpty(publicEnd))
if (!string.IsNullOrEmpty(res.PublicEnd))
{
var country = await Utils.Utils.GetCityCodeAsync(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;
NatTypeStatusLabel.Enabled = true;
}
}
private async Task HttpConnectAsync()
{
TcpStatusLabel.Enabled = false;
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
var httpConnectAsync = MainController.HttpConnectAsync(cts.Token);
try
{
var httpRes = await httpConnectAsync;
if (httpRes != null)
TcpStatusLabel.Text = $"TCP{i18N.Translate(": ")}{httpRes}ms";
else
TcpStatusLabel.Text = $"TCP{i18N.Translate(": ", "Timeout")}";
TcpStatusLabel.Visible = true;
}
finally
{
TcpStatusLabel.Enabled = true;
}
}
@@ -1253,7 +1286,7 @@ namespace Netch.Forms
return;
}
State = State.Terminating;
// State = State.Terminating;
NotifyIcon.Visible = false;
Hide();

View File

@@ -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
{

View File

@@ -61,7 +61,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 +98,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();
@@ -361,7 +361,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 +434,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 +760,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 +815,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;
@@ -1070,7 +1070,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 +1079,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;
}
}

View File

@@ -48,7 +48,7 @@ namespace Netch.Forms
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 +112,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 +203,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

View File

@@ -1,10 +1,11 @@
using System.Threading.Tasks;
using Netch.Models;
using Netch.Servers;
namespace Netch.Interfaces
{
public interface IModeController : IController
{
public Task StartAsync(Server server, Mode mode);
public Task StartAsync(Socks5Server server, Mode mode);
}
}

View File

@@ -10,7 +10,7 @@ namespace Netch.Interfaces
public string? LocalAddress { get; set; }
public Task<Socks5> StartAsync(Server s);
public Task<Socks5LocalServer> StartAsync(Server s);
}
public static class ServerControllerExtension

49
Netch/Models/Arguments.cs Normal file
View 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
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
namespace Netch.Models.GitHubRelease
{

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Netch.Models
{
public struct NatTypeTestResult
{
public string? Result;
public string? LocalEnd;
public string? PublicEnd;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
@@ -42,11 +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()
{
@@ -61,7 +57,7 @@ namespace Netch.Models
{
var remark = string.IsNullOrWhiteSpace(Remark) ? $"{Hostname}:{Port}" : Remark;
var shortName = Type.IsNullOrEmpty() ? "WTF" : ServerHelper.GetUtilByTypeName(Type).ShortName;
var shortName = ServerHelper.GetUtilByTypeName(Type).ShortName;
return $"[{shortName}][{Group}] {remark}";
}
@@ -113,9 +109,9 @@ namespace Netch.Models
public static class ServerExtension
{
public static async Task<string> AutoResolveHostnameAsync(this Server server)
public static async Task<string> AutoResolveHostnameAsync(this Server server, AddressFamily inet = AddressFamily.Unspecified)
{
return Global.Settings.ResolveServerHostname ? (await DnsUtils.LookupAsync(server.Hostname))!.ToString() : server.Hostname;
return Global.Settings.ResolveServerHostname ? (await DnsUtils.LookupAsync(server.Hostname, inet))!.ToString() : server.Hostname;
}
public static bool IsInGroup(this Server server)

View File

@@ -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
{
@@ -106,11 +106,6 @@ namespace Netch.Models
public bool FilterICMP { get; set; } = false;
/// <summary>
/// 是否使用RDR内置SS
/// </summary>
public bool RedirectorSS { get; set; } = false;
/// <summary>
/// 是否代理子进程
/// </summary>
@@ -263,6 +258,8 @@ namespace Netch.Models
public V2rayConfig V2RayConfig { get; set; } = new();
public bool NoSupportDialog { get; set; } = false;
public Setting Clone()
{
return (Setting)MemberwiseClone();

View File

@@ -3,6 +3,8 @@ 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;
@@ -10,6 +12,7 @@ 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;
@@ -90,6 +93,8 @@ namespace Netch
}
Task.Run(LogEnvironment).Forget();
CheckClr();
CheckOS();
// 绑定错误捕获
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
@@ -105,13 +110,43 @@ namespace Netch
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: {OSVersion}", 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()
{
PInvoke.AllocConsole();

View File

@@ -1,30 +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>
@@ -39,13 +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="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.2.4089">
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.10.4539">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -54,42 +47,26 @@
<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="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
<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">

View File

@@ -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;
}
}
}
}

View File

@@ -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>

View File

@@ -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": "正在更新订阅",
@@ -163,6 +163,7 @@
"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 +173,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": "不受支持"
}

View File

@@ -1,74 +0,0 @@
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
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 async Task<Socks5> StartAsync(Server s)
{
var server = (Shadowsocks)s;
var command = new SSParameter
{
s = await server.AutoResolveHostnameAsync(),
p = server.Port,
b = this.LocalAddress(),
l = this.Socks5LocalPort(),
m = server.EncryptMethod,
k = server.Password,
u = true,
plugin = server.Plugin,
plugin_opts = server.PluginOption
};
await StartGuardAsync(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; }
}
}
}

View 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);
}
}
}

View File

@@ -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);

View File

@@ -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()

View File

@@ -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("#"))

View File

@@ -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;
}
}

View File

@@ -1,7 +1,7 @@
#nullable disable
namespace Netch.Servers.Shadowsocks.Models.SSD
namespace Netch.Servers
{
public class SSDServer
public class SSDServerJObject
{
/// <summary>
/// 加密方式

View File

@@ -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; }

View File

@@ -1,88 +0,0 @@
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
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 async Task<Socks5> StartAsync(Server s)
{
var server = (ShadowsocksR)s;
var command = new SSRParameter
{
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 = true
};
await StartGuardAsync(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; }
}
}
}

View 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);
}
}
}

View File

@@ -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);

View File

@@ -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()

View File

@@ -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);

View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;
using Netch.Models;
namespace Netch.Servers
{
public class S5Controller : V2rayController
{
public override string Name { get; } = "Socks5";
public override async Task<Socks5> StartAsync(Server s)
{
var server = (Socks5)s;
if (server.Auth())
await base.StartAsync(s);
return server;
}
}
}

View 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);
}
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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"])

View File

@@ -1,7 +1,7 @@
#nullable disable
using System.Collections.Generic;
namespace Netch.Servers.Models
namespace Netch.Servers
{
public class TrojanConfig
{

View File

@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Netch.Controllers;
using Netch.Interfaces;
using Netch.Models;
using Netch.Servers.Models;
using Netch.Utils;
namespace Netch.Servers
@@ -27,9 +26,9 @@ namespace Netch.Servers
public string? LocalAddress { get; set; }
public async Task<Socks5> StartAsync(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(),
@@ -52,7 +51,7 @@ namespace Netch.Servers
}
await StartGuardAsync("-c ..\\data\\last.json");
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname);
}
}
}

View File

@@ -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);

View File

@@ -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()

View File

@@ -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("#"))

View File

@@ -1,9 +1,6 @@
namespace Netch.Servers.Models
namespace Netch.Servers
{
/// <summary>
/// 使用 v2rayN 定义的 VMess 链接格式
/// </summary>
public class V2rayNSharing
public class V2rayNJObject
{
/// <summary>
/// 链接版本

View File

@@ -1,7 +1,7 @@
#nullable disable
// ReSharper disable InconsistentNaming
namespace Netch.Servers.V2ray.Models
namespace Netch.Servers
{
public struct V2rayConfig
{

View File

@@ -1,12 +1,10 @@
using System.Threading.Tasks;
using Netch.Models;
using Netch.Servers.V2ray.Models;
using Netch.Utils;
using V2rayConfig = Netch.Servers.V2ray.Models.V2rayConfig;
#pragma warning disable VSTHRD200
namespace Netch.Servers.Utils
namespace Netch.Servers
{
public static class V2rayConfigUtils
{
@@ -44,7 +42,7 @@ namespace Netch.Servers.Utils
switch (server)
{
case Socks5 socks5:
case Socks5Server socks5:
{
outbound.protocol = "socks";
outbound.settings.servers = new object[]
@@ -71,7 +69,7 @@ namespace Netch.Servers.Utils
outbound.mux.concurrency = -1;
break;
}
case VLESS vless:
case VLESSServer vless:
{
outbound.protocol = "vless";
outbound.settings.vnext = new[]
@@ -107,7 +105,7 @@ namespace Netch.Servers.Utils
break;
}
case VMess vmess:
case VMessServer vmess:
{
outbound.protocol = "vmess";
outbound.settings.vnext = new[]
@@ -139,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

View File

@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Netch.Controllers;
using Netch.Interfaces;
using Netch.Models;
using Netch.Servers.Utils;
namespace Netch.Servers
{
@@ -28,7 +27,7 @@ namespace Netch.Servers
public string? LocalAddress { get; set; }
public virtual async Task<Socks5> StartAsync(Server s)
public virtual async Task<Socks5LocalServer> StartAsync(Server s)
{
await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read))
{
@@ -36,7 +35,7 @@ namespace Netch.Servers
}
await StartGuardAsync("-config ..\\data\\last.json");
return new Socks5Bridge(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname);
return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname);
}
}
}

View File

@@ -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", ""));
}

View File

@@ -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);

View File

@@ -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";

View File

@@ -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)

View File

@@ -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);

View File

@@ -3,7 +3,7 @@ using Netch.Models;
namespace Netch.Servers
{
public class VMess : Server
public class VMessServer : Server
{
private string _tlsSecureType = VMessGlobal.TLSSecure[0];

View File

@@ -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;

View File

@@ -7,7 +7,7 @@ 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

View File

@@ -112,13 +112,11 @@ namespace Netch.Utils
var tempFile = Path.Combine(DataDirectoryFullName, FileFullName + ".tmp");
await using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
await using (fileStream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions).ConfigureAwait(false);
await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions);
}
if (!File.Exists(FileFullName))
File.Create(FileFullName);
await EnsureConfigFileExistsAsync();
File.Replace(tempFile, FileFullName, BackupFileFullName);
}
@@ -127,5 +125,13 @@ namespace Netch.Utils
Log.Error(e, "保存配置异常");
}
}
private static async ValueTask EnsureConfigFileExistsAsync()
{
if (!File.Exists(FileFullName))
{
await File.Create(FileFullName).DisposeAsync();
}
}
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Netch.Models;
using Timer = System.Timers.Timer;
namespace Netch.Utils
{
public static class DelayTestHelper
{
private static readonly Timer Timer;
private static readonly SemaphoreSlim Lock = new(1, 1);
private static readonly SemaphoreSlim PoolLock = new(16, 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.WaitAsync();
Lock.Release();
}
return;
}
await Lock.WaitAsync();
try
{
var tasks = Global.Settings.Server.Select(async s =>
{
await PoolLock.WaitAsync();
try
{
await s.PingAsync();
}
finally
{
PoolLock.Release();
}
});
await Task.WhenAll(tasks);
}
catch (Exception)
{
// ignored
}
finally
{
Lock.Release();
}
}
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();
}
}
}
}

View File

@@ -18,43 +18,22 @@ namespace Netch.Utils
private static readonly Hashtable Cache = new();
private static readonly Hashtable Cache6 = new();
public static async Task<IPAddress?> LookupAsync(string hostname, AddressFamily inet = AddressFamily.InterNetwork, int timeout = 3000)
public static async Task<IPAddress?> LookupAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000)
{
try
{
if (inet == AddressFamily.InterNetwork)
var cacheResult = inet switch
{
if (Cache.Contains(hostname))
return Cache[hostname] as IPAddress;
}
else
{
Trace.Assert(inet == AddressFamily.InterNetworkV6);
if (Cache6.Contains(hostname))
return Cache6[hostname] as IPAddress;
}
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 (cacheResult != null)
return cacheResult;
var resTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
if (resTask == task)
{
var addresses = await task;
var result = addresses.FirstOrDefault(i => i.AddressFamily == inet);
if (result == null)
return null;
if (inet == AddressFamily.InterNetwork)
Cache.Add(hostname, result);
else
Cache6.Add(hostname, result);
return result;
}
return null;
return await LookupNoCacheAsync(hostname, inet, timeout);
}
catch (Exception e)
{
@@ -63,6 +42,38 @@ namespace Netch.Utils
}
}
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>

View File

@@ -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

View File

@@ -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("未知模式类型");

View File

@@ -195,8 +195,8 @@ namespace Netch.Utils
[Flags]
public enum PortType
{
TCP = 0x01,
UDP = 0x10,
TCP = 0b_01,
UDP = 0b_10,
Both = TCP | UDP
}

View File

@@ -11,19 +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()!);
// TODO replace with .NET 6 Deserialize from DOM
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)

View File

@@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Netch.Interfaces;
using Netch.Models;
using Timer = System.Timers.Timer;
namespace Netch.Utils
{
@@ -23,102 +17,11 @@ namespace Netch.Utils
ServerUtilDictionary = serversUtilsTypes.Select(t => (IServerUtil)Activator.CreateInstance(t)!).ToDictionary(util => util.TypeName);
}
#region Delay
public static class DelayTestHelper
{
private static readonly Timer Timer;
private static readonly object TestAllLock = new();
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 16);
public static readonly NumberRange Range = new(0, int.MaxValue / 1000);
static DelayTestHelper()
{
Timer = new Timer
{
Interval = 10000,
AutoReset = true
};
Timer.Elapsed += (_, _) => TestAllDelayAsync().Forget();
}
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 async Task TestAllDelayAsync()
{
if (!Monitor.TryEnter(TestAllLock))
return;
try
{
var tasks = Global.Settings.Server.Select(async s =>
{
await SemaphoreSlim.WaitAsync();
try
{
await s.PingAsync();
}
finally
{
SemaphoreSlim.Release();
}
});
await Task.WhenAll(tasks);
}
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;
Timer.Start();
TestAllDelayAsync().Forget();
}
}
#endregion
#region Handler
public static Dictionary<string, IServerUtil> ServerUtilDictionary { get; set; }
public static Dictionary<string, IServerUtil> ServerUtilDictionary { get; }
public static IServerUtil GetUtilByTypeName(string typeName)
{
return ServerUtilDictionary[typeName];
return ServerUtilDictionary.GetValueOrDefault(typeName) ?? throw new NotSupportedException("Specified server type is not supported.");
}
public static IServerUtil? GetUtilByUriScheme(string scheme)
@@ -130,7 +33,5 @@ namespace Netch.Utils
{
return GetUtilByTypeName(typeName).ServerType;
}
#endregion
}
}

View File

@@ -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,

View File

@@ -0,0 +1,106 @@
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 (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";
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;
}
}
}

View File

@@ -0,0 +1,3 @@
{
"rollForward": "Major"
}

View File

@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.5" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.5" />
<PackageReference Include="coverlet.collector" Version="3.0.3">
<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>

9
common.props Normal file
View File

@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<Platforms>x64</Platforms>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>