diff --git a/.editorconfig b/.editorconfig
index 475813fa..7213a91c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -97,3 +97,7 @@ resharper_suggest_var_or_type_simple_types_highlighting = hint
resharper_web_config_module_not_resolved_highlighting = warning
resharper_web_config_type_not_resolved_highlighting = warning
resharper_web_config_wrong_module_highlighting = warning
+
+[*.csproj]
+indent_size = 2
+ij_xml_space_inside_empty_tag = true
\ No newline at end of file
diff --git a/Netch/Constants.cs b/Netch/Constants.cs
index 5ec78658..4323f845 100644
--- a/Netch/Constants.cs
+++ b/Netch/Constants.cs
@@ -1,31 +1,30 @@
-namespace Netch
+namespace Netch;
+
+public static class Constants
{
- public static class Constants
+ public const string TempConfig = "data\\last.json";
+ public const string TempRouteFile = "data\\route.txt";
+
+ public const string AioDnsRuleFile = "bin\\aiodns.conf";
+ public const string NFDriver = "bin\\nfdriver.sys";
+ public const string STUNServersFile = "bin\\stun.txt";
+
+ public const string LogFile = "logging\\application.log";
+
+ 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 TempConfig = "data\\last.json";
- public const string TempRouteFile = "data\\route.txt";
-
- public const string AioDnsRuleFile = "bin\\aiodns.conf";
- public const string NFDriver = "bin\\nfdriver.sys";
- public const string STUNServersFile = "bin\\stun.txt";
-
- public const string LogFile = "logging\\application.log";
-
- 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";
- public const string ForceUpdate = "-forceUpdate";
- }
-
- public const string WintunDllFile = "bin\\wintun.dll";
- public const string DisableModeDirectoryFileName = "disabled";
-
- public const string DefaultPrimaryDNS = "1.1.1.1:53";
- public const string DefaultCNPrimaryDNS = "223.5.5.5:53";
+ public const string Show = "-show";
+ public const string ForceUpdate = "-forceUpdate";
}
+
+ public const string WintunDllFile = "bin\\wintun.dll";
+ public const string DisableModeDirectoryFileName = "disabled";
+
+ public const string DefaultPrimaryDNS = "1.1.1.1:53";
+ public const string DefaultCNPrimaryDNS = "223.5.5.5:53";
}
\ No newline at end of file
diff --git a/Netch/Controllers/DNSController.cs b/Netch/Controllers/DNSController.cs
index d98a593f..5aee6a3f 100644
--- a/Netch/Controllers/DNSController.cs
+++ b/Netch/Controllers/DNSController.cs
@@ -1,33 +1,30 @@
-using System.IO;
-using System.Threading.Tasks;
-using Netch.Interfaces;
+using Netch.Interfaces;
using Netch.Models;
using static Netch.Interops.AioDNS;
-namespace Netch.Controllers
+namespace Netch.Controllers;
+
+public class DNSController : IController
{
- public class DNSController : IController
+ public string Name => "DNS Service";
+
+ public async Task StartAsync(string listenAddress)
{
- public string Name => "DNS Service";
+ var aioDnsConfig = Global.Settings.AioDNS;
- public async Task StartAsync(string listenAddress)
- {
- var aioDnsConfig = Global.Settings.AioDNS;
+ Dial(NameList.TYPE_REST, "");
+ Dial(NameList.TYPE_LIST, Path.GetFullPath(Constants.AioDnsRuleFile));
+ // TODO remove ListenPort setting
+ Dial(NameList.TYPE_LISN, $"{listenAddress}:{aioDnsConfig.ListenPort}");
+ Dial(NameList.TYPE_CDNS, $"{aioDnsConfig.ChinaDNS}");
+ Dial(NameList.TYPE_ODNS, $"{aioDnsConfig.OtherDNS}");
- Dial(NameList.TYPE_REST, "");
- Dial(NameList.TYPE_LIST, Path.GetFullPath(Constants.AioDnsRuleFile));
- // TODO remove ListenPort setting
- Dial(NameList.TYPE_LISN, $"{listenAddress}:{aioDnsConfig.ListenPort}");
- Dial(NameList.TYPE_CDNS, $"{aioDnsConfig.ChinaDNS}");
- Dial(NameList.TYPE_ODNS, $"{aioDnsConfig.OtherDNS}");
+ if (!await InitAsync())
+ throw new MessageException("AioDNS start failed.");
+ }
- if (!await InitAsync())
- throw new MessageException("AioDNS start failed.");
- }
-
- public async Task StopAsync()
- {
- await FreeAsync();
- }
+ public async Task StopAsync()
+ {
+ await FreeAsync();
}
}
\ No newline at end of file
diff --git a/Netch/Controllers/Guard.cs b/Netch/Controllers/Guard.cs
index 0f369210..c0d78c94 100644
--- a/Netch/Controllers/Guard.cs
+++ b/Netch/Controllers/Guard.cs
@@ -1,182 +1,176 @@
-using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
-using System.IO;
-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;
-namespace Netch.Controllers
+namespace Netch.Controllers;
+
+public abstract class Guard
{
- public abstract class Guard
+ private FileStream? _logFileStream;
+ private StreamWriter? _logStreamWriter;
+
+ /// application path relative of Netch\bin
+ ///
+ /// application output encode
+ protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null)
{
- private FileStream? _logFileStream;
- private StreamWriter? _logStreamWriter;
+ RedirectOutput = redirectOutput;
- /// application path relative of Netch\bin
- ///
- /// application output encode
- protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null)
+ var fileName = Path.GetFullPath($"bin\\{mainFile}");
+
+ if (!File.Exists(fileName))
+ throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!"));
+
+ Instance = new Process
{
- RedirectOutput = redirectOutput;
-
- var fileName = Path.GetFullPath($"bin\\{mainFile}");
-
- if (!File.Exists(fileName))
- throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!"));
-
- Instance = new Process
+ StartInfo =
{
- StartInfo =
- {
- FileName = fileName,
- WorkingDirectory = $"{Global.NetchDir}\\bin",
- CreateNoWindow = true,
- UseShellExecute = !RedirectOutput,
- RedirectStandardOutput = RedirectOutput,
- StandardOutputEncoding = RedirectOutput ? encoding : null,
- RedirectStandardError = RedirectOutput,
- StandardErrorEncoding = RedirectOutput ? encoding : null,
- WindowStyle = ProcessWindowStyle.Hidden
- }
- };
- }
-
- protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log");
-
- protected virtual IEnumerable StartedKeywords { get; } = new List();
-
- protected virtual IEnumerable FailedKeywords { get; } = new List();
-
- public abstract string Name { get; }
-
- private State State { get; set; } = State.Waiting;
-
- private bool RedirectOutput { get; }
-
- public Process Instance { get; }
-
- ~Guard()
- {
- _logFileStream?.Dispose();
- _logStreamWriter?.Dispose();
- Instance.Dispose();
- }
-
- protected async Task StartGuardAsync(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
- {
- State = State.Starting;
-
- _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.Write, FileShare.Read);
- _logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true };
-
- Instance.StartInfo.Arguments = argument;
- Instance.Start();
- Global.Job.AddProcess(Instance);
-
- if (priority != ProcessPriorityClass.Normal)
- Instance.PriorityClass = priority;
-
- if (RedirectOutput)
- {
- Task.Run(() => ReadOutput(Instance.StandardOutput)).Forget();
- Task.Run(() => ReadOutput(Instance.StandardError)).Forget();
-
- if (!StartedKeywords.Any())
- {
- // Skip, No started keyword
- State = State.Started;
- return;
- }
-
- // wait ReadOutput change State
- for (var i = 0; i < 1000; i++)
- {
- await Task.Delay(50);
- switch (State)
- {
- case State.Started:
- OnStarted();
- return;
- case State.Stopped:
- await StopGuardAsync();
- OnStartFailed();
- throw new MessageException($"{Name} 控制器启动失败");
- }
- }
-
- await StopGuardAsync();
- throw new MessageException($"{Name} 控制器启动超时");
+ FileName = fileName,
+ WorkingDirectory = $"{Global.NetchDir}\\bin",
+ CreateNoWindow = true,
+ UseShellExecute = !RedirectOutput,
+ RedirectStandardOutput = RedirectOutput,
+ StandardOutputEncoding = RedirectOutput ? encoding : null,
+ RedirectStandardError = RedirectOutput,
+ StandardErrorEncoding = RedirectOutput ? encoding : null,
+ WindowStyle = ProcessWindowStyle.Hidden
}
- }
+ };
+ }
- private void ReadOutput(TextReader reader)
+ protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log");
+
+ protected virtual IEnumerable StartedKeywords { get; } = new List();
+
+ protected virtual IEnumerable FailedKeywords { get; } = new List();
+
+ public abstract string Name { get; }
+
+ private State State { get; set; } = State.Waiting;
+
+ private bool RedirectOutput { get; }
+
+ public Process Instance { get; }
+
+ ~Guard()
+ {
+ _logFileStream?.Dispose();
+ _logStreamWriter?.Dispose();
+ Instance.Dispose();
+ }
+
+ protected async Task StartGuardAsync(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
+ {
+ State = State.Starting;
+
+ _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ _logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true };
+
+ Instance.StartInfo.Arguments = argument;
+ Instance.Start();
+ Global.Job.AddProcess(Instance);
+
+ if (priority != ProcessPriorityClass.Normal)
+ Instance.PriorityClass = priority;
+
+ if (RedirectOutput)
{
- string? line;
- while ((line = reader.ReadLine()) != null)
- {
- _logStreamWriter!.WriteLine(line);
- OnReadNewLine(line);
+ Task.Run(() => ReadOutput(Instance.StandardOutput)).Forget();
+ Task.Run(() => ReadOutput(Instance.StandardError)).Forget();
- if (State == State.Starting)
+ if (!StartedKeywords.Any())
+ {
+ // Skip, No started keyword
+ State = State.Started;
+ return;
+ }
+
+ // wait ReadOutput change State
+ for (var i = 0; i < 1000; i++)
+ {
+ await Task.Delay(50);
+ switch (State)
{
- if (StartedKeywords.Any(s => line.Contains(s)))
- State = State.Started;
- else if (FailedKeywords.Any(s => line.Contains(s)))
- {
+ case State.Started:
+ OnStarted();
+ return;
+ case State.Stopped:
+ await StopGuardAsync();
OnStartFailed();
- State = State.Stopped;
- }
+ throw new MessageException($"{Name} 控制器启动失败");
}
}
- State = State.Stopped;
- }
-
- public virtual async Task StopAsync()
- {
await StopGuardAsync();
- }
-
- protected async Task StopGuardAsync()
- {
- _logStreamWriter?.Close();
- _logFileStream?.Close();
-
- try
- {
- if (Instance is { HasExited: false })
- {
- Instance.Kill();
- await Instance.WaitForExitAsync();
- }
- }
- catch (Win32Exception e)
- {
- Log.Error(e, "Stop {Name} failed", Instance.ProcessName);
- }
- catch
- {
- // ignored
- }
- }
-
- protected virtual void OnStarted()
- {
- }
-
- protected virtual void OnReadNewLine(string line)
- {
- }
-
- protected virtual void OnStartFailed()
- {
- Utils.Utils.Open(LogPath);
+ throw new MessageException($"{Name} 控制器启动超时");
}
}
+
+ private void ReadOutput(TextReader reader)
+ {
+ string? line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ _logStreamWriter!.WriteLine(line);
+ OnReadNewLine(line);
+
+ if (State == State.Starting)
+ {
+ if (StartedKeywords.Any(s => line.Contains(s)))
+ State = State.Started;
+ else if (FailedKeywords.Any(s => line.Contains(s)))
+ {
+ OnStartFailed();
+ State = State.Stopped;
+ }
+ }
+ }
+
+ State = State.Stopped;
+ }
+
+ public virtual async Task StopAsync()
+ {
+ await StopGuardAsync();
+ }
+
+ protected async Task StopGuardAsync()
+ {
+ _logStreamWriter?.Close();
+ _logFileStream?.Close();
+
+ try
+ {
+ if (Instance is { HasExited: false })
+ {
+ Instance.Kill();
+ await Instance.WaitForExitAsync();
+ }
+ }
+ catch (Win32Exception e)
+ {
+ Log.Error(e, "Stop {Name} failed", Instance.ProcessName);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ protected virtual void OnStarted()
+ {
+ }
+
+ protected virtual void OnReadNewLine(string line)
+ {
+ }
+
+ protected virtual void OnStartFailed()
+ {
+ Utils.Utils.Open(LogPath);
+ }
}
\ No newline at end of file
diff --git a/Netch/Controllers/MainController.cs b/Netch/Controllers/MainController.cs
index e904bf8d..dd66e14e 100644
--- a/Netch/Controllers/MainController.cs
+++ b/Netch/Controllers/MainController.cs
@@ -1,8 +1,4 @@
-using System;
using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Netch.Interfaces;
using Netch.Models;
@@ -10,193 +6,191 @@ using Netch.Models.Modes;
using Netch.Servers;
using Netch.Services;
using Netch.Utils;
-using Serilog;
-namespace Netch.Controllers
+namespace Netch.Controllers;
+
+public static class MainController
{
- public static class MainController
+ public static Socks5Server? Socks5Server { get; private set; }
+
+ 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; }
+
+ private static readonly AsyncSemaphore Lock = new(1);
+
+ public static async Task StartAsync(Server server, Mode mode)
{
- public static Socks5Server? Socks5Server { get; private set; }
+ using var releaser = await Lock.EnterAsync();
- public static Server? Server { get; private set; }
+ Log.Information("Start MainController: {Server} {Mode}", $"{server.Type}", $"[{(int)mode.Type}]{mode.i18NRemark}");
- public static Mode? Mode { get; private set; }
+ if (await DnsUtils.LookupAsync(server.Hostname) == null)
+ throw new MessageException(i18N.Translate("Lookup Server hostname failed"));
- public static IServerController? ServerController { get; private set; }
+ // TODO Disable NAT Type Test setting
+ // cache STUN Server ip to prevent "Wrong STUN Server"
+ DnsUtils.LookupAsync(Global.Settings.STUN_Server).Forget();
- public static IModeController? ModeController { get; private set; }
+ Server = server;
+ Mode = mode;
- private static readonly AsyncSemaphore Lock = new(1);
+ await Task.WhenAll(Task.Run(NativeMethods.RefreshDNSCache), Task.Run(Firewall.AddNetchFwRules));
- public static async Task StartAsync(Server server, Mode mode)
+ try
{
- using var releaser = await Lock.EnterAsync();
+ ModeController = ModeService.GetModeControllerByType(mode.Type, out var modePort, out var portName);
- Log.Information("Start MainController: {Server} {Mode}", $"{server.Type}", $"[{(int)mode.Type}]{mode.i18NRemark}");
+ if (modePort != null)
+ TryReleaseTcpPort((ushort)modePort, portName);
- 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));
-
- try
+ if (Server is Socks5Server socks5 && (!socks5.Auth() || ModeController.Features.HasFlag(ModeFeature.SupportSocks5Auth)))
{
- ModeController = ModeService.GetModeControllerByType(mode.Type, out var modePort, out var portName);
-
- if (modePort != null)
- TryReleaseTcpPort((ushort)modePort, portName);
-
- if (Server is Socks5Server socks5 && (!socks5.Auth() || ModeController.Features.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);
+ Socks5Server = socks5;
}
- catch (Exception e)
+ else
{
- releaser.Dispose();
- await StopAsync();
+ // Start Server Controller to get a local socks5 server
+ Log.Debug("Server Information: {Data}", $"{server.Type} {server.MaskedData()}");
- switch (e)
- {
- case DllNotFoundException:
- case FileNotFoundException:
- throw new Exception(e.Message + "\n\n" + i18N.Translate("Missing File or runtime components"));
- case MessageException:
- throw;
- default:
- Log.Error(e, "Unhandled Exception When Start MainController");
- Utils.Utils.Open(Constants.LogFile);
- throw new MessageException($"{i18N.Translate("Unhandled Exception")}\n{e.Message}");
- }
+ 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);
}
-
- public static async Task StopAsync()
+ catch (Exception e)
{
- if (Lock.CurrentCount == 0)
- {
- (await Lock.EnterAsync()).Dispose();
- if (ServerController == null && ModeController == null)
- // stopped
- return;
+ releaser.Dispose();
+ await StopAsync();
- // else begin stop
+ switch (e)
+ {
+ case DllNotFoundException:
+ case FileNotFoundException:
+ throw new Exception(e.Message + "\n\n" + i18N.Translate("Missing File or runtime components"));
+ case MessageException:
+ throw;
+ default:
+ Log.Error(e, "Unhandled Exception When Start MainController");
+ Utils.Utils.Open(Constants.LogFile);
+ throw new MessageException($"{i18N.Translate("Unhandled Exception")}\n{e.Message}");
}
-
- using var _ = await Lock.EnterAsync();
-
- if (ServerController == null && ModeController == null)
- return;
-
- Log.Information("Stop Main Controller");
- StatusPortInfoText.Reset();
-
- var tasks = new[]
- {
- ServerController?.StopAsync() ?? Task.CompletedTask,
- ModeController?.StopAsync() ?? Task.CompletedTask
- };
-
- try
- {
- await Task.WhenAll(tasks);
- }
- catch (Exception e)
- {
- Log.Error(e, "MainController Stop Error");
- }
-
- ServerController = null;
- ModeController = null;
- }
-
- public static void PortCheck(ushort port, string portName, PortType portType = PortType.Both)
- {
- try
- {
- PortHelper.CheckPort(port, portType);
- }
- catch (PortInUseException)
- {
- throw new MessageException(i18N.TranslateFormat("The {0} port is in use.", $"{portName} ({port})"));
- }
- catch (PortReservedException)
- {
- throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})"));
- }
- }
-
- public static void TryReleaseTcpPort(ushort port, string portName)
- {
- foreach (var p in PortHelper.GetProcessByUsedTcpPort(port))
- {
- var fileName = p.MainModule?.FileName;
- if (fileName == null)
- continue;
-
- if (fileName.StartsWith(Global.NetchDir))
- {
- p.Kill();
- p.WaitForExit();
- }
- else
- {
- throw new MessageException(i18N.TranslateFormat("The {0} port is used by {1}.", $"{portName} ({port})", $"({p.Id}){fileName}"));
- }
- }
-
- PortCheck(port, portName, PortType.TCP);
- }
-
- public static async Task DiscoveryNatTypeAsync(CancellationToken ctx = default)
- {
- Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null");
- return await Socks5ServerTestUtils.DiscoveryNatTypeAsync(Socks5Server, ctx);
- }
-
- public static async Task 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;
}
}
+
+ public static async Task StopAsync()
+ {
+ if (Lock.CurrentCount == 0)
+ {
+ (await Lock.EnterAsync()).Dispose();
+ if (ServerController == null && ModeController == null)
+ // stopped
+ return;
+
+ // else begin stop
+ }
+
+ using var _ = await Lock.EnterAsync();
+
+ if (ServerController == null && ModeController == null)
+ return;
+
+ Log.Information("Stop Main Controller");
+ StatusPortInfoText.Reset();
+
+ var tasks = new[]
+ {
+ ServerController?.StopAsync() ?? Task.CompletedTask,
+ ModeController?.StopAsync() ?? Task.CompletedTask
+ };
+
+ try
+ {
+ await Task.WhenAll(tasks);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "MainController Stop Error");
+ }
+
+ ServerController = null;
+ ModeController = null;
+ }
+
+ public static void PortCheck(ushort port, string portName, PortType portType = PortType.Both)
+ {
+ try
+ {
+ PortHelper.CheckPort(port, portType);
+ }
+ catch (PortInUseException)
+ {
+ throw new MessageException(i18N.TranslateFormat("The {0} port is in use.", $"{portName} ({port})"));
+ }
+ catch (PortReservedException)
+ {
+ throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})"));
+ }
+ }
+
+ public static void TryReleaseTcpPort(ushort port, string portName)
+ {
+ foreach (var p in PortHelper.GetProcessByUsedTcpPort(port))
+ {
+ var fileName = p.MainModule?.FileName;
+ if (fileName == null)
+ continue;
+
+ if (fileName.StartsWith(Global.NetchDir))
+ {
+ p.Kill();
+ p.WaitForExit();
+ }
+ else
+ {
+ throw new MessageException(i18N.TranslateFormat("The {0} port is used by {1}.", $"{portName} ({port})", $"({p.Id}){fileName}"));
+ }
+ }
+
+ PortCheck(port, portName, PortType.TCP);
+ }
+
+ public static async Task DiscoveryNatTypeAsync(CancellationToken ctx = default)
+ {
+ Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null");
+ return await Socks5ServerTestUtils.DiscoveryNatTypeAsync(Socks5Server, ctx);
+ }
+
+ public static async Task 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;
+ }
}
\ No newline at end of file
diff --git a/Netch/Controllers/NFController.cs b/Netch/Controllers/NFController.cs
index 82a02e6c..18399095 100644
--- a/Netch/Controllers/NFController.cs
+++ b/Netch/Controllers/NFController.cs
@@ -1,262 +1,255 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
+using System.Net;
using System.ServiceProcess;
-using System.Threading.Tasks;
using Netch.Interfaces;
using Netch.Models;
using Netch.Models.Modes;
using Netch.Models.Modes.ProcessMode;
using Netch.Servers;
using Netch.Utils;
-using Serilog;
using static Netch.Interops.Redirector;
-namespace Netch.Controllers
+namespace Netch.Controllers;
+
+public class NFController : IModeController
{
- public class NFController : IModeController
+ private Server? _server;
+ private Redirector _mode = null!;
+ private RedirectorConfig _rdrConfig = null!;
+
+ private static readonly ServiceController NFService = new("netfilter2");
+
+ private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys";
+
+ public string Name => "Redirector";
+
+ public ModeFeature Features => ModeFeature.SupportIPv6 | ModeFeature.SupportSocks5Auth;
+
+ public async Task StartAsync(Socks5Server server, Mode mode)
{
- private Server? _server;
- private Redirector _mode = null!;
- private RedirectorConfig _rdrConfig = null!;
+ if (mode is not Redirector processMode)
+ throw new InvalidOperationException();
- private static readonly ServiceController NFService = new("netfilter2");
+ _server = server;
+ _mode = processMode;
+ _rdrConfig = Global.Settings.Redirector;
- private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys";
+ CheckDriver();
- public string Name => "Redirector";
+ Dial(NameList.AIO_FILTERLOOPBACK, _mode.FilterLoopback);
+ Dial(NameList.AIO_FILTERINTRANET, _mode.FilterIntranet);
+ Dial(NameList.AIO_FILTERPARENT, _mode.FilterParent ?? _rdrConfig.HandleOnlyDNS);
+ Dial(NameList.AIO_FILTERICMP, _mode.FilterICMP ?? _rdrConfig.FilterICMP);
+ if (_mode.FilterICMP ?? _rdrConfig.FilterICMP)
+ Dial(NameList.AIO_ICMPING, (_mode.FilterICMP != null ? _mode.ICMPDelay ?? 10 : _rdrConfig.ICMPDelay).ToString());
- public ModeFeature Features => ModeFeature.SupportIPv6 | ModeFeature.SupportSocks5Auth;
+ Dial(NameList.AIO_FILTERTCP, _mode.FilterTCP ?? _rdrConfig.FilterTCP);
+ Dial(NameList.AIO_FILTERUDP, _mode.FilterUDP ?? _rdrConfig.FilterUDP);
- public async Task StartAsync(Socks5Server server, Mode mode)
+ // DNS
+ Dial(NameList.AIO_FILTERDNS, _mode.FilterDNS ?? _rdrConfig.FilterDNS);
+ Dial(NameList.AIO_DNSONLY, _mode.HandleOnlyDNS ?? _rdrConfig.HandleOnlyDNS);
+ Dial(NameList.AIO_DNSPROX, _mode.DNSProxy ?? _rdrConfig.DNSProxy);
+ if (_mode.FilterDNS ?? _rdrConfig.FilterDNS)
{
- if (mode is not Redirector processMode)
- throw new InvalidOperationException();
+ var dnsStr = _mode.FilterDNS != null ? _mode.DNSHost : _rdrConfig.DNSHost;
- _server = server;
- _mode = processMode;
- _rdrConfig = Global.Settings.Redirector;
+ dnsStr = dnsStr.ValueOrDefault() ?? $"{Constants.DefaultPrimaryDNS}:53";
- CheckDriver();
+ var dns = IPEndPoint.Parse(dnsStr);
+ if (dns.Port == 0)
+ dns.Port = 53;
- Dial(NameList.AIO_FILTERLOOPBACK, _mode.FilterLoopback);
- Dial(NameList.AIO_FILTERINTRANET, _mode.FilterIntranet);
- Dial(NameList.AIO_FILTERPARENT, _mode.FilterParent ?? _rdrConfig.HandleOnlyDNS);
- Dial(NameList.AIO_FILTERICMP, _mode.FilterICMP ?? _rdrConfig.FilterICMP);
- if (_mode.FilterICMP ?? _rdrConfig.FilterICMP)
- Dial(NameList.AIO_ICMPING, (_mode.FilterICMP != null ? _mode.ICMPDelay ?? 10 : _rdrConfig.ICMPDelay).ToString());
-
- Dial(NameList.AIO_FILTERTCP, _mode.FilterTCP ?? _rdrConfig.FilterTCP);
- Dial(NameList.AIO_FILTERUDP, _mode.FilterUDP ?? _rdrConfig.FilterUDP);
-
- // DNS
- Dial(NameList.AIO_FILTERDNS, _mode.FilterDNS ?? _rdrConfig.FilterDNS);
- Dial(NameList.AIO_DNSONLY, _mode.HandleOnlyDNS ?? _rdrConfig.HandleOnlyDNS);
- Dial(NameList.AIO_DNSPROX, _mode.DNSProxy ?? _rdrConfig.DNSProxy);
- if (_mode.FilterDNS ?? _rdrConfig.FilterDNS)
- {
- var dnsStr = _mode.FilterDNS != null ? _mode.DNSHost : _rdrConfig.DNSHost;
-
- dnsStr = dnsStr.ValueOrDefault() ?? $"{Constants.DefaultPrimaryDNS}:53";
-
- var dns = IPEndPoint.Parse(dnsStr);
- if (dns.Port == 0)
- dns.Port = 53;
-
- Dial(NameList.AIO_DNSHOST, dns.Address.ToString());
- Dial(NameList.AIO_DNSPORT, dns.Port.ToString());
- }
-
- // Server
- Dial(NameList.AIO_TGTHOST, await server.AutoResolveHostnameAsync());
- Dial(NameList.AIO_TGTPORT, server.Port.ToString());
- Dial(NameList.AIO_TGTUSER, server.Username ?? string.Empty);
- Dial(NameList.AIO_TGTPASS, server.Password ?? string.Empty);
-
- // Mode Rule
- DialRule();
-
- if (!await InitAsync())
- throw new MessageException("Redirector start failed.");
+ Dial(NameList.AIO_DNSHOST, dns.Address.ToString());
+ Dial(NameList.AIO_DNSPORT, dns.Port.ToString());
}
- public async Task StopAsync()
- {
- await FreeAsync();
- }
+ // Server
+ Dial(NameList.AIO_TGTHOST, await server.AutoResolveHostnameAsync());
+ Dial(NameList.AIO_TGTPORT, server.Port.ToString());
+ Dial(NameList.AIO_TGTUSER, server.Username ?? string.Empty);
+ Dial(NameList.AIO_TGTPASS, server.Password ?? string.Empty);
- #region CheckRule
+ // Mode Rule
+ DialRule();
- ///
- ///
- ///
- ///
- /// No Problem true
- private static bool CheckCppRegex(string r, bool clear = true)
- {
- try
- {
- if (r.StartsWith("!"))
- return Dial(NameList.AIO_ADDNAME, r.Substring(1));
-
- return Dial(NameList.AIO_ADDNAME, r);
- }
- finally
- {
- if (clear)
- Dial(NameList.AIO_CLRNAME, "");
- }
- }
-
- ///
- ///
- ///
- ///
- /// No Problem true
- public static bool CheckRules(IEnumerable rules, out IEnumerable results)
- {
- results = rules.Where(r => !CheckCppRegex(r, false));
- Dial(NameList.AIO_CLRNAME, "");
- return !results.Any();
- }
-
- public static string GenerateInvalidRulesMessage(IEnumerable rules)
- {
- return $"{string.Join("\n", rules)}\n" + i18N.Translate("Above rules does not conform to C++ regular expression syntax");
- }
-
- #endregion
-
- private void DialRule()
- {
- Dial(NameList.AIO_CLRNAME, "");
- var invalidList = new List();
- foreach (var s in _mode.Bypass)
- {
- if (!Dial(NameList.AIO_BYPNAME, s))
- invalidList.Add(s);
- }
-
- foreach (var s in _mode.Handle)
- {
- if (!Dial(NameList.AIO_ADDNAME, s))
- invalidList.Add(s);
- }
-
- if (invalidList.Any())
- throw new MessageException(GenerateInvalidRulesMessage(invalidList));
-
- // Bypass Self
- Dial(NameList.AIO_BYPNAME, "^" + Global.NetchDir.ToRegexString());
- }
-
- #region DriverUtil
-
- private static void CheckDriver()
- {
- var binFileVersion = Utils.Utils.GetFileVersion(Constants.NFDriver);
- var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver);
-
- Log.Information("Built-in netfilter2 driver version: {Name}", binFileVersion);
- Log.Information("Installed netfilter2 driver version: {Name}", systemFileVersion);
-
- if (!File.Exists(SystemDriver))
- {
- // Install
- InstallDriver();
- return;
- }
-
- var reinstall = false;
- if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
- {
- if (binResult.CompareTo(systemResult) > 0)
- // Update
- reinstall = true;
- else if (systemResult.Major != binResult.Major)
- // Downgrade when Major version different (may have breaking changes)
- reinstall = true;
- }
- else
- {
- // Parse File versionName to Version failed
- if (!systemFileVersion.Equals(binFileVersion))
- // versionNames are different, Reinstall
- reinstall = true;
- }
-
- if (!reinstall)
- return;
-
- Log.Information("Update netfilter2 driver");
- UninstallDriver();
- InstallDriver();
- }
-
- ///
- /// 安装 NF 驱动
- ///
- /// 驱动是否安装成功
- private static void InstallDriver()
- {
- Log.Information("Install netfilter2 driver");
- Global.MainForm.StatusText(i18N.Translate("Installing netfilter2 driver"));
-
- if (!File.Exists(Constants.NFDriver))
- throw new MessageException(i18N.Translate("builtin driver files missing, can't install NF driver"));
-
- try
- {
- File.Copy(Constants.NFDriver, SystemDriver);
- }
- catch (Exception e)
- {
- Log.Error(e, "Copy netfilter2.sys failed\n");
- throw new MessageException($"Copy netfilter2.sys failed\n{e.Message}");
- }
-
- // 注册驱动文件
- if (Interops.Redirector.aio_register("netfilter2"))
- {
- Log.Information("Install netfilter2 driver finished");
- }
- else
- {
- Log.Error("Register netfilter2 failed");
- }
- }
-
- ///
- /// 卸载 NF 驱动
- ///
- /// 是否成功卸载
- public static bool UninstallDriver()
- {
- Log.Information("Uninstall netfilter2");
- try
- {
- if (NFService.Status == ServiceControllerStatus.Running)
- {
- NFService.Stop();
- NFService.WaitForStatus(ServiceControllerStatus.Stopped);
- }
- }
- catch (Exception)
- {
- // ignored
- }
-
- if (!File.Exists(SystemDriver))
- return true;
-
- Interops.Redirector.aio_unregister("netfilter2");
- File.Delete(SystemDriver);
-
- return true;
- }
-
- #endregion
+ if (!await InitAsync())
+ throw new MessageException("Redirector start failed.");
}
+
+ public async Task StopAsync()
+ {
+ await FreeAsync();
+ }
+
+ #region CheckRule
+
+ ///
+ ///
+ ///
+ ///
+ /// No Problem true
+ private static bool CheckCppRegex(string r, bool clear = true)
+ {
+ try
+ {
+ if (r.StartsWith("!"))
+ return Dial(NameList.AIO_ADDNAME, r.Substring(1));
+
+ return Dial(NameList.AIO_ADDNAME, r);
+ }
+ finally
+ {
+ if (clear)
+ Dial(NameList.AIO_CLRNAME, "");
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// No Problem true
+ public static bool CheckRules(IEnumerable rules, out IEnumerable results)
+ {
+ results = rules.Where(r => !CheckCppRegex(r, false));
+ Dial(NameList.AIO_CLRNAME, "");
+ return !results.Any();
+ }
+
+ public static string GenerateInvalidRulesMessage(IEnumerable rules)
+ {
+ return $"{string.Join("\n", rules)}\n" + i18N.Translate("Above rules does not conform to C++ regular expression syntax");
+ }
+
+ #endregion
+
+ private void DialRule()
+ {
+ Dial(NameList.AIO_CLRNAME, "");
+ var invalidList = new List();
+ foreach (var s in _mode.Bypass)
+ {
+ if (!Dial(NameList.AIO_BYPNAME, s))
+ invalidList.Add(s);
+ }
+
+ foreach (var s in _mode.Handle)
+ {
+ if (!Dial(NameList.AIO_ADDNAME, s))
+ invalidList.Add(s);
+ }
+
+ if (invalidList.Any())
+ throw new MessageException(GenerateInvalidRulesMessage(invalidList));
+
+ // Bypass Self
+ Dial(NameList.AIO_BYPNAME, "^" + Global.NetchDir.ToRegexString());
+ }
+
+ #region DriverUtil
+
+ private static void CheckDriver()
+ {
+ var binFileVersion = Utils.Utils.GetFileVersion(Constants.NFDriver);
+ var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver);
+
+ Log.Information("Built-in netfilter2 driver version: {Name}", binFileVersion);
+ Log.Information("Installed netfilter2 driver version: {Name}", systemFileVersion);
+
+ if (!File.Exists(SystemDriver))
+ {
+ // Install
+ InstallDriver();
+ return;
+ }
+
+ var reinstall = false;
+ if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
+ {
+ if (binResult.CompareTo(systemResult) > 0)
+ // Update
+ reinstall = true;
+ else if (systemResult.Major != binResult.Major)
+ // Downgrade when Major version different (may have breaking changes)
+ reinstall = true;
+ }
+ else
+ {
+ // Parse File versionName to Version failed
+ if (!systemFileVersion.Equals(binFileVersion))
+ // versionNames are different, Reinstall
+ reinstall = true;
+ }
+
+ if (!reinstall)
+ return;
+
+ Log.Information("Update netfilter2 driver");
+ UninstallDriver();
+ InstallDriver();
+ }
+
+ ///
+ /// 安装 NF 驱动
+ ///
+ /// 驱动是否安装成功
+ private static void InstallDriver()
+ {
+ Log.Information("Install netfilter2 driver");
+ Global.MainForm.StatusText(i18N.Translate("Installing netfilter2 driver"));
+
+ if (!File.Exists(Constants.NFDriver))
+ throw new MessageException(i18N.Translate("builtin driver files missing, can't install NF driver"));
+
+ try
+ {
+ File.Copy(Constants.NFDriver, SystemDriver);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Copy netfilter2.sys failed\n");
+ throw new MessageException($"Copy netfilter2.sys failed\n{e.Message}");
+ }
+
+ // 注册驱动文件
+ if (Interops.Redirector.aio_register("netfilter2"))
+ {
+ Log.Information("Install netfilter2 driver finished");
+ }
+ else
+ {
+ Log.Error("Register netfilter2 failed");
+ }
+ }
+
+ ///
+ /// 卸载 NF 驱动
+ ///
+ /// 是否成功卸载
+ public static bool UninstallDriver()
+ {
+ Log.Information("Uninstall netfilter2");
+ try
+ {
+ if (NFService.Status == ServiceControllerStatus.Running)
+ {
+ NFService.Stop();
+ NFService.WaitForStatus(ServiceControllerStatus.Stopped);
+ }
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+
+ if (!File.Exists(SystemDriver))
+ return true;
+
+ Interops.Redirector.aio_unregister("netfilter2");
+ File.Delete(SystemDriver);
+
+ return true;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/Netch/Controllers/PcapController.cs b/Netch/Controllers/PcapController.cs
index ae1c28f2..e5338b67 100644
--- a/Netch/Controllers/PcapController.cs
+++ b/Netch/Controllers/PcapController.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Text;
using Microsoft.VisualStudio.Threading;
using Netch.Forms;
using Netch.Interfaces;
@@ -13,93 +8,92 @@ using Netch.Models.Modes.ShareMode;
using Netch.Servers;
using Netch.Utils;
-namespace Netch.Controllers
+namespace Netch.Controllers;
+
+public class PcapController : Guard, IModeController
{
- public class PcapController : Guard, IModeController
+ private readonly LogForm _form;
+ private ShareMode _mode = null!;
+ private Socks5Server _server = null!;
+
+ public PcapController() : base("pcap2socks.exe", encoding: Encoding.UTF8)
{
- private readonly LogForm _form;
- private ShareMode _mode = null!;
- private Socks5Server _server = null!;
+ _form = new LogForm(Global.MainForm);
+ _form.CreateControl();
+ }
- public PcapController() : base("pcap2socks.exe", encoding: Encoding.UTF8)
+ protected override IEnumerable StartedKeywords { get; } = new[] { "└" };
+
+ public override string Name => "pcap2socks";
+
+ public ModeFeature Features => 0;
+
+ public async Task StartAsync(Socks5Server server, Mode mode)
+ {
+ if (mode is not ShareMode shareMode)
+ throw new InvalidOperationException();
+
+ _server = server;
+ _mode = shareMode;
+
+ var outboundNetworkInterface = NetworkInterfaceUtils.GetBest();
+
+ var arguments = new List