From 9b12ae946242f3e35eaf6e3eaa5df199182777a1 Mon Sep 17 00:00:00 2001 From: ChsBuffer <33744752+chsbuffer@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:00:18 +0800 Subject: [PATCH] Refactoring --- .../EncryptedProxy/SSController.cs | 75 -- Netch/Controllers/ICSController.cs | 15 +- Netch/Controllers/Interface/ModeController.cs | 6 +- Netch/Controllers/MainController.cs | 45 +- Netch/Controllers/Mode/HTTPController.cs | 14 +- Netch/Controllers/Mode/NFController.cs | 22 +- Netch/Controllers/Mode/TUNTAPController.cs | 12 +- Netch/Controllers/PrivoxyController.cs | 6 +- Netch/Forms/MainForm.Control.cs | 2 +- Netch/Forms/MainForm.Designer.cs | 52 +- Netch/Forms/MainForm.MenuStrip.cs | 41 +- Netch/Forms/MainForm.Profile.cs | 4 +- Netch/Forms/MainForm.Server_Mode.cs | 18 + Netch/Forms/MainForm.Status.cs | 3 +- Netch/Forms/MainForm.cs | 54 +- Netch/Forms/Mode/Process.cs | 1 + Netch/Forms/SettingForm.cs | 2 +- Netch/Global.cs | 137 +--- Netch/Models/IServerUtil.cs | 42 ++ Netch/Models/LegacyServer.cs | 115 --- Netch/Models/LegacySetting.cs | 60 -- Netch/Models/Server.cs | 99 +-- .../ServerController.cs} | 7 +- .../WinFW/NetworkConnectionCollection.cs | 3 +- Netch/NativeMethods.cs | 12 - Netch/Netch.csproj | 3 - Netch/Resources/zh-CN | 1 + .../Form/ShadowsocksForm.Designer.cs} | 8 +- .../Shadowsocks/Form/ShadowsocksForm.cs} | 45 +- .../Shadowsocks/Form/ShadowsocksForm.resx} | 0 .../Shadowsocks}/Models/SSD/Main.cs | 6 +- .../Shadowsocks/Models/SSD/SSDServer.cs} | 6 +- .../Shadowsocks/Models/ShadowsocksConfig.cs} | 4 +- Netch/ServerEx/Shadowsocks/SSController.cs | 95 +++ Netch/ServerEx/Shadowsocks/SSUtil.cs | 185 +++++ Netch/ServerEx/Shadowsocks/Shadowsocks.cs | 62 ++ .../Form/ShadowsocksRForm.Designer.cs} | 8 +- .../ShadowsocksR/Form/ShadowsocksRForm.cs} | 53 +- .../ShadowsocksR/Form/ShadowsocksRForm.resx} | 0 .../ShadowsocksR}/SSRController.cs | 13 +- Netch/ServerEx/ShadowsocksR/SSRUtil.cs | 144 ++++ Netch/ServerEx/ShadowsocksR/ShadowsocksR.cs | 100 +++ .../Socks5/Form/Socks5Form.Designer.cs} | 8 +- .../Socks5/Form/Socks5Form.cs} | 45 +- .../Socks5/Form/Socks5Form.resx} | 0 Netch/ServerEx/Socks5/S5Util.cs | 81 +++ Netch/ServerEx/Socks5/Socks5.cs | 22 + .../Trojan/Form/TrojanForm.Designer.cs} | 10 +- .../Trojan/Form/TrojanForm.cs} | 45 +- .../Trojan/Form/TrojanForm.resx} | 0 .../Trojan/Models/TrojanConfig.cs} | 8 +- Netch/ServerEx/Trojan/Trojan.cs | 22 + .../Trojan}/TrojanController.cs | 10 +- Netch/ServerEx/Trojan/TrojanUtil.cs | 102 +++ .../VMess/Form/VMessForm.Designer.cs} | 8 +- .../VMess/Form/VMessForm.cs} | 63 +- .../VMess/Form/VMessForm.resx} | 0 .../VMess/Models/VMessConfig.cs} | 6 +- .../VMess/Models/VMessJObject.cs} | 6 +- Netch/ServerEx/VMess/VMess.cs | 118 +++ .../VMess}/VMessController.cs | 71 +- Netch/ServerEx/VMess/VMessUtil.cs | 151 ++++ Netch/Utils/Bandwidth.cs | 4 +- Netch/Utils/Configuration.cs | 70 +- Netch/Utils/Extensions.cs | 23 - Netch/Utils/Firewall.cs | 2 - Netch/Utils/OnlyInstance.cs | 2 +- Netch/Utils/Servers.cs | 51 ++ Netch/Utils/ShareLink.cs | 674 ++++-------------- Netch/Utils/TUNTAP.cs | 27 + Netch/Utils/Utils.cs | 27 +- Netch/Utils/WMI.cs | 20 - 72 files changed, 1661 insertions(+), 1595 deletions(-) delete mode 100644 Netch/Controllers/EncryptedProxy/SSController.cs create mode 100644 Netch/Models/IServerUtil.cs delete mode 100644 Netch/Models/LegacyServer.cs delete mode 100644 Netch/Models/LegacySetting.cs rename Netch/{Controllers/Interface/EncryptedProxy.cs => Models/ServerController.cs} (86%) rename Netch/{Forms/Server/Shadowsocks.Designer.cs => ServerEx/Shadowsocks/Form/ShadowsocksForm.Designer.cs} (98%) rename Netch/{Forms/Server/Shadowsocks.cs => ServerEx/Shadowsocks/Form/ShadowsocksForm.cs} (58%) rename Netch/{Forms/Server/Shadowsocks.resx => ServerEx/Shadowsocks/Form/ShadowsocksForm.resx} (100%) rename Netch/{ => ServerEx/Shadowsocks}/Models/SSD/Main.cs (89%) rename Netch/{Models/SSD/Server.cs => ServerEx/Shadowsocks/Models/SSD/SSDServer.cs} (90%) rename Netch/{Models/SS/ShadowsocksServer.cs => ServerEx/Shadowsocks/Models/ShadowsocksConfig.cs} (80%) create mode 100644 Netch/ServerEx/Shadowsocks/SSController.cs create mode 100644 Netch/ServerEx/Shadowsocks/SSUtil.cs create mode 100644 Netch/ServerEx/Shadowsocks/Shadowsocks.cs rename Netch/{Forms/Server/ShadowsocksR.Designer.cs => ServerEx/ShadowsocksR/Form/ShadowsocksRForm.Designer.cs} (99%) rename Netch/{Forms/Server/ShadowsocksR.cs => ServerEx/ShadowsocksR/Form/ShadowsocksRForm.cs} (56%) rename Netch/{Forms/Server/ShadowsocksR.resx => ServerEx/ShadowsocksR/Form/ShadowsocksRForm.resx} (100%) rename Netch/{Controllers/EncryptedProxy => ServerEx/ShadowsocksR}/SSRController.cs (80%) create mode 100644 Netch/ServerEx/ShadowsocksR/SSRUtil.cs create mode 100644 Netch/ServerEx/ShadowsocksR/ShadowsocksR.cs rename Netch/{Forms/Server/Socks5.Designer.cs => ServerEx/Socks5/Form/Socks5Form.Designer.cs} (98%) rename Netch/{Forms/Server/Socks5.cs => ServerEx/Socks5/Form/Socks5Form.cs} (54%) rename Netch/{Forms/Server/Socks5.resx => ServerEx/Socks5/Form/Socks5Form.resx} (100%) create mode 100644 Netch/ServerEx/Socks5/S5Util.cs create mode 100644 Netch/ServerEx/Socks5/Socks5.cs rename Netch/{Forms/Server/Trojan.Designer.cs => ServerEx/Trojan/Form/TrojanForm.Designer.cs} (97%) rename Netch/{Forms/Server/Trojan.cs => ServerEx/Trojan/Form/TrojanForm.cs} (52%) rename Netch/{Forms/Server/Trojan.resx => ServerEx/Trojan/Form/TrojanForm.resx} (100%) rename Netch/{Models/Trojan.cs => ServerEx/Trojan/Models/TrojanConfig.cs} (96%) create mode 100644 Netch/ServerEx/Trojan/Trojan.cs rename Netch/{Controllers/EncryptedProxy => ServerEx/Trojan}/TrojanController.cs (78%) create mode 100644 Netch/ServerEx/Trojan/TrojanUtil.cs rename Netch/{Forms/Server/Vmess.Designer.cs => ServerEx/VMess/Form/VMessForm.Designer.cs} (99%) rename Netch/{Forms/Server/Vmess.cs => ServerEx/VMess/Form/VMessForm.cs} (60%) rename Netch/{Forms/Server/Vmess.resx => ServerEx/VMess/Form/VMessForm.resx} (100%) rename Netch/{Models/Information/VMess.cs => ServerEx/VMess/Models/VMessConfig.cs} (98%) rename Netch/{Models/VMess.cs => ServerEx/VMess/Models/VMessJObject.cs} (95%) create mode 100644 Netch/ServerEx/VMess/VMess.cs rename Netch/{Controllers/EncryptedProxy => ServerEx/VMess}/VMessController.cs (73%) create mode 100644 Netch/ServerEx/VMess/VMessUtil.cs delete mode 100644 Netch/Utils/Extensions.cs create mode 100644 Netch/Utils/Servers.cs delete mode 100644 Netch/Utils/WMI.cs diff --git a/Netch/Controllers/EncryptedProxy/SSController.cs b/Netch/Controllers/EncryptedProxy/SSController.cs deleted file mode 100644 index 46337e7a..00000000 --- a/Netch/Controllers/EncryptedProxy/SSController.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Text; -using Netch.Models; -using Netch.Utils; - -namespace Netch.Controllers -{ - public class SSController : EncryptedProxy - { - private bool dllFlag; - - public SSController() - { - Name = "Shadowsocks"; - MainFile = "Shadowsocks.exe"; - StartedKeywords.Add("listening at"); - StoppedKeywords.AddRange(new[] {"Invalid config path", "usage", "plugin service exit unexpectedly"}); - } - - public override bool Start(Server server, Mode mode) - { - //从DLL启动Shaowsocks - if (Global.Settings.BootShadowsocksFromDLL && (mode.Type == 0 || mode.Type == 1 || mode.Type == 2)) - { - dllFlag = true; - State = State.Starting; - var client = Encoding.UTF8.GetBytes($"{LocalAddress}:{Socks5LocalPort}"); - var remote = Encoding.UTF8.GetBytes($"{server.Hostname}:{server.Port}"); - var passwd = Encoding.UTF8.GetBytes($"{server.Password}"); - var method = Encoding.UTF8.GetBytes($"{server.EncryptMethod}"); - if (!NativeMethods.Shadowsocks.Info(client, remote, passwd, method)) - { - State = State.Stopped; - Logging.Error("DLL SS INFO 设置失败!"); - return false; - } - - Logging.Info("DLL SS INFO 设置成功!"); - - if (!NativeMethods.Shadowsocks.Start()) - { - State = State.Stopped; - Logging.Error("DLL SS 启动失败!"); - return false; - } - - Logging.Info("DLL SS 启动成功!"); - State = State.Started; - return true; - } - - #region Argument - - var argument = new StringBuilder(); - argument.Append( - $"-s {server.Hostname} -p {server.Port} -b {LocalAddress} -l {Socks5LocalPort} -m {server.EncryptMethod} -k \"{server.Password}\" -u"); - if (!string.IsNullOrWhiteSpace(server.Plugin) && !string.IsNullOrWhiteSpace(server.PluginOption)) - argument.Append($" --plugin {server.Plugin} --plugin-opts \"{server.PluginOption}\""); - if (mode.BypassChina) argument.Append(" --acl default.acl"); - - #endregion - - return StartInstanceAuto(argument.ToString()); - } - - /// - /// SSController 停止 - /// - public override void Stop() - { - if (dllFlag) NativeMethods.Shadowsocks.Stop(); - else - StopInstance(); - } - } -} \ No newline at end of file diff --git a/Netch/Controllers/ICSController.cs b/Netch/Controllers/ICSController.cs index 0e668579..fd7fbe04 100644 --- a/Netch/Controllers/ICSController.cs +++ b/Netch/Controllers/ICSController.cs @@ -51,7 +51,7 @@ namespace Netch.Controllers ushort[] gatewayMetric = null; string[] dns = null; - var outboundWmi = WMI.GetManagementObjectByDeviceNameOrDefault(Global.Outbound.Adapter.Description); + var outboundWmi = GetManagementObjectByDeviceNameOrDefault(Global.Outbound.Adapter.Description); if (outboundWmi == null) { @@ -167,5 +167,18 @@ namespace Netch.Controllers entry.Put(options); } } + + public static ManagementObject GetManagementObjectByDeviceNameOrDefault(string deviceName) + { + foreach (ManagementObject mo in new ManagementClass("Win32_NetworkAdapterConfiguration").GetInstances()) + { + if (((string) mo["Caption"]).EndsWith(deviceName)) + { + return mo; + } + } + + return null; + } } } \ No newline at end of file diff --git a/Netch/Controllers/Interface/ModeController.cs b/Netch/Controllers/Interface/ModeController.cs index 696320af..8958834a 100644 --- a/Netch/Controllers/Interface/ModeController.cs +++ b/Netch/Controllers/Interface/ModeController.cs @@ -7,9 +7,11 @@ namespace Netch.Controllers /// /// 启动 /// - /// 服务器 + /// 服务器 /// 模式 /// 是否成功 - public abstract bool Start(Server server, Mode mode); + public abstract bool Start(Server s, Mode mode); + + public abstract bool TestNatRequired { get; } } } \ No newline at end of file diff --git a/Netch/Controllers/MainController.cs b/Netch/Controllers/MainController.cs index 9ebfcccd..eb962a0f 100644 --- a/Netch/Controllers/MainController.cs +++ b/Netch/Controllers/MainController.cs @@ -10,7 +10,7 @@ namespace Netch.Controllers { public static class MainController { - public static EncryptedProxy EncryptedProxyController { get; private set; } + public static ServerController ServerController { get; private set; } public static ModeController ModeController { get; private set; } public static bool NttTested; @@ -27,7 +27,7 @@ namespace Netch.Controllers { Logging.Info($"启动主控制器: {server.Type} [{mode.Type}]{mode.Remark}"); - if (server.Type == "Socks5" && mode.Type == 4) + if (server.IsSocks5() && mode.Type == 4) { return false; } @@ -54,15 +54,8 @@ namespace Netch.Controllers throw new StartFailedException(); } - // 成功启动 - switch (mode.Type) - { - case 0: - case 1: - case 2: - NatTest(); - break; - } + if (ModeController.TestNatRequired) + NatTest(); return true; } @@ -97,32 +90,18 @@ namespace Netch.Controllers private static async Task StartServer(Server server, Mode mode) { - switch (server.Type) + if (server.IsSocks5()) { - case "Socks5": - return true; - case "SS": - EncryptedProxyController = new SSController(); - break; - case "SSR": - EncryptedProxyController = new SSRController(); - break; - case "VMess": - EncryptedProxyController = new VMessController(); - break; - case "Trojan": - EncryptedProxyController = new TrojanController(); - break; - default: - Logging.Error("未知服务器类型"); - return false; + return true; } - Utils.Utils.KillProcessByName(EncryptedProxyController.MainFile); + ServerController = Servers.GetUtilByTypeName(server.Type).GetController(); + + Utils.Utils.KillProcessByName(ServerController.MainFile); PortCheckAndShowMessageBox(Global.Settings.Socks5LocalPort, "Socks5"); - Global.MainForm.StatusText(i18N.Translate("Starting ", EncryptedProxyController.Name)); - if (await Task.Run(() => EncryptedProxyController.Start(server, mode))) + Global.MainForm.StatusText(i18N.Translate("Starting ", ServerController.Name)); + if (await Task.Run(() => ServerController.Start(server, mode))) { UsingPorts.Add(StatusPortInfoText.Socks5Port = Global.Settings.Socks5LocalPort); StatusPortInfoText.ShareLan = Global.Settings.LocalAddress == "0.0.0.0"; @@ -186,7 +165,7 @@ namespace Netch.Controllers var tasks = new[] { - Task.Run(() => EncryptedProxyController?.Stop()), + Task.Run(() => ServerController?.Stop()), Task.Run(() => ModeController?.Stop()), }; await Task.WhenAll(tasks); diff --git a/Netch/Controllers/Mode/HTTPController.cs b/Netch/Controllers/Mode/HTTPController.cs index 87bd61ee..47be4084 100644 --- a/Netch/Controllers/Mode/HTTPController.cs +++ b/Netch/Controllers/Mode/HTTPController.cs @@ -4,12 +4,15 @@ using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; using Netch.Models; +using Netch.ServerEx.Socks5; using Netch.Utils; namespace Netch.Controllers { public class HTTPController : ModeController { + public override bool TestNatRequired { get; } = false; + public const string IEProxyExceptions = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"; /// @@ -28,24 +31,25 @@ namespace Netch.Controllers /// /// 启动 /// - /// 服务器 + /// 服务器 /// 模式 /// 是否启动成功 - public override bool Start(Server server, Mode mode) + public override bool Start(Server s, Mode mode) { RecordPrevious(); try { - if (server.Type == "Socks5") + if (s.IsSocks5()) { + var server = (Socks5) s; if (!string.IsNullOrWhiteSpace(server.Username) && !string.IsNullOrWhiteSpace(server.Password)) return false; - pPrivoxyController.Start(server, mode); + pPrivoxyController.Start(s, mode); } else { - pPrivoxyController.Start(server, mode); + pPrivoxyController.Start(s, mode); } if (mode.Type == 3) NativeMethods.SetGlobal($"127.0.0.1:{Global.Settings.HTTPLocalPort}", IEProxyExceptions); diff --git a/Netch/Controllers/Mode/NFController.cs b/Netch/Controllers/Mode/NFController.cs index cb1ab0b8..8876f1b2 100644 --- a/Netch/Controllers/Mode/NFController.cs +++ b/Netch/Controllers/Mode/NFController.cs @@ -11,6 +11,8 @@ namespace Netch.Controllers { public class NFController : ModeController { + public override bool TestNatRequired { get; } = true; + private static readonly ServiceController NFService = new ServiceController("netfilter2"); private static readonly string BinDriver = string.Empty; @@ -45,7 +47,7 @@ namespace Netch.Controllers Name = "Redirector"; } - public override bool Start(Server server, Mode mode) + public override bool Start(Server s, Mode mode) { Logging.Info("内置驱动版本: " + Utils.Utils.FileVersion(BinDriver)); if (Utils.Utils.FileVersion(SystemDriver) != Utils.Utils.FileVersion(BinDriver)) @@ -69,22 +71,22 @@ namespace Netch.Controllers aio_dial((int) NameList.TYPE_ADDNAME, "NTT.exe"); - if (server.Type != "Socks5") + if (s.IsSocks5()) { - aio_dial((int) NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Settings.Socks5LocalPort}"); - aio_dial((int) NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Settings.Socks5LocalPort}"); - } - else - { - var result = DNS.Lookup(server.Hostname); + var result = DNS.Lookup(s.Hostname); if (result == null) { Logging.Info("无法解析服务器 IP 地址"); return false; } - aio_dial((int) NameList.TYPE_TCPHOST, $"{result}:{server.Port}"); - aio_dial((int) NameList.TYPE_UDPHOST, $"{result}:{server.Port}"); + aio_dial((int) NameList.TYPE_TCPHOST, $"{result}:{s.Port}"); + aio_dial((int) NameList.TYPE_UDPHOST, $"{result}:{s.Port}"); + } + else + { + aio_dial((int) NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Settings.Socks5LocalPort}"); + aio_dial((int) NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Settings.Socks5LocalPort}"); } if (Global.Settings.ModifySystemDNS) diff --git a/Netch/Controllers/Mode/TUNTAPController.cs b/Netch/Controllers/Mode/TUNTAPController.cs index 8eb97931..b22ba467 100644 --- a/Netch/Controllers/Mode/TUNTAPController.cs +++ b/Netch/Controllers/Mode/TUNTAPController.cs @@ -16,6 +16,8 @@ namespace Netch.Controllers { public class TUNTAPController : ModeController { + public override bool TestNatRequired { get; } = true; + // ByPassLan IP private readonly List _bypassLanIPs = new List {"10.0.0.0/8", "172.16.0.0/16", "192.168.0.0/16"}; @@ -198,10 +200,10 @@ namespace Netch.Controllers return true; } - public override bool Start(Server server, Mode mode) + public override bool Start(Server s, Mode mode) { _savedMode = mode; - _savedServer = server; + _savedServer = s; if (!Configure()) return false; @@ -229,8 +231,8 @@ namespace Netch.Controllers } var argument = new StringBuilder(); - if (server.Type == "Socks5") - argument.Append($"-proxyServer {server.Hostname}:{server.Port} "); + if (s.IsSocks5()) + argument.Append($"-proxyServer {s.Hostname}:{s.Port} "); else argument.Append($"-proxyServer 127.0.0.1:{Global.Settings.Socks5LocalPort} "); @@ -303,7 +305,7 @@ namespace Netch.Controllers if (MessageBoxX.Show(i18N.Translate("TUN/TAP driver is not detected. Is it installed now?"), confirm: true) == DialogResult.OK) { - Configuration.addtap(); + TUNTAP.addtap(); // 给点时间,不然立马安装完毕就查找适配器可能会导致找不到适配器ID Thread.Sleep(1000); if (string.IsNullOrEmpty(Global.TUNTAP.ComponentID = TUNTAP.GetComponentID())) diff --git a/Netch/Controllers/PrivoxyController.cs b/Netch/Controllers/PrivoxyController.cs index 7f6ecf84..2b5df91c 100644 --- a/Netch/Controllers/PrivoxyController.cs +++ b/Netch/Controllers/PrivoxyController.cs @@ -14,13 +14,11 @@ namespace Netch.Controllers public bool Start(Server server, Mode mode) { - var isSocks5 = server.Type == "Socks5"; - var socks5Port = isSocks5 ? server.Port : Global.Settings.Socks5LocalPort; var text = File.ReadAllText("bin\\default.conf") .Replace("_BIND_PORT_", Global.Settings.HTTPLocalPort.ToString()) - .Replace("_DEST_PORT_", socks5Port.ToString()) + .Replace("_DEST_PORT_", (server.IsSocks5() ? server.Port : Global.Settings.Socks5LocalPort).ToString()) .Replace("0.0.0.0", Global.Settings.LocalAddress); - if (isSocks5) + if (server.IsSocks5()) text = text.Replace("/ 127.0.0.1", $"/ {server.Hostname}"); File.WriteAllText("data\\privoxy.conf", text); diff --git a/Netch/Forms/MainForm.Control.cs b/Netch/Forms/MainForm.Control.cs index 6d310396..d67d9996 100644 --- a/Netch/Forms/MainForm.Control.cs +++ b/Netch/Forms/MainForm.Control.cs @@ -22,7 +22,7 @@ namespace Netch.Forms if (State == State.Waiting || State == State.Stopped) { // 服务器、模式 需选择 - if (!(ServerComboBox.SelectedItem is Models.Server server)) + if (!(ServerComboBox.SelectedItem is Server server)) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; diff --git a/Netch/Forms/MainForm.Designer.cs b/Netch/Forms/MainForm.Designer.cs index 473ddea1..ded9318e 100644 --- a/Netch/Forms/MainForm.Designer.cs +++ b/Netch/Forms/MainForm.Designer.cs @@ -33,11 +33,6 @@ this.MenuStrip = new System.Windows.Forms.MenuStrip(); this.ServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ImportServersFromClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.AddSocks5ServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.AddShadowsocksServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.AddShadowsocksRServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.AddVMessServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.AddTrojanServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.CreateProcessModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ReloadModesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -129,12 +124,7 @@ // ServerToolStripMenuItem // this.ServerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.ImportServersFromClipboardToolStripMenuItem, - this.AddSocks5ServerToolStripMenuItem, - this.AddShadowsocksServerToolStripMenuItem, - this.AddShadowsocksRServerToolStripMenuItem, - this.AddVMessServerToolStripMenuItem, - this.AddTrojanServerToolStripMenuItem}); + this.ImportServersFromClipboardToolStripMenuItem}); this.ServerToolStripMenuItem.Margin = new System.Windows.Forms.Padding(3, 0, 0, 1); this.ServerToolStripMenuItem.Name = "ServerToolStripMenuItem"; this.ServerToolStripMenuItem.Size = new System.Drawing.Size(57, 21); @@ -147,41 +137,6 @@ this.ImportServersFromClipboardToolStripMenuItem.Text = "Import Servers From Clipboard"; this.ImportServersFromClipboardToolStripMenuItem.Click += new System.EventHandler(this.ImportServersFromClipboardToolStripMenuItem_Click); // - // AddSocks5ServerToolStripMenuItem - // - this.AddSocks5ServerToolStripMenuItem.Name = "AddSocks5ServerToolStripMenuItem"; - this.AddSocks5ServerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); - this.AddSocks5ServerToolStripMenuItem.Text = "Add [Socks5] Server"; - this.AddSocks5ServerToolStripMenuItem.Click += new System.EventHandler(this.AddServerToolStripMenuItem_Click); - // - // AddShadowsocksServerToolStripMenuItem - // - this.AddShadowsocksServerToolStripMenuItem.Name = "AddShadowsocksServerToolStripMenuItem"; - this.AddShadowsocksServerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); - this.AddShadowsocksServerToolStripMenuItem.Text = "Add [Shadowsocks] Server"; - this.AddShadowsocksServerToolStripMenuItem.Click += new System.EventHandler(this.AddServerToolStripMenuItem_Click); - // - // AddShadowsocksRServerToolStripMenuItem - // - this.AddShadowsocksRServerToolStripMenuItem.Name = "AddShadowsocksRServerToolStripMenuItem"; - this.AddShadowsocksRServerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); - this.AddShadowsocksRServerToolStripMenuItem.Text = "Add [ShadowsocksR] Server"; - this.AddShadowsocksRServerToolStripMenuItem.Click += new System.EventHandler(this.AddServerToolStripMenuItem_Click); - // - // AddVMessServerToolStripMenuItem - // - this.AddVMessServerToolStripMenuItem.Name = "AddVMessServerToolStripMenuItem"; - this.AddVMessServerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); - this.AddVMessServerToolStripMenuItem.Text = "Add [VMess] Server"; - this.AddVMessServerToolStripMenuItem.Click += new System.EventHandler(this.AddServerToolStripMenuItem_Click); - // - // AddTrojanServerToolStripMenuItem - // - this.AddTrojanServerToolStripMenuItem.Name = "AddTrojanServerToolStripMenuItem"; - this.AddTrojanServerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); - this.AddTrojanServerToolStripMenuItem.Text = "Add [Trojan] Server"; - this.AddTrojanServerToolStripMenuItem.Click += new System.EventHandler(this.AddServerToolStripMenuItem_Click); - // // ModeToolStripMenuItem // this.ModeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -751,11 +706,6 @@ } private System.Windows.Forms.ToolStripButton AboutToolStripButton; - private System.Windows.Forms.ToolStripMenuItem AddShadowsocksRServerToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem AddShadowsocksServerToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem AddSocks5ServerToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem AddTrojanServerToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem AddVMessServerToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem CleanDNSCacheToolStripMenuItem; private System.Windows.Forms.TableLayoutPanel configLayoutPanel; private System.Windows.Forms.GroupBox ConfigurationGroupBox; diff --git a/Netch/Forms/MainForm.MenuStrip.cs b/Netch/Forms/MainForm.MenuStrip.cs index 065f5a1e..ca3dafa3 100644 --- a/Netch/Forms/MainForm.MenuStrip.cs +++ b/Netch/Forms/MainForm.MenuStrip.cs @@ -6,11 +6,8 @@ using System.Threading.Tasks; using System.Windows.Forms; using Netch.Controllers; using Netch.Forms.Mode; -using Netch.Forms.Server; using Netch.Models; using Netch.Utils; -using Trojan = Netch.Forms.Server.Trojan; -using VMess = Netch.Forms.Server.VMess; namespace Netch.Forms { @@ -29,7 +26,7 @@ namespace Netch.Forms var texts = Clipboard.GetText(); if (!string.IsNullOrWhiteSpace(texts)) { - var result = ShareLink.Parse(texts); + var result = ShareLink.ParseText(texts); if (result != null) { @@ -50,18 +47,15 @@ namespace Netch.Forms private void AddServerToolStripMenuItem_Click(object sender, EventArgs e) { - Form form = ((ToolStripMenuItem) sender).Name switch - { - "AddSocks5ServerToolStripMenuItem" => new Socks5(), - "AddShadowsocksServerToolStripMenuItem" => new Shadowsocks(), - "AddShadowsocksRServerToolStripMenuItem" => new ShadowsocksR(), - "AddVMessServerToolStripMenuItem" => new VMess(), - "AddTrojanServerToolStripMenuItem" => new Trojan(), - _ => null - }; + var s = ((ToolStripMenuItem) sender).Text; + + var start = s.IndexOf("[", StringComparison.Ordinal) + 1; + var end = s.IndexOf("]", start, StringComparison.Ordinal); + var result = s.Substring(start, end - start); Hide(); - form?.ShowDialog(); + Servers.GetUtilByFullName(result).Create(); + InitServer(); Configuration.Save(); Show(); @@ -147,7 +141,7 @@ namespace Netch.Forms Remark = "ProxyUpdate", Type = 5 }; - await MainController.Start(ServerComboBox.SelectedItem as Models.Server, mode); + await MainController.Start(ServerComboBox.SelectedItem as Server, mode); } var serverLock = new object(); @@ -164,20 +158,11 @@ namespace Netch.Forms var str = await WebUtil.DownloadStringAsync(request); - try - { - str = ShareLink.URLSafeBase64Decode(str); - } - catch - { - // ignored - } - lock (serverLock) { Global.Settings.Server.RemoveAll(server => server.Group == item.Remark); - var result = ShareLink.Parse(str); + var result = ShareLink.ParseText(str); if (result != null) { foreach (var server in result) @@ -300,7 +285,7 @@ namespace Netch.Forms Type = 5 }; State = State.Starting; - await MainController.Start(ServerComboBox.SelectedItem as Models.Server, mode); + await MainController.Start(ServerComboBox.SelectedItem as Server, mode); } var req = WebUtil.CreateRequest(Global.Settings.ACL); @@ -355,8 +340,8 @@ namespace Netch.Forms { await Task.Run(() => { - Configuration.deltapall(); - Configuration.addtap(); + TUNTAP.deltapall(); + TUNTAP.addtap(); }); StatusText(i18N.Translate("Reinstall TUN/TAP driver successfully")); } diff --git a/Netch/Forms/MainForm.Profile.cs b/Netch/Forms/MainForm.Profile.cs index 8ea02d58..753f4986 100644 --- a/Netch/Forms/MainForm.Profile.cs +++ b/Netch/Forms/MainForm.Profile.cs @@ -91,7 +91,7 @@ namespace Netch.Forms if (p.IsDummy) throw new Exception("Profile not found."); - var server = ServerComboBox.Items.Cast().FirstOrDefault(s => s.Remark.Equals(p.ServerRemark)); + var server = ServerComboBox.Items.Cast().FirstOrDefault(s => s.Remark.Equals(p.ServerRemark)); var mode = ModeComboBox.Items.Cast().FirstOrDefault(m => m.Remark.Equals(p.ModeRemark)); if (server == null) @@ -110,7 +110,7 @@ namespace Netch.Forms private void SaveProfile(int index) { - var selectedServer = (Models.Server) ServerComboBox.SelectedItem; + var selectedServer = (Server) ServerComboBox.SelectedItem; var selectedMode = (Models.Mode) ModeComboBox.SelectedItem; var name = ProfileNameText.Text; diff --git a/Netch/Forms/MainForm.Server_Mode.cs b/Netch/Forms/MainForm.Server_Mode.cs index 139457dd..88939a66 100644 --- a/Netch/Forms/MainForm.Server_Mode.cs +++ b/Netch/Forms/MainForm.Server_Mode.cs @@ -1,7 +1,9 @@ using System; using System.Drawing; +using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; +using Netch.Utils; namespace Netch.Forms { @@ -152,5 +154,21 @@ namespace Netch.Forms // ignored } } + + private void AddAddServerToolStripMenuItems() + { + foreach (var serversUtil in Servers.ServerUtils.Where(i => !string.IsNullOrEmpty(i.FullName))) + { + var fullName = serversUtil.FullName; + var control = new ToolStripMenuItem + { + Name = $"Add{fullName}ServerToolStripMenuItem", + Size = new Size(259, 22), + Text = i18N.TranslateFormat("Add [{0}] Server", fullName), + }; + control.Click += AddServerToolStripMenuItem_Click; + ServerToolStripMenuItem.DropDownItems.Add(control); + } + } } } \ No newline at end of file diff --git a/Netch/Forms/MainForm.Status.cs b/Netch/Forms/MainForm.Status.cs index 35596997..1c7700e5 100644 --- a/Netch/Forms/MainForm.Status.cs +++ b/Netch/Forms/MainForm.Status.cs @@ -1,7 +1,6 @@ using System; using System.Drawing; using System.Text; -using Netch.Controllers; using Netch.Models; using Netch.Utils; @@ -13,6 +12,8 @@ namespace Netch.Forms partial class MainForm { + private bool IsWaiting => State == State.Waiting || State == State.Stopped; + private State _state = State.Waiting; /// diff --git a/Netch/Forms/MainForm.cs b/Netch/Forms/MainForm.cs index 0388b807..51b17ae6 100644 --- a/Netch/Forms/MainForm.cs +++ b/Netch/Forms/MainForm.cs @@ -5,17 +5,13 @@ using System.Windows.Forms; using Microsoft.Win32; using Netch.Controllers; using Netch.Forms.Mode; -using Netch.Forms.Server; using Netch.Models; using Netch.Utils; -using Trojan = Netch.Forms.Server.Trojan; -using VMess = Netch.Forms.Server.VMess; namespace Netch.Forms { public partial class MainForm : Form { - public MainForm() { InitializeComponent(); @@ -40,6 +36,8 @@ namespace Netch.Forms private void MainForm_Load(object sender, EventArgs e) { + AddAddServerToolStripMenuItems(); + OnlyInstance.Called += OnCalled; // 计算 ComboBox绘制 目标宽度 _eWidth = ServerComboBox.Width / 10; @@ -180,11 +178,6 @@ namespace Netch.Forms { ServerToolStripMenuItem.Text = i18N.Translate("Server"); ImportServersFromClipboardToolStripMenuItem.Text = i18N.Translate("Import Servers From Clipboard"); - AddSocks5ServerToolStripMenuItem.Text = i18N.Translate("Add [Socks5] Server"); - AddShadowsocksServerToolStripMenuItem.Text = i18N.Translate("Add [Shadowsocks] Server"); - AddShadowsocksRServerToolStripMenuItem.Text = i18N.Translate("Add [ShadowsocksR] Server"); - AddVMessServerToolStripMenuItem.Text = i18N.Translate("Add [VMess] Server"); - AddTrojanServerToolStripMenuItem.Text = i18N.Translate("Add [Trojan] Server"); ModeToolStripMenuItem.Text = i18N.Translate("Mode"); HelpToolStripMenuItem.Text = i18N.Translate("Help"); CreateProcessModeToolStripMenuItem.Text = i18N.Translate("Create Process Mode"); @@ -227,7 +220,7 @@ namespace Netch.Forms private void Exit(bool forceExit = false) { - if (State != State.Waiting && State != State.Stopped && !Global.Settings.StopWhenExited && !forceExit) + if (!IsWaiting && !Global.Settings.StopWhenExited && !forceExit) { MessageBoxX.Show(i18N.Translate("Please press Stop button first")); @@ -237,9 +230,8 @@ namespace Netch.Forms Hide(); NotifyIcon.Visible = false; - if (State != State.Waiting && State != State.Stopped) + if (!IsWaiting) { - // 已启动 ControlFun(); } @@ -282,17 +274,11 @@ namespace Netch.Forms return; } - Form server = Global.Settings.Server[ServerComboBox.SelectedIndex].Type switch - { - "Socks5" => new Socks5(Global.Settings.Server[ServerComboBox.SelectedIndex]), - "SS" => new Shadowsocks(Global.Settings.Server[ServerComboBox.SelectedIndex]), - "SSR" => new ShadowsocksR(Global.Settings.Server[ServerComboBox.SelectedIndex]), - "VMess" => new VMess(Global.Settings.Server[ServerComboBox.SelectedIndex]), - "Trojan" => new Trojan(Global.Settings.Server[ServerComboBox.SelectedIndex]), - _ => null - }; Hide(); - server?.ShowDialog(); + + var server = Global.Settings.Server[ServerComboBox.SelectedIndex]; + Servers.GetUtilByTypeName(server.Type).Edit(server); + InitServer(); Configuration.Save(); Show(); @@ -318,7 +304,7 @@ namespace Netch.Forms private void EditModePictureBox_Click(object sender, EventArgs e) { // 当前ModeComboBox中至少有一项 - if (ModeComboBox.Items.Count <= 0 || ModeComboBox.SelectedIndex == -1) + if (ModeComboBox.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a mode first")); return; @@ -331,7 +317,6 @@ namespace Netch.Forms { Hide(); new Process(selectedMode).ShowDialog(); - InitMode(); Show(); break; } @@ -352,10 +337,7 @@ namespace Netch.Forms return; } - var selectedMode = (Models.Mode) ModeComboBox.SelectedItem; - ModeComboBox.Items.Remove(selectedMode); - Modes.Delete(selectedMode); - + Modes.Delete((Models.Mode) ModeComboBox.SelectedItem); SelectLastMode(); } @@ -368,11 +350,21 @@ namespace Netch.Forms return; } - var selectedMode = (Models.Server) ServerComboBox.SelectedItem; try { //听说巨硬BUG经常会炸,所以Catch一下 :D - Clipboard.SetText(ShareLink.GetShareLink(selectedMode)); + var server = (Server) ServerComboBox.SelectedItem; + string text; + if (ModifierKeys == Keys.Control) + { + text = ShareLink.GetNetchLink(server); + } + else + { + text = ShareLink.GetShareLink(server); + } + + Clipboard.SetText(text); } catch (Exception) { @@ -391,7 +383,7 @@ namespace Netch.Forms var index = ServerComboBox.SelectedIndex; - Global.Settings.Server.Remove(ServerComboBox.SelectedItem as Models.Server); + Global.Settings.Server.Remove(ServerComboBox.SelectedItem as Server); InitServer(); Configuration.Save(); diff --git a/Netch/Forms/Mode/Process.cs b/Netch/Forms/Mode/Process.cs index b83f6d13..5234d6c3 100644 --- a/Netch/Forms/Mode/Process.cs +++ b/Netch/Forms/Mode/Process.cs @@ -215,6 +215,7 @@ namespace Netch.Forms.Mode _mode.Rule.AddRange(RuleListBox.Items.Cast()); Modes.WriteFile(_mode); + Global.MainForm.InitMode(); Edited = false; MessageBoxX.Show(i18N.Translate("Mode updated successfully")); } diff --git a/Netch/Forms/SettingForm.cs b/Netch/Forms/SettingForm.cs index 5a44a94d..65760b42 100644 --- a/Netch/Forms/SettingForm.cs +++ b/Netch/Forms/SettingForm.cs @@ -156,7 +156,7 @@ namespace Netch.Forms static void CheckPort(string portName, int port, int originPort, PortType portType = PortType.Both) { - if (port <= 0 || port >= 65536) + if (port <= 0 || port > 65536) throw new FormatException(); if (port == originPort) diff --git a/Netch/Global.cs b/Netch/Global.cs index 1f10a127..72160ba5 100644 --- a/Netch/Global.cs +++ b/Netch/Global.cs @@ -11,14 +11,13 @@ namespace Netch { public static class Global { - /// /// 换行 /// public const string EOF = "\r\n"; public static readonly string NetchDir = Application.StartupPath; - + /// /// 主窗体的静态实例 /// @@ -26,138 +25,6 @@ namespace Netch public static bool SupportFakeDns = false; - /// - /// SS/SSR 加密方式 - /// - public static class EncryptMethods - { - /// - /// SS 加密列表 - /// - public static List SS = new List - { - "rc4-md5", - "aes-128-gcm", - "aes-192-gcm", - "aes-256-gcm", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "bf-cfb", - "chacha20-ietf-poly1305", - "xchacha20-ietf-poly1305", - "salsa20", - "chacha20", - "chacha20-ietf" - }; - - /// - /// SSR 加密列表 - /// - public static List SSR = new List - { - "none", - "table", - "rc4", - "rc4-md5", - "rc4-md5-6", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "bf-cfb", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "cast5-cfb", - "des-cfb", - "idea-cfb", - "rc2-cfb", - "seed-cfb", - "salsa20", - "chacha20", - "chacha20-ietf" - }; - /// - /// VMess 解密列表 - /// - public static List VMess = new List - { - "auto", - "none", - "aes-128-gcm", - "chacha20-poly1305" - }; - - /// - /// VMess QUIC 加密列表 - /// - public static List VMessQUIC = new List - { - "none", - "aes-128-gcm", - "chacha20-poly1305" - }; - } - - /// - /// SSR 协议列表 - /// - public static List Protocols = new List - { - "origin", - "verify_deflate", - "auth_sha1_v4", - "auth_aes128_md5", - "auth_aes128_sha1", - "auth_chain_a" - }; - - /// - /// SSR 混淆列表 - /// - public static List OBFSs = new List - { - "plain", - "http_simple", - "http_post", - "tls1.2_ticket_auth" - }; - - /// - /// V2Ray 传输协议 - /// - public static List TransferProtocols = new List - { - "tcp", - "kcp", - "ws", - "h2", - "quic" - }; - - /// - /// V2Ray 伪装类型 - /// - public static List FakeTypes = new List - { - "none", - "http", - "srtp", - "utp", - "wechat-video", - "dtls", - "wireguard" - }; - /// /// 出口适配器 /// @@ -212,4 +79,4 @@ namespace Netch /// public static readonly List Modes = new List(); } -} +} \ No newline at end of file diff --git a/Netch/Models/IServerUtil.cs b/Netch/Models/IServerUtil.cs new file mode 100644 index 00000000..097d6ab0 --- /dev/null +++ b/Netch/Models/IServerUtil.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Netch.Models +{ + public interface IServerUtil + { + /// + /// Collection order basis + /// + ushort Priority { get; } + + /// + /// Server.Type + /// + string TypeName { get; } + + /// + /// Protocol Name + /// + string FullName { get; } + + /// + /// Support URI + /// + string[] UriScheme { get; } + + Server ParseJObject(JObject j); + + public void Edit(Server s); + + public void Create(); + + string GetShareLink(Server server); + + public abstract ServerController GetController(); + + public abstract IEnumerable ParseUri(string text); + + bool CheckServer(Server s); + } +} \ No newline at end of file diff --git a/Netch/Models/LegacyServer.cs b/Netch/Models/LegacyServer.cs deleted file mode 100644 index 87bebc8b..00000000 --- a/Netch/Models/LegacyServer.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace Netch.Models -{ - public class LegacyServer - { - /// - /// 备注 - /// - public string Remark; - - /// - /// 组 - /// - public string Group = "None"; - - /// - /// 类型(Socks5、Shadowsocks、ShadowsocksR、VMess) - /// - public string Type; - - /// - /// 地址 - /// - public string Address; - - /// - /// 端口 - /// - public int Port; - - /// - /// 用户名 - /// - public string Username; - - /// - /// 密码 - /// - public string Password; - - /// - /// 用户 ID(V2) - /// - public string UserID = string.Empty; - - /// - /// 额外 ID(V2) - /// - public int AlterID = 0; - - /// - /// 加密方式 - /// - public string EncryptMethod; - - /// - /// 协议 - /// - public string Protocol; - - /// - /// 协议参数 - /// - public string ProtocolParam; - - /// - /// 混淆(SSR)/ 插件(SS) - /// - public string OBFS; - - /// - /// 混淆参数(SSR)/ 插件参数(SS) - /// - public string OBFSParam; - - /// - /// 传输协议(V2) - /// - public string TransferProtocol = "tcp"; - - /// - /// 伪装类型(V2) - /// - public string FakeType = string.Empty; - - /// - /// 伪装域名(V2:HTTP、WebSocket、HTTP/2) - /// - public string Host = string.Empty; - - /// - /// 传输路径(V2:WebSocket、HTTP/2) - /// - public string Path = string.Empty; - - /// - /// QUIC 加密方式(V2) - /// - public string QUICSecurity = "none"; - - /// - /// QUIC 加密密钥(V2) - /// - public string QUICSecret = string.Empty; - - /// - /// TLS 底层传输安全(V2) - /// - public bool TLSSecure = false; - - /// - /// 延迟 - /// - public int Delay = -1; - } -} diff --git a/Netch/Models/LegacySetting.cs b/Netch/Models/LegacySetting.cs deleted file mode 100644 index d99b6b9d..00000000 --- a/Netch/Models/LegacySetting.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; - -namespace Netch.Models -{ - /// - /// 用于读取和写入的配置的类 - /// - public class LegacySetting - { - /// - /// 服务器选择位置 - /// - public int ServerComboBoxSelectedIndex = 0; - - /// - /// 模式选择位置 - /// - public int ModeComboBoxSelectedIndex = 0; - - /// - /// HTTP 本地端口 - /// - public int HTTPLocalPort = 2802; - - /// - /// Socks5 本地端口 - /// - public int Socks5LocalPort = 2801; - - /// - /// HTTP 和 Socks5 本地代理地址 - /// - public string LocalAddress = "127.0.0.1"; - - /// - /// Redirector TCP 占用端口 - /// - public int RedirectorTCPPort = 2800; - - /// - /// TUNTAP 适配器配置 - /// - public TUNTAPConfig TUNTAP = new TUNTAPConfig(); - - /// - /// 服务器列表 - /// - public List Server = new List(); - - /// - /// 订阅链接列表 - /// - public List SubscribeLink = new List(); - - /// - /// 全局绕过 IP 列表 - /// - public List BypassIPs = new List(); - } -} diff --git a/Netch/Models/Server.cs b/Netch/Models/Server.cs index 92a9ddc4..18b90ee0 100644 --- a/Netch/Models/Server.cs +++ b/Netch/Models/Server.cs @@ -17,7 +17,7 @@ namespace Netch.Models public string Group = "None"; /// - /// 代理类型(HTTP、HTTPS、Socks5、SS、SSR、VMess) + /// 代理类型 /// public string Type; @@ -36,101 +36,6 @@ namespace Netch.Models /// public int Port; - /// - /// 账号(HTTP、HTTPS、Socks5) - /// - public string Username; - - /// - /// 密码(HTTP、HTTPS、Socks5、SS、SSR) - /// - public string Password; - - /// - /// 用户 ID(VMess) - /// - public string UserID = string.Empty; - - /// - /// 额外 ID(VMess) - /// - public int AlterID = 0; - - /// - /// 加密方式(SS、SSR、VMess) - /// - public string EncryptMethod; - - /// - /// 插件(SS) - /// - public string Plugin; - - /// - /// 插件参数(SS) - /// - public string PluginOption; - - /// - /// 协议(SSR) - /// - public string Protocol = Global.Protocols[0]; - - /// - /// 协议参数(SSR) - /// - public string ProtocolParam; - - /// - /// 混淆(SSR) - /// - public string OBFS = Global.OBFSs[0]; - - /// - /// 混淆参数(SSR) - /// - public string OBFSParam; - - /// - /// 传输协议(VMess) - /// - public string TransferProtocol = Global.TransferProtocols[0]; - - /// - /// 伪装类型(VMess) - /// - public string FakeType = Global.FakeTypes[0]; - - /// - /// 伪装域名(VMess:HTTP、WebSocket、HTTP/2) - /// - public string Host = string.Empty; - - /// - /// 传输路径(VMess:WebSocket、HTTP/2) - /// - public string Path = string.Empty; - - /// - /// QUIC 加密方式(VMess) - /// - public string QUICSecure = Global.EncryptMethods.VMessQUIC[0]; - - /// - /// QUIC 加密密钥(VMess) - /// - public string QUICSecret = string.Empty; - - /// - /// TLS 底层传输安全(VMess) - /// - public bool TLSSecure = false; - - /// - /// Mux 多路复用(VMess) - /// - public bool UseMux = true; - /// /// 延迟 /// @@ -141,6 +46,8 @@ namespace Netch.Models /// public string Country; + public bool IsSocks5() => Type == "Socks5"; + /// /// 获取备注 /// diff --git a/Netch/Controllers/Interface/EncryptedProxy.cs b/Netch/Models/ServerController.cs similarity index 86% rename from Netch/Controllers/Interface/EncryptedProxy.cs rename to Netch/Models/ServerController.cs index 5f3f4a6d..493bbb6f 100644 --- a/Netch/Controllers/Interface/EncryptedProxy.cs +++ b/Netch/Models/ServerController.cs @@ -1,8 +1,8 @@ -using Netch.Models; +using Netch.Controllers; -namespace Netch.Controllers +namespace Netch.Models { - public abstract class EncryptedProxy : Controller + public abstract class ServerController : Controller { private int? _socks5Port; @@ -27,5 +27,6 @@ namespace Netch.Controllers /// 模式 /// 是否启动成功 public abstract bool Start(Server server, Mode mode); + } } \ No newline at end of file diff --git a/Netch/Models/WinFW/NetworkConnectionCollection.cs b/Netch/Models/WinFW/NetworkConnectionCollection.cs index 89ad9b3b..171510c4 100644 --- a/Netch/Models/WinFW/NetworkConnectionCollection.cs +++ b/Netch/Models/WinFW/NetworkConnectionCollection.cs @@ -1,7 +1,6 @@ using System.Collections; using System.Linq; -using System.Management; -using NETCONLib; + using NETCONLib; namespace WinFW { diff --git a/Netch/NativeMethods.cs b/Netch/NativeMethods.cs index b7f7dff3..c1c521ba 100644 --- a/Netch/NativeMethods.cs +++ b/Netch/NativeMethods.cs @@ -64,18 +64,6 @@ namespace Netch [DllImport("sysproxy", CallingConvention = CallingConvention.Cdecl)] public static extern bool SetURL([MarshalAs(UnmanagedType.LPTStr)] string remote); - public class Shadowsocks - { - [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] - public static extern bool Info(byte[] client, byte[] remote, byte[] passwd, byte[] method); - - [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] - public static extern bool Start(); - - [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] - public static extern void Stop(); - } - [DllImport("dnsapi", EntryPoint = "DnsFlushResolverCache")] public static extern uint FlushDNSResolverCache(); } diff --git a/Netch/Netch.csproj b/Netch/Netch.csproj index 161fc70e..bc7bd024 100644 --- a/Netch/Netch.csproj +++ b/Netch/Netch.csproj @@ -87,9 +87,6 @@ - - Form - True True diff --git a/Netch/Resources/zh-CN b/Netch/Resources/zh-CN index 50856791..00127ba7 100644 --- a/Netch/Resources/zh-CN +++ b/Netch/Resources/zh-CN @@ -33,6 +33,7 @@ "Server": "服务器", "Import Servers From Clipboard": "从剪贴板导入服务器", "Import servers error!": "未找到可导入的链接!", + "Add [{0}] Server": "添加 [{0}] 服务器", "Add [Socks5] Server": "添加 [Socks5] 服务器", "Add [Shadowsocks] Server": "添加 [Shadowsocks] 服务器", "Add [ShadowsocksR] Server": "添加 [ShadowsocksR] 服务器", diff --git a/Netch/Forms/Server/Shadowsocks.Designer.cs b/Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.Designer.cs similarity index 98% rename from Netch/Forms/Server/Shadowsocks.Designer.cs rename to Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.Designer.cs index 6159e23a..a2dc534e 100644 --- a/Netch/Forms/Server/Shadowsocks.Designer.cs +++ b/Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.Designer.cs @@ -1,6 +1,6 @@ -namespace Netch.Forms.Server +namespace Netch.ServerEx.Shadowsocks.Form { - partial class Shadowsocks + partial class ShadowsocksForm { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Shadowsocks)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ShadowsocksForm)); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.PluginOptionsTextBox = new System.Windows.Forms.TextBox(); this.PluginOptionsLabel = new System.Windows.Forms.Label(); @@ -215,7 +215,7 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.MaximizeBox = false; - this.Name = "Shadowsocks"; + this.Name = "ShadowsocksForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Shadowsocks"; this.Load += new System.EventHandler(this.Shadowsocks_Load); diff --git a/Netch/Forms/Server/Shadowsocks.cs b/Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.cs similarity index 58% rename from Netch/Forms/Server/Shadowsocks.cs rename to Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.cs index 87615917..d32ee87e 100644 --- a/Netch/Forms/Server/Shadowsocks.cs +++ b/Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.cs @@ -1,19 +1,18 @@ using System; -using System.Drawing; using System.Windows.Forms; using Netch.Utils; -namespace Netch.Forms.Server +namespace Netch.ServerEx.Shadowsocks.Form { - public partial class Shadowsocks : Form + public partial class ShadowsocksForm : System.Windows.Forms.Form { - private readonly Models.Server _server; + private readonly Shadowsocks _server; - public Shadowsocks(Models.Server server = default) + public ShadowsocksForm(Shadowsocks server = default) { InitializeComponent(); - _server = server ?? new Models.Server {EncryptMethod = Global.EncryptMethods.SS[0]}; + _server = server ?? new Shadowsocks(); } private void Shadowsocks_Load(object sender, EventArgs e) @@ -29,7 +28,7 @@ namespace Netch.Forms.Server PluginOptionsLabel.Text = i18N.Translate(PluginOptionsLabel.Text); ControlButton.Text = i18N.Translate(ControlButton.Text); - EncryptMethodComboBox.Items.AddRange(Global.EncryptMethods.SS.ToArray()); + EncryptMethodComboBox.Items.AddRange(SSGlobal.EncryptMethods.ToArray()); #endregion @@ -37,43 +36,19 @@ namespace Netch.Forms.Server AddressTextBox.Text = _server.Hostname; PortTextBox.Text = _server.Port.ToString(); PasswordTextBox.Text = _server.Password; - EncryptMethodComboBox.SelectedIndex = Global.EncryptMethods.SS.IndexOf(_server.EncryptMethod); + EncryptMethodComboBox.SelectedIndex = SSGlobal.EncryptMethods.IndexOf(_server.EncryptMethod); PluginTextBox.Text = _server.Plugin; PluginOptionsTextBox.Text = _server.PluginOption; } private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) { - if (sender is ComboBox cbx) - { - e.DrawBackground(); - - if (e.Index >= 0) - { - var sf = new StringFormat - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Center - }; - - var brush = new SolidBrush(cbx.ForeColor); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - brush = SystemBrushes.HighlightText as SolidBrush; - } - - e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, sf); - } - } + Utils.Utils.DrawCenterComboBox(sender, e); } private void ControlButton_Click(object sender, EventArgs e) { - if (!int.TryParse(PortTextBox.Text, out var port)) - { - return; - } + if (!ushort.TryParse(PortTextBox.Text, out var port)) return; _server.Remark = RemarkTextBox.Text; _server.Type = "SS"; @@ -86,9 +61,7 @@ namespace Netch.Forms.Server _server.Country = null; if (Global.Settings.Server.IndexOf(_server) == -1) - { Global.Settings.Server.Add(_server); - } MessageBoxX.Show(i18N.Translate("Saved")); Close(); diff --git a/Netch/Forms/Server/Shadowsocks.resx b/Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.resx similarity index 100% rename from Netch/Forms/Server/Shadowsocks.resx rename to Netch/ServerEx/Shadowsocks/Form/ShadowsocksForm.resx diff --git a/Netch/Models/SSD/Main.cs b/Netch/ServerEx/Shadowsocks/Models/SSD/Main.cs similarity index 89% rename from Netch/Models/SSD/Main.cs rename to Netch/ServerEx/Shadowsocks/Models/SSD/Main.cs index 61dcce4a..cf9ebe61 100644 --- a/Netch/Models/SSD/Main.cs +++ b/Netch/ServerEx/Shadowsocks/Models/SSD/Main.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Netch.Models.SSD +namespace Netch.ServerEx.Shadowsocks.Models.SSD { public class Main { @@ -37,6 +37,6 @@ namespace Netch.Models.SSD /// /// 服务器数组 /// - public List servers; + public List servers; } -} +} \ No newline at end of file diff --git a/Netch/Models/SSD/Server.cs b/Netch/ServerEx/Shadowsocks/Models/SSD/SSDServer.cs similarity index 90% rename from Netch/Models/SSD/Server.cs rename to Netch/ServerEx/Shadowsocks/Models/SSD/SSDServer.cs index e7f4ac59..952b8146 100644 --- a/Netch/Models/SSD/Server.cs +++ b/Netch/ServerEx/Shadowsocks/Models/SSD/SSDServer.cs @@ -1,6 +1,6 @@ -namespace Netch.Models.SSD +namespace Netch.ServerEx.Shadowsocks.Models.SSD { - public class Server + public class SSDServer { /// /// 服务器地址 @@ -37,4 +37,4 @@ /// public string remarks; } -} +} \ No newline at end of file diff --git a/Netch/Models/SS/ShadowsocksServer.cs b/Netch/ServerEx/Shadowsocks/Models/ShadowsocksConfig.cs similarity index 80% rename from Netch/Models/SS/ShadowsocksServer.cs rename to Netch/ServerEx/Shadowsocks/Models/ShadowsocksConfig.cs index 00c36322..c8cf9978 100644 --- a/Netch/Models/SS/ShadowsocksServer.cs +++ b/Netch/ServerEx/Shadowsocks/Models/ShadowsocksConfig.cs @@ -1,6 +1,6 @@ -namespace Netch.Models.SS +namespace Netch.ServerEx.Shadowsocks.Models { - public class ShadowsocksServer + public class ShadowsocksConfig { public string server { get; set; } public int server_port { get; set; } diff --git a/Netch/ServerEx/Shadowsocks/SSController.cs b/Netch/ServerEx/Shadowsocks/SSController.cs new file mode 100644 index 00000000..47cea076 --- /dev/null +++ b/Netch/ServerEx/Shadowsocks/SSController.cs @@ -0,0 +1,95 @@ +using System.Runtime.InteropServices; +using System.Text; +using Netch.Models; +using Netch.Utils; + +namespace Netch.ServerEx.Shadowsocks +{ + public class SSController : ServerController + { + public SSController() + { + Name = "Shadowsocks"; + } + + public override bool Start(Server s, Mode mode) + { + bool DllFlag() + { + return Global.Settings.BootShadowsocksFromDLL && (mode.Type == 0 || mode.Type == 1 || mode.Type == 2); + } + + var server = (Shadowsocks) s; + //从DLL启动Shaowsocks + if (DllFlag()) + { + State = State.Starting; + var client = Encoding.UTF8.GetBytes($"{LocalAddress}:{Socks5LocalPort}"); + var remote = Encoding.UTF8.GetBytes($"{server.Hostname}:{server.Port}"); + var passwd = Encoding.UTF8.GetBytes($"{server.Password}"); + var method = Encoding.UTF8.GetBytes($"{server.EncryptMethod}"); + if (!ShadowsocksDLL.Info(client, remote, passwd, method)) + { + State = State.Stopped; + Logging.Error("DLL SS INFO 设置失败!"); + return false; + } + + Logging.Info("DLL SS INFO 设置成功!"); + + if (!ShadowsocksDLL.Start()) + { + State = State.Stopped; + Logging.Error("DLL SS 启动失败!"); + return false; + } + + Logging.Info("DLL SS 启动成功!"); + State = State.Started; + return true; + } + + #region Argument + + var argument = new StringBuilder(); + argument.Append( + $"-s {server.Hostname} " + + $"-p {server.Port} " + + $"-b {LocalAddress} " + + $"-l {Socks5LocalPort} " + + $"-m {server.EncryptMethod} " + + $"-k \"{server.Password}\" " + + "-u "); + if (!string.IsNullOrWhiteSpace(server.Plugin) && !string.IsNullOrWhiteSpace(server.PluginOption)) + argument.Append($"--plugin {server.Plugin} " + + $"--plugin-opts \"{server.PluginOption}\""); + if (mode.BypassChina) + argument.Append(" --acl default.acl"); + + #endregion + + return StartInstanceAuto(argument.ToString()); + } + + public override void Stop() + { + if (Instance == null) + ShadowsocksDLL.Stop(); + else + StopInstance(); + } + + + private class ShadowsocksDLL + { + [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] + public static extern bool Info(byte[] client, byte[] remote, byte[] passwd, byte[] method); + + [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] + public static extern bool Start(); + + [DllImport("shadowsocks-windows-dynamic", CallingConvention = CallingConvention.Cdecl)] + public static extern void Stop(); + } + } +} \ No newline at end of file diff --git a/Netch/ServerEx/Shadowsocks/SSUtil.cs b/Netch/ServerEx/Shadowsocks/SSUtil.cs new file mode 100644 index 00000000..baabe5ed --- /dev/null +++ b/Netch/ServerEx/Shadowsocks/SSUtil.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using Netch.Models; +using Netch.ServerEx.Shadowsocks.Form; +using Netch.ServerEx.Shadowsocks.Models.SSD; +using Netch.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Netch.ServerEx.Shadowsocks +{ + public class SSUtil : IServerUtil + { + public ushort Priority { get; } = 1; + public string TypeName { get; } = "SS"; + public string FullName { get; } = "Shadowsocks"; + + public string[] UriScheme { get; } = {"ss","ssd"}; + + public Server ParseJObject(JObject j) + { + return j.ToObject(); + } + + public void Edit(Server s) + { + new ShadowsocksForm((Shadowsocks) s).ShowDialog(); + } + + public void Create() + { + new ShadowsocksForm().ShowDialog(); + } + + public string GetShareLink(Server s) + { + var server = (Shadowsocks) s; + // ss://method:password@server:port#Remark + return "ss://" + ShareLink.URLSafeBase64Encode($"{server.EncryptMethod}:{server.Password}@{server.Hostname}:{server.Port}") + "#" + HttpUtility.UrlEncode(server.Remark); + } + + public ServerController GetController() + { + return new SSController(); + } + + public IEnumerable ParseUri(string text) + { + if (text.StartsWith("ss://")) + { + return new[] {ParseSsUri(text)}; + } + + if (text.StartsWith("ssd://")) + { + return ParseSsdUri(text); + } + + return null; + } + + public IEnumerable ParseSsdUri(string s) + { + var json = JsonConvert.DeserializeObject
(ShareLink.URLSafeBase64Decode(s.Substring(6))); + + return json.servers.Select(server => new Shadowsocks + { + Remark = server.remarks, + Hostname = server.server, + Port = server.port != 0 ? server.port : json.port, + Password = server.password ?? json.password, + EncryptMethod = server.encryption ?? json.encryption, + Plugin = string.IsNullOrEmpty(json.plugin) ? string.IsNullOrEmpty(server.plugin) ? null : server.plugin : json.plugin, + PluginOption = string.IsNullOrEmpty(json.plugin_options) ? string.IsNullOrEmpty(server.plugin_options) ? null : server.plugin_options : json.plugin_options + }) + .Where(CheckServer); + } + + public Shadowsocks ParseSsUri(string text) + { + var data = new Shadowsocks(); + + text = text.Replace("/?", "?"); + try + { + if (text.Contains("#")) + { + data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); + text = text.Split('#')[0]; + } + + if (text.Contains("?")) + { + var finder = new Regex(@"^(?.+?)\?(.+)$"); + var match = finder.Match(text); + + if (match.Success) + { + var plugins = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("plugin")); + if (plugins != null) + { + var plugin = plugins.Substring(0, plugins.IndexOf(";", StringComparison.Ordinal)); + var pluginopts = plugins.Substring(plugins.IndexOf(";", StringComparison.Ordinal) + 1); + switch (plugin) + { + case "obfs-local": + case "simple-obfs": + plugin = "simple-obfs"; + if (!pluginopts.Contains("obfs=")) + pluginopts = "obfs=http;obfs-host=" + pluginopts; + break; + case "simple-obfs-tls": + plugin = "simple-obfs"; + if (!pluginopts.Contains("obfs=")) + pluginopts = "obfs=tls;obfs-host=" + pluginopts; + break; + } + + data.Plugin = plugin; + data.PluginOption = pluginopts; + } + + text = match.Groups["data"].Value; + } + else + { + throw new FormatException(); + } + } + + if (text.Contains("@")) + { + var finder = new Regex(@"^ss://(?.+?)@(?.+):(?\d+)"); + var parser = new Regex(@"^(?.+?):(?.+)$"); + var match = finder.Match(text); + if (!match.Success) throw new FormatException(); + + data.Hostname = match.Groups["server"].Value; + data.Port = int.Parse(match.Groups["port"].Value); + + var base64 = ShareLink.URLSafeBase64Decode(match.Groups["base64"].Value); + match = parser.Match(base64); + if (!match.Success) throw new FormatException(); + + data.EncryptMethod = match.Groups["method"].Value; + data.Password = match.Groups["password"].Value; + } + else + { + var parser = new Regex(@"^((?.+?):(?.+)@(?.+):(?\d+))"); + var match = parser.Match(ShareLink.URLSafeBase64Decode(text.Replace("ss://", ""))); + if (!match.Success) throw new FormatException(); + + data.Hostname = match.Groups["server"].Value; + data.Port = int.Parse(match.Groups["port"].Value); + data.EncryptMethod = match.Groups["method"].Value; + data.Password = match.Groups["password"].Value; + } + + return CheckServer(data) ? data : null; + } + catch (FormatException) + { + return null; + } + } + + public bool CheckServer(Server s) + { + var server = (Shadowsocks) s; + if (!SSGlobal.EncryptMethods.Contains(server.EncryptMethod)) + { + Logging.Error($"不支持的 SS 加密方式:{server.EncryptMethod}"); + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Netch/ServerEx/Shadowsocks/Shadowsocks.cs b/Netch/ServerEx/Shadowsocks/Shadowsocks.cs new file mode 100644 index 00000000..cf105c89 --- /dev/null +++ b/Netch/ServerEx/Shadowsocks/Shadowsocks.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using Netch.Models; + +namespace Netch.ServerEx.Shadowsocks +{ + public class Shadowsocks : Server + { + /// + /// 加密方式 + /// + public string EncryptMethod { get; set; } = SSGlobal.EncryptMethods[0]; + + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 插件 + /// + public string Plugin { get; set; } + + /// + /// 插件参数 + /// + public string PluginOption { get; set; } + + public Shadowsocks() + { + Type = "SS"; + } + } + + public static class SSGlobal + { + /// + /// SS 加密列表 + /// + public static readonly List EncryptMethods = new List + { + "rc4-md5", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "bf-cfb", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + "salsa20", + "chacha20", + "chacha20-ietf" + }; + } +} \ No newline at end of file diff --git a/Netch/Forms/Server/ShadowsocksR.Designer.cs b/Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.Designer.cs similarity index 99% rename from Netch/Forms/Server/ShadowsocksR.Designer.cs rename to Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.Designer.cs index d90ff9f2..db69bade 100644 --- a/Netch/Forms/Server/ShadowsocksR.Designer.cs +++ b/Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.Designer.cs @@ -1,6 +1,6 @@ -namespace Netch.Forms.Server +namespace Netch.ServerEx.ShadowsocksR.Form { - partial class ShadowsocksR + partial class ShadowsocksRForm { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ShadowsocksR)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ShadowsocksRForm)); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.OBFSParamLabel = new System.Windows.Forms.Label(); this.OBFSOptionParamTextBox = new System.Windows.Forms.TextBox(); @@ -263,7 +263,7 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.MaximizeBox = false; - this.Name = "ShadowsocksR"; + this.Name = "ShadowsocksRForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "ShadowsocksR"; this.Load += new System.EventHandler(this.ShadowsocksR_Load); diff --git a/Netch/Forms/Server/ShadowsocksR.cs b/Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.cs similarity index 56% rename from Netch/Forms/Server/ShadowsocksR.cs rename to Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.cs index 4b6ce9df..bcf623a0 100644 --- a/Netch/Forms/Server/ShadowsocksR.cs +++ b/Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.cs @@ -1,19 +1,18 @@ using System; -using System.Drawing; using System.Windows.Forms; using Netch.Utils; -namespace Netch.Forms.Server +namespace Netch.ServerEx.ShadowsocksR.Form { - public partial class ShadowsocksR : Form + public partial class ShadowsocksRForm : System.Windows.Forms.Form { - private readonly Models.Server _server; + private readonly ShadowsocksR _server; - public ShadowsocksR(Models.Server server = default) + public ShadowsocksRForm(Models.Server server = default) { InitializeComponent(); - _server = server ?? new Models.Server {EncryptMethod = Global.EncryptMethods.SSR[0]}; + _server = (ShadowsocksR) (server ?? new ShadowsocksR()); } private void ShadowsocksR_Load(object sender, EventArgs e) @@ -31,10 +30,10 @@ namespace Netch.Forms.Server OBFSParamLabel.Text = i18N.Translate(OBFSParamLabel.Text); ControlButton.Text = i18N.Translate(ControlButton.Text); - EncryptMethodComboBox.Items.AddRange(Global.EncryptMethods.SSR.ToArray()); + EncryptMethodComboBox.Items.AddRange(SSRGlobal.EncryptMethods.ToArray()); - ProtocolComboBox.Items.AddRange(Global.Protocols.ToArray()); - OBFSComboBox.Items.AddRange(Global.OBFSs.ToArray()); + ProtocolComboBox.Items.AddRange(SSRGlobal.Protocols.ToArray()); + OBFSComboBox.Items.AddRange(SSRGlobal.OBFSs.ToArray()); #endregion @@ -42,45 +41,21 @@ namespace Netch.Forms.Server AddressTextBox.Text = _server.Hostname; PortTextBox.Text = _server.Port.ToString(); PasswordTextBox.Text = _server.Password; - EncryptMethodComboBox.SelectedIndex = Global.EncryptMethods.SSR.IndexOf(_server.EncryptMethod); - ProtocolComboBox.SelectedIndex = Global.Protocols.IndexOf(_server.Protocol); + EncryptMethodComboBox.SelectedIndex = SSRGlobal.EncryptMethods.IndexOf(_server.EncryptMethod); + ProtocolComboBox.SelectedIndex = SSRGlobal.Protocols.IndexOf(_server.Protocol); ProtocolParamTextBox.Text = _server.ProtocolParam; - OBFSComboBox.SelectedIndex = Global.OBFSs.IndexOf(_server.OBFS); + OBFSComboBox.SelectedIndex = SSRGlobal.OBFSs.IndexOf(_server.OBFS); OBFSOptionParamTextBox.Text = _server.OBFSParam; } private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) { - if (sender is ComboBox cbx) - { - e.DrawBackground(); - - if (e.Index >= 0) - { - var sf = new StringFormat - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Center - }; - - var brush = new SolidBrush(cbx.ForeColor); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - brush = SystemBrushes.HighlightText as SolidBrush; - } - - e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, sf); - } - } + Utils.Utils.DrawCenterComboBox(sender, e); } private void ControlButton_Click(object sender, EventArgs e) { - if (!int.TryParse(PortTextBox.Text, out var port)) - { - return; - } + if (!ushort.TryParse(PortTextBox.Text, out var port)) return; _server.Remark = RemarkTextBox.Text; _server.Type = "SSR"; @@ -95,9 +70,7 @@ namespace Netch.Forms.Server _server.Country = null; if (Global.Settings.Server.IndexOf(_server) == -1) - { Global.Settings.Server.Add(_server); - } MessageBoxX.Show(i18N.Translate("Saved")); Close(); diff --git a/Netch/Forms/Server/ShadowsocksR.resx b/Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.resx similarity index 100% rename from Netch/Forms/Server/ShadowsocksR.resx rename to Netch/ServerEx/ShadowsocksR/Form/ShadowsocksRForm.resx diff --git a/Netch/Controllers/EncryptedProxy/SSRController.cs b/Netch/ServerEx/ShadowsocksR/SSRController.cs similarity index 80% rename from Netch/Controllers/EncryptedProxy/SSRController.cs rename to Netch/ServerEx/ShadowsocksR/SSRController.cs index 0fb018ec..ce111a0c 100644 --- a/Netch/Controllers/EncryptedProxy/SSRController.cs +++ b/Netch/ServerEx/ShadowsocksR/SSRController.cs @@ -1,20 +1,21 @@ -using System.Text; +using System.Text; using Netch.Models; -namespace Netch.Controllers +namespace Netch.ServerEx.ShadowsocksR { - public class SSRController : EncryptedProxy + public class SSRController : ServerController { public SSRController() { Name = "ShadowsocksR"; MainFile = "ShadowsocksR.exe"; - StartedKeywords.Add("listening at"); - StoppedKeywords.AddRange(new[] {"Invalid config path", "usage"}); } - public override bool Start(Server server, Mode mode) + + public override bool Start(Server s, Mode mode) { + var server = (ShadowsocksR) s; + #region Argument var argument = new StringBuilder(); diff --git a/Netch/ServerEx/ShadowsocksR/SSRUtil.cs b/Netch/ServerEx/ShadowsocksR/SSRUtil.cs new file mode 100644 index 00000000..9ca60d18 --- /dev/null +++ b/Netch/ServerEx/ShadowsocksR/SSRUtil.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Netch.Models; +using Netch.ServerEx.ShadowsocksR.Form; +using Netch.Utils; +using Newtonsoft.Json.Linq; + +namespace Netch.ServerEx.ShadowsocksR +{ + public class SSRUtil : IServerUtil + { + public ushort Priority { get; } = 1; + public string TypeName { get; } = "SSR"; + public string FullName { get; } = "ShadowsocksR"; + + public string[] UriScheme { get; } = {"ssr"}; + + public Server ParseJObject(JObject j) + { + return j.ToObject(); + } + + + public void Edit(Server s) + { + new ShadowsocksRForm(s).ShowDialog(); + } + + public void Create() + { + new ShadowsocksRForm().ShowDialog(); + } + + public string GetShareLink(Server s) + { + var server = (ShadowsocksR) 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) + var paraStr = $"/?obfsparam={ShareLink.URLSafeBase64Encode(server.OBFSParam)}&protoparam={ShareLink.URLSafeBase64Encode(server.ProtocolParam)}&remarks={ShareLink.URLSafeBase64Encode(server.Remark)}"; + return "ssr://" + ShareLink.URLSafeBase64Encode($"{server.Hostname}:{server.Port}:{server.Protocol}:{server.EncryptMethod}:{server.OBFS}:{ShareLink.URLSafeBase64Encode(server.Password)}{paraStr}"); + } + + public ServerController GetController() + { + return new SSRController(); + } + + /// + /// SSR链接解析器 + /// Copy From https://github.com/HMBSbige/ShadowsocksR-Windows/blob/d9dc8d032a6e04c14b9dc6c8f673c9cc5aa9f607/shadowsocks-csharp/Model/Server.cs#L428 + /// Thx :D + /// + /// + /// + public IEnumerable ParseUri(string text) + { + // ssr://host:port:protocol:method:obfs:base64pass/?obfsparam=base64&remarks=base64&group=base64&udpport=0&uot=1 + var ssr = Regex.Match(text, "ssr://([A-Za-z0-9_-]+)", RegexOptions.IgnoreCase); + if (!ssr.Success) + throw new FormatException(); + + var data = ShareLink.URLSafeBase64Decode(ssr.Groups[1].Value); + var paramsDict = new Dictionary(); + + var paramStartPos = data.IndexOf("?", StringComparison.Ordinal); + if (paramStartPos > 0) + { + paramsDict = ShareLink.ParseParam(data.Substring(paramStartPos + 1)); + data = data.Substring(0, paramStartPos); + } + + if (data.IndexOf("/", StringComparison.Ordinal) >= 0) data = data.Substring(0, data.LastIndexOf("/", StringComparison.Ordinal)); + + var urlFinder = new Regex("^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)"); + var match = urlFinder.Match(data); + + if (match == null || !match.Success) + throw new FormatException(); + + var serverAddr = match.Groups[1].Value; + var serverPort = ushort.Parse(match.Groups[2].Value); + var protocol = match.Groups[3].Value.Length == 0 ? "origin" : match.Groups[3].Value; + protocol = protocol.Replace("_compatible", ""); + var method = match.Groups[4].Value; + var obfs = match.Groups[5].Value.Length == 0 ? "plain" : match.Groups[5].Value; + obfs = obfs.Replace("_compatible", ""); + var password = ShareLink.URLSafeBase64Decode(match.Groups[6].Value); + var protocolParam = ""; + var obfsParam = ""; + var remarks = ""; + + if (paramsDict.ContainsKey("protoparam")) protocolParam = ShareLink.URLSafeBase64Decode(paramsDict["protoparam"]); + + if (paramsDict.ContainsKey("obfsparam")) obfsParam = ShareLink.URLSafeBase64Decode(paramsDict["obfsparam"]); + + if (paramsDict.ContainsKey("remarks")) remarks = ShareLink.URLSafeBase64Decode(paramsDict["remarks"]); + + var group = paramsDict.ContainsKey("group") ? ShareLink.URLSafeBase64Decode(paramsDict["group"]) : string.Empty; + + return new[] + { + new ShadowsocksR + { + Hostname = serverAddr, + Port = serverPort, + Protocol = protocol, + EncryptMethod = method, + OBFS = obfs, + Password = password, + ProtocolParam = protocolParam, + OBFSParam = obfsParam, + Remark = remarks, + Group = group + } + }; + } + + public bool CheckServer(Server s) + { + var server = (ShadowsocksR) s; + if (!SSRGlobal.EncryptMethods.Contains(server.EncryptMethod)) + { + Logging.Error($"不支持的 SSR 加密方式:{server.EncryptMethod}"); + return false; + } + + if (!SSRGlobal.Protocols.Contains(server.Protocol)) + { + Logging.Error($"不支持的 SSR 协议:{server.Protocol}"); + return false; + } + + if (!SSRGlobal.OBFSs.Contains(server.OBFS)) + { + Logging.Error($"不支持的 SSR 混淆:{server.OBFS}"); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Netch/ServerEx/ShadowsocksR/ShadowsocksR.cs b/Netch/ServerEx/ShadowsocksR/ShadowsocksR.cs new file mode 100644 index 00000000..a807cd14 --- /dev/null +++ b/Netch/ServerEx/ShadowsocksR/ShadowsocksR.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using Netch.Models; + +namespace Netch.ServerEx.ShadowsocksR +{ + public class ShadowsocksR : Server + { + /// + /// 加密方式 + /// + public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0]; + + /// + /// 混淆 + /// + public string OBFS { get; set; } = SSRGlobal.OBFSs[0]; + + /// + /// 混淆参数 + /// + public string OBFSParam { get; set; } + + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 协议 + /// + public string Protocol { get; set; } = SSRGlobal.Protocols[0]; + + /// + /// 协议参数 + /// + public string ProtocolParam { get; set; } + + public ShadowsocksR() + { + Type = "SSR"; + } + } + + public class SSRGlobal + { + /// + /// SSR 协议列表 + /// + public static readonly List Protocols = new List + { + "origin", + "verify_deflate", + "auth_sha1_v4", + "auth_aes128_md5", + "auth_aes128_sha1", + "auth_chain_a" + }; + + /// + /// SSR 混淆列表 + /// + public static readonly List OBFSs = new List + { + "plain", + "http_simple", + "http_post", + "tls1.2_ticket_auth" + }; + + /// + /// SS/SSR 加密方式 + /// + public static readonly List EncryptMethods = new List + { + "none", + "table", + "rc4", + "rc4-md5", + "rc4-md5-6", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "bf-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "cast5-cfb", + "des-cfb", + "idea-cfb", + "rc2-cfb", + "seed-cfb", + "salsa20", + "chacha20", + "chacha20-ietf" + }; + } +} \ No newline at end of file diff --git a/Netch/Forms/Server/Socks5.Designer.cs b/Netch/ServerEx/Socks5/Form/Socks5Form.Designer.cs similarity index 98% rename from Netch/Forms/Server/Socks5.Designer.cs rename to Netch/ServerEx/Socks5/Form/Socks5Form.Designer.cs index c6f1006f..6e2404fd 100644 --- a/Netch/Forms/Server/Socks5.Designer.cs +++ b/Netch/ServerEx/Socks5/Form/Socks5Form.Designer.cs @@ -1,6 +1,6 @@ -namespace Netch.Forms.Server +namespace Netch.ServerEx.Socks5.Form { - partial class Socks5 + partial class Socks5Form { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Socks5)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Socks5Form)); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.PasswordLabel = new System.Windows.Forms.Label(); this.UsernameTextBox = new System.Windows.Forms.TextBox(); @@ -170,7 +170,7 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.MaximizeBox = false; - this.Name = "Socks5"; + this.Name = "Socks5Form"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Socks5"; this.Load += new System.EventHandler(this.Shadowsocks_Load); diff --git a/Netch/Forms/Server/Socks5.cs b/Netch/ServerEx/Socks5/Form/Socks5Form.cs similarity index 54% rename from Netch/Forms/Server/Socks5.cs rename to Netch/ServerEx/Socks5/Form/Socks5Form.cs index c0db97e8..35c8d9c1 100644 --- a/Netch/Forms/Server/Socks5.cs +++ b/Netch/ServerEx/Socks5/Form/Socks5Form.cs @@ -1,19 +1,17 @@ using System; -using System.Drawing; -using System.Windows.Forms; using Netch.Utils; -namespace Netch.Forms.Server +namespace Netch.ServerEx.Socks5.Form { - public partial class Socks5 : Form + public partial class Socks5Form : System.Windows.Forms.Form { - private readonly Models.Server _server; + private readonly Socks5 _server; - public Socks5(Models.Server server = default) + public Socks5Form(Models.Server server = default) { InitializeComponent(); - _server = server ?? new Models.Server(); + _server = (Socks5) (server ?? new Socks5()); } private void Shadowsocks_Load(object sender, EventArgs e) @@ -36,38 +34,9 @@ namespace Netch.Forms.Server PasswordTextBox.Text = _server.Password; } - private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) - { - if (sender is ComboBox cbx) - { - e.DrawBackground(); - - if (e.Index >= 0) - { - var sf = new StringFormat - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Center - }; - - var brush = new SolidBrush(cbx.ForeColor); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - brush = SystemBrushes.HighlightText as SolidBrush; - } - - e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, sf); - } - } - } - private void ControlButton_Click(object sender, EventArgs e) { - if (!int.TryParse(PortTextBox.Text, out var port)) - { - return; - } + if (!ushort.TryParse(PortTextBox.Text, out var port)) return; _server.Remark = RemarkTextBox.Text; _server.Type = "Socks5"; @@ -78,9 +47,7 @@ namespace Netch.Forms.Server _server.Country = null; if (Global.Settings.Server.IndexOf(_server) == -1) - { Global.Settings.Server.Add(_server); - } MessageBoxX.Show(i18N.Translate("Saved")); Close(); diff --git a/Netch/Forms/Server/Socks5.resx b/Netch/ServerEx/Socks5/Form/Socks5Form.resx similarity index 100% rename from Netch/Forms/Server/Socks5.resx rename to Netch/ServerEx/Socks5/Form/Socks5Form.resx diff --git a/Netch/ServerEx/Socks5/S5Util.cs b/Netch/ServerEx/Socks5/S5Util.cs new file mode 100644 index 00000000..d5fc42af --- /dev/null +++ b/Netch/ServerEx/Socks5/S5Util.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using Netch.Models; +using Netch.ServerEx.Socks5.Form; +using Newtonsoft.Json.Linq; + +namespace Netch.ServerEx.Socks5 +{ + public class S5Util : IServerUtil + { + public ushort Priority { get; } = 0; + public string TypeName { get; } = "Socks5"; + public string FullName { get; } = "Socks5"; + + public string[] UriScheme { get; } = { }; + + public Server ParseJObject(JObject j) + { + return j.ToObject(); + } + + public void Edit(Server s) + { + new Socks5Form(s).ShowDialog(); + } + + public void Create() + { + new Socks5Form().ShowDialog(); + } + + public string GetShareLink(Server server) + { + // https://t.me/socks?server=1.1.1.1&port=443 + return $"https://t.me/socks?server={server.Hostname}&port={server.Port}"; + } + + public ServerController GetController() + { + return null; + } + + public IEnumerable ParseUri(string text) + { + var dict = text + .Replace("tg://socks?", "") + .Replace("https://t.me/socks?", "") + .Split('&') + .Select(str => str.Split('=')) + .ToDictionary(splited => splited[0], splited => splited[1]); + + if (!dict.ContainsKey("server") || !dict.ContainsKey("port")) + { + return null; + } + + var data = new Socks5 + { + Hostname = dict["server"], + Port = int.Parse(dict["port"]) + }; + + if (dict.ContainsKey("user") && !string.IsNullOrWhiteSpace(dict["user"])) + { + data.Username = dict["user"]; + } + + if (dict.ContainsKey("pass") && !string.IsNullOrWhiteSpace(dict["pass"])) + { + data.Password = dict["pass"]; + } + + return new[] {data}; + } + + public bool CheckServer(Server s) + { + return true; + } + } +} \ No newline at end of file diff --git a/Netch/ServerEx/Socks5/Socks5.cs b/Netch/ServerEx/Socks5/Socks5.cs new file mode 100644 index 00000000..d500e76b --- /dev/null +++ b/Netch/ServerEx/Socks5/Socks5.cs @@ -0,0 +1,22 @@ +using Netch.Models; + +namespace Netch.ServerEx.Socks5 +{ + public class Socks5 : Server + { + /// + /// 密码 + /// + public string Password; + + /// + /// 账号 + /// + public string Username; + + public Socks5() + { + Type = "Socks5"; + } + } +} \ No newline at end of file diff --git a/Netch/Forms/Server/Trojan.Designer.cs b/Netch/ServerEx/Trojan/Form/TrojanForm.Designer.cs similarity index 97% rename from Netch/Forms/Server/Trojan.Designer.cs rename to Netch/ServerEx/Trojan/Form/TrojanForm.Designer.cs index 317524c4..e85c25fa 100644 --- a/Netch/Forms/Server/Trojan.Designer.cs +++ b/Netch/ServerEx/Trojan/Form/TrojanForm.Designer.cs @@ -1,6 +1,6 @@ -namespace Netch.Forms.Server +namespace Netch.ServerEx.Trojan.Form { - partial class Trojan + partial class TrojanForm { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Trojan)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TrojanForm)); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.PasswordTextBox = new System.Windows.Forms.TextBox(); this.PasswordLabel = new System.Windows.Forms.Label(); @@ -149,10 +149,10 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.MaximizeBox = false; - this.Name = "Trojan"; + this.Name = "TrojanForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Trojan"; - this.Load += new System.EventHandler(this.Trojan_Load); + this.Load += new System.EventHandler(this.TrojanForm_Load); this.ConfigurationGroupBox.ResumeLayout(false); this.ConfigurationGroupBox.PerformLayout(); this.ResumeLayout(false); diff --git a/Netch/Forms/Server/Trojan.cs b/Netch/ServerEx/Trojan/Form/TrojanForm.cs similarity index 52% rename from Netch/Forms/Server/Trojan.cs rename to Netch/ServerEx/Trojan/Form/TrojanForm.cs index df0ea1bc..f3d2489c 100644 --- a/Netch/Forms/Server/Trojan.cs +++ b/Netch/ServerEx/Trojan/Form/TrojanForm.cs @@ -1,22 +1,22 @@ using System; -using System.Drawing; using System.Windows.Forms; +using Netch.Models; using Netch.Utils; -namespace Netch.Forms.Server +namespace Netch.ServerEx.Trojan.Form { - public partial class Trojan : Form + public partial class TrojanForm : System.Windows.Forms.Form { - private readonly Models.Server _server; + private readonly Trojan _server; - public Trojan(Models.Server server = default) + public TrojanForm(Server server = default) { InitializeComponent(); - _server = server ?? new Models.Server(); + _server = (Trojan) (server ?? new Trojan()); } - private void Trojan_Load(object sender, EventArgs e) + private void TrojanForm_Load(object sender, EventArgs e) { ConfigurationGroupBox.Text = i18N.Translate(ConfigurationGroupBox.Text); RemarkLabel.Text = i18N.Translate(RemarkLabel.Text); @@ -32,37 +32,12 @@ namespace Netch.Forms.Server private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) { - if (sender is ComboBox cbx) - { - e.DrawBackground(); - - if (e.Index >= 0) - { - var sf = new StringFormat - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Center - }; - - var brush = new SolidBrush(cbx.ForeColor); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - brush = SystemBrushes.HighlightText as SolidBrush; - } - - e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, sf); - } - } + Utils.Utils.DrawCenterComboBox(sender, e); } private void ControlButton_Click(object sender, EventArgs e) { - if (!int.TryParse(PortTextBox.Text, out var port)) - { - return; - } - + if (!ushort.TryParse(PortTextBox.Text, out var port)) return; _server.Remark = RemarkTextBox.Text; _server.Type = "Trojan"; @@ -72,9 +47,7 @@ namespace Netch.Forms.Server _server.Country = null; if (Global.Settings.Server.IndexOf(_server) == -1) - { Global.Settings.Server.Add(_server); - } MessageBoxX.Show(i18N.Translate("Saved")); Close(); diff --git a/Netch/Forms/Server/Trojan.resx b/Netch/ServerEx/Trojan/Form/TrojanForm.resx similarity index 100% rename from Netch/Forms/Server/Trojan.resx rename to Netch/ServerEx/Trojan/Form/TrojanForm.resx diff --git a/Netch/Models/Trojan.cs b/Netch/ServerEx/Trojan/Models/TrojanConfig.cs similarity index 96% rename from Netch/Models/Trojan.cs rename to Netch/ServerEx/Trojan/Models/TrojanConfig.cs index 3301a16e..57ff417c 100644 --- a/Netch/Models/Trojan.cs +++ b/Netch/ServerEx/Trojan/Models/TrojanConfig.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -namespace Netch.Models +namespace Netch.ServerEx.Trojan.Models { - public class Trojan + public class TrojanConfig { /// /// 启动类型 @@ -51,11 +51,13 @@ namespace Netch.Models public string cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"; public string cipher_tls13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"; public string sni = string.Empty; + public List alpn = new List { "h2", "http/1.1" }; + public bool reuse_session = true; public bool session_ticket = true; public string curves = ""; @@ -69,4 +71,4 @@ namespace Netch.Models public bool fast_open = true; public int fast_open_qlen = 20; } -} +} \ No newline at end of file diff --git a/Netch/ServerEx/Trojan/Trojan.cs b/Netch/ServerEx/Trojan/Trojan.cs new file mode 100644 index 00000000..17474ccc --- /dev/null +++ b/Netch/ServerEx/Trojan/Trojan.cs @@ -0,0 +1,22 @@ +using Netch.Models; + +namespace Netch.ServerEx.Trojan +{ + public class Trojan : Server + { + public Trojan() + { + Type = "Trojan"; + } + + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 伪装域名 + /// + public string Host { get; set; } + } +} \ No newline at end of file diff --git a/Netch/Controllers/EncryptedProxy/TrojanController.cs b/Netch/ServerEx/Trojan/TrojanController.cs similarity index 78% rename from Netch/Controllers/EncryptedProxy/TrojanController.cs rename to Netch/ServerEx/Trojan/TrojanController.cs index e9bdc229..e1c77fbe 100644 --- a/Netch/Controllers/EncryptedProxy/TrojanController.cs +++ b/Netch/ServerEx/Trojan/TrojanController.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.IO; using Netch.Models; +using Netch.ServerEx.Trojan.Models; using Newtonsoft.Json; -namespace Netch.Controllers +namespace Netch.ServerEx.Trojan { - public class TrojanController : EncryptedProxy + public class TrojanController : ServerController { public TrojanController() { @@ -15,9 +16,10 @@ namespace Netch.Controllers StoppedKeywords.Add("exiting"); } - public override bool Start(Server server, Mode mode) + public override bool Start(Server s, Mode mode) { - File.WriteAllText("data\\last.json", JsonConvert.SerializeObject(new Trojan + var server = (Trojan) s; + File.WriteAllText("data\\last.json", JsonConvert.SerializeObject(new TrojanConfig { local_addr = LocalAddress, local_port = Socks5LocalPort, diff --git a/Netch/ServerEx/Trojan/TrojanUtil.cs b/Netch/ServerEx/Trojan/TrojanUtil.cs new file mode 100644 index 00000000..ccc3189e --- /dev/null +++ b/Netch/ServerEx/Trojan/TrojanUtil.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Web; +using Netch.Models; +using Netch.ServerEx.Trojan.Form; +using Newtonsoft.Json.Linq; + +namespace Netch.ServerEx.Trojan +{ + public class TrojanUtil : IServerUtil + { + public ushort Priority { get; } = 2; + public string TypeName { get; } = "Trojan"; + public string FullName { get; } = "Trojan"; + + public string[] UriScheme { get; } = {"trojan"}; + + public Server ParseJObject(JObject j) + { + return j.ToObject(); + } + + public void Edit(Server s) + { + new TrojanForm(s).ShowDialog(); + } + + public void Create() + { + new TrojanForm().ShowDialog(); + } + + public string GetShareLink(Server server) + { + // TODO + return ""; + } + + public ServerController GetController() + { + return new TrojanController(); + } + + public IEnumerable ParseUri(string text) + { + var data = new Trojan(); + + text = text.Replace("/?", "?"); + try + { + if (text.Contains("#")) + { + data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); + text = text.Split('#')[0]; + } + + if (text.Contains("?")) + { + var reg = new Regex(@"^(?.+?)\?(.+)$"); + var regmatch = reg.Match(text); + + if (regmatch.Success) + { + var peer = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("peer")); + + if (peer != null) + data.Host = peer; + + text = regmatch.Groups["data"].Value; + } + else + { + throw new FormatException(); + } + } + + var finder = new Regex(@"^trojan://(?.+?)@(?.+):(?\d+)"); + var match = finder.Match(text); + if (!match.Success) + { + throw new FormatException(); + } + + data.Password = match.Groups["psk"].Value; + data.Hostname = match.Groups["server"].Value; + data.Port = int.Parse(match.Groups["port"].Value); + + return new[] {data}; + } + catch (FormatException) + { + return null; + } + } + + public bool CheckServer(Server s) + { + return true; + } + } +} \ No newline at end of file diff --git a/Netch/Forms/Server/Vmess.Designer.cs b/Netch/ServerEx/VMess/Form/VMessForm.Designer.cs similarity index 99% rename from Netch/Forms/Server/Vmess.Designer.cs rename to Netch/ServerEx/VMess/Form/VMessForm.Designer.cs index 5bcb0ec0..a4ba81ec 100644 --- a/Netch/Forms/Server/Vmess.Designer.cs +++ b/Netch/ServerEx/VMess/Form/VMessForm.Designer.cs @@ -1,6 +1,6 @@ -namespace Netch.Forms.Server +namespace Netch.ServerEx.VMess.Form { - partial class VMess + partial class VMessForm { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(VMess)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(VMessForm)); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.UseMuxCheckBox = new System.Windows.Forms.CheckBox(); this.TLSSecureCheckBox = new System.Windows.Forms.CheckBox(); @@ -382,7 +382,7 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6); this.MaximizeBox = false; - this.Name = "VMess"; + this.Name = "VMessForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "VMess"; this.Load += new System.EventHandler(this.VMess_Load); diff --git a/Netch/Forms/Server/Vmess.cs b/Netch/ServerEx/VMess/Form/VMessForm.cs similarity index 60% rename from Netch/Forms/Server/Vmess.cs rename to Netch/ServerEx/VMess/Form/VMessForm.cs index a26c50e2..a28ed9ef 100644 --- a/Netch/Forms/Server/Vmess.cs +++ b/Netch/ServerEx/VMess/Form/VMessForm.cs @@ -1,45 +1,24 @@ using System; -using System.Drawing; using System.Windows.Forms; +using Netch.Models; using Netch.Utils; -namespace Netch.Forms.Server +namespace Netch.ServerEx.VMess.Form { - public partial class VMess : Form + public partial class VMessForm : System.Windows.Forms.Form { - private static Models.Server _server; + private static VMess _server; - public VMess(Models.Server server = default) + public VMessForm(Server server = default) { InitializeComponent(); - _server = server ?? new Models.Server {EncryptMethod = Global.EncryptMethods.VMess[0]}; + _server = (VMess) server ?? new VMess(); } private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) { - if (sender is ComboBox cbx) - { - e.DrawBackground(); - - if (e.Index >= 0) - { - var sf = new StringFormat - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Center - }; - - var brush = new SolidBrush(cbx.ForeColor); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - brush = SystemBrushes.HighlightText as SolidBrush; - } - - e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, sf); - } - } + Utils.Utils.DrawCenterComboBox(sender, e); } private void VMess_Load(object sender, EventArgs e) @@ -62,10 +41,10 @@ namespace Netch.Forms.Server UseMuxCheckBox.Text = i18N.Translate(UseMuxCheckBox.Text); ControlButton.Text = i18N.Translate(ControlButton.Text); - EncryptMethodComboBox.Items.AddRange(Global.EncryptMethods.VMess.ToArray()); - TransferProtocolComboBox.Items.AddRange(Global.TransferProtocols.ToArray()); - FakeTypeComboBox.Items.AddRange(Global.FakeTypes.ToArray()); - QUICSecurityComboBox.Items.AddRange(Global.EncryptMethods.VMessQUIC.ToArray()); + EncryptMethodComboBox.Items.AddRange(VMessGlobal.EncryptMethods.ToArray()); + TransferProtocolComboBox.Items.AddRange(VMessGlobal.TransferProtocols.ToArray()); + FakeTypeComboBox.Items.AddRange(VMessGlobal.FakeTypes.ToArray()); + QUICSecurityComboBox.Items.AddRange(VMessGlobal.QUIC.ToArray()); #endregion @@ -74,12 +53,12 @@ namespace Netch.Forms.Server PortTextBox.Text = _server.Port.ToString(); UserIDTextBox.Text = _server.UserID; AlterIDTextBox.Text = _server.AlterID.ToString(); - EncryptMethodComboBox.SelectedIndex = Global.EncryptMethods.VMess.IndexOf(_server.EncryptMethod); - TransferProtocolComboBox.SelectedIndex = Global.TransferProtocols.IndexOf(_server.TransferProtocol); - FakeTypeComboBox.SelectedIndex = Global.FakeTypes.IndexOf(_server.FakeType); + EncryptMethodComboBox.SelectedIndex = VMessGlobal.EncryptMethods.IndexOf(_server.EncryptMethod); + TransferProtocolComboBox.SelectedIndex = VMessGlobal.TransferProtocols.IndexOf(_server.TransferProtocol); + FakeTypeComboBox.SelectedIndex = VMessGlobal.FakeTypes.IndexOf(_server.FakeType); HostTextBox.Text = _server.Host; PathTextBox.Text = _server.Path; - QUICSecurityComboBox.SelectedIndex = Global.EncryptMethods.VMessQUIC.IndexOf(_server.QUICSecure); + QUICSecurityComboBox.SelectedIndex = VMessGlobal.QUIC.IndexOf(_server.QUICSecure); QUICSecretTextBox.Text = _server.QUICSecret; TLSSecureCheckBox.Checked = _server.TLSSecure; UseMuxCheckBox.Checked = _server.UseMux; @@ -87,15 +66,9 @@ namespace Netch.Forms.Server private void ControlButton_Click(object sender, EventArgs e) { - if (!int.TryParse(PortTextBox.Text, out var port)) - { - return; - } + if (!ushort.TryParse(PortTextBox.Text, out var port)) return; - if (!int.TryParse(AlterIDTextBox.Text, out var alterId)) - { - return; - } + if (!int.TryParse(AlterIDTextBox.Text, out var alterId)) return; _server.Remark = RemarkTextBox.Text; _server.Type = "VMess"; @@ -115,9 +88,7 @@ namespace Netch.Forms.Server _server.Country = null; if (Global.Settings.Server.IndexOf(_server) == -1) - { Global.Settings.Server.Add(_server); - } MessageBoxX.Show(i18N.Translate("Saved")); Close(); diff --git a/Netch/Forms/Server/Vmess.resx b/Netch/ServerEx/VMess/Form/VMessForm.resx similarity index 100% rename from Netch/Forms/Server/Vmess.resx rename to Netch/ServerEx/VMess/Form/VMessForm.resx diff --git a/Netch/Models/Information/VMess.cs b/Netch/ServerEx/VMess/Models/VMessConfig.cs similarity index 98% rename from Netch/Models/Information/VMess.cs rename to Netch/ServerEx/VMess/Models/VMessConfig.cs index 32133e67..c08b6a93 100644 --- a/Netch/Models/Information/VMess.cs +++ b/Netch/ServerEx/VMess/Models/VMessConfig.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -namespace Netch.Models.Information +namespace Netch.ServerEx.VMess.Models { - public class VMess + public class VMessConfig { public class InboundSettings { @@ -203,4 +203,4 @@ namespace Netch.Models.Information public Routing routing; } } -} +} \ No newline at end of file diff --git a/Netch/Models/VMess.cs b/Netch/ServerEx/VMess/Models/VMessJObject.cs similarity index 95% rename from Netch/Models/VMess.cs rename to Netch/ServerEx/VMess/Models/VMessJObject.cs index 22c9550f..261a81a8 100644 --- a/Netch/Models/VMess.cs +++ b/Netch/ServerEx/VMess/Models/VMessJObject.cs @@ -1,9 +1,9 @@ -namespace Netch.Models +namespace Netch.ServerEx.VMess.Models { /// /// 使用 v2rayN 定义的 VMess 链接格式 /// - public class VMess + public class VMessJObject { /// /// Mux Class @@ -73,4 +73,4 @@ /// public Mux mux; } -} +} \ No newline at end of file diff --git a/Netch/ServerEx/VMess/VMess.cs b/Netch/ServerEx/VMess/VMess.cs new file mode 100644 index 00000000..a1bdf7f0 --- /dev/null +++ b/Netch/ServerEx/VMess/VMess.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using Netch.Models; + +namespace Netch.ServerEx.VMess +{ + public class VMess : Server + { + public VMess() + { + Type = "VMess"; + } + + /// + /// 用户 ID + /// + public string UserID { get; set; } + + /// + /// 额外 ID + /// + public int AlterID { get; set; } + + /// + /// 加密方式 + /// + public string EncryptMethod { get; set; } = VMessGlobal.EncryptMethods[0]; + + + /// + /// 传输协议 + /// + public string TransferProtocol { get; set; } = VMessGlobal.TransferProtocols[0]; + + /// + /// 伪装类型 + /// + public string FakeType { get; set; } = VMessGlobal.FakeTypes[0]; + + /// + /// QUIC + /// + public string QUIC { get; set; } = VMessGlobal.QUIC[0]; + + /// + /// 伪装域名 + /// + public string Host { get; set; } + + /// + /// 传输路径 + /// + public string Path { get; set; } + + /// + /// QUIC 加密方式 + /// + public string QUICSecure { get; set; } = VMessGlobal.QUIC[0]; + + /// + /// QUIC 加密密钥 + /// + public string QUICSecret { get; set; } = string.Empty; + + /// + /// TLS 底层传输安全 + /// + public bool TLSSecure { get; set; } = false; + + /// + /// Mux 多路复用 + /// + public bool UseMux { get; set; } = true; + } + + public class VMessGlobal + { + public static readonly List EncryptMethods = new List + { + "auto", + "none", + "aes-128-gcm", + "chacha20-poly1305" + }; + + public static readonly List QUIC = new List + { + "none", + "aes-128-gcm", + "chacha20-poly1305" + }; + + /// + /// V2Ray 传输协议 + /// + public static readonly List TransferProtocols = new List + { + "tcp", + "kcp", + "ws", + "h2", + "quic" + }; + + /// + /// V2Ray 伪装类型 + /// + public static readonly List FakeTypes = new List + { + "none", + "http", + "srtp", + "utp", + "wechat-video", + "dtls", + "wireguard" + }; + } +} \ No newline at end of file diff --git a/Netch/Controllers/EncryptedProxy/VMessController.cs b/Netch/ServerEx/VMess/VMessController.cs similarity index 73% rename from Netch/Controllers/EncryptedProxy/VMessController.cs rename to Netch/ServerEx/VMess/VMessController.cs index 18f72f62..4eb5850e 100644 --- a/Netch/Controllers/EncryptedProxy/VMessController.cs +++ b/Netch/ServerEx/VMess/VMessController.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; using System.IO; using Netch.Models; +using Netch.ServerEx.VMess.Models; using Newtonsoft.Json; -using VMess = Netch.Models.Information.VMess; -namespace Netch.Controllers +namespace Netch.ServerEx.VMess { - public class VMessController : EncryptedProxy + public class VMessController : ServerController { public VMessController() { @@ -16,34 +16,35 @@ namespace Netch.Controllers StoppedKeywords.AddRange(new[] {"config file not readable", "failed to"}); } - public override bool Start(Server server, Mode mode) + public override bool Start(Server s, Mode mode) { - File.WriteAllText("data\\last.json", JsonConvert.SerializeObject(new VMess.Config + var server = (VMess) s; + File.WriteAllText("data\\last.json", JsonConvert.SerializeObject(new VMessConfig.Config() { - inbounds = new List + inbounds = new List { - new VMess.Inbounds + new VMessConfig.Inbounds { - settings = new VMess.InboundSettings(), + settings = new VMessConfig.InboundSettings(), port = Socks5LocalPort, listen = LocalAddress } }, - outbounds = new List + outbounds = new List { - new VMess.Outbounds + new VMessConfig.Outbounds { - settings = new VMess.OutboundSettings + settings = new VMessConfig.OutboundSettings { - vnext = new List + vnext = new List { - new VMess.VNext + new VMessConfig.VNext { address = server.Hostname, port = server.Port, - users = new List + users = new List { - new VMess.User + new VMessConfig.User { id = server.UserID, alterId = server.AlterID, @@ -53,30 +54,30 @@ namespace Netch.Controllers } } }, - streamSettings = new VMess.StreamSettings + streamSettings = new VMessConfig.StreamSettings { network = server.TransferProtocol, security = server.TLSSecure ? "tls" : string.Empty, wsSettings = server.TransferProtocol == "ws" - ? new VMess.WebSocketSettings + ? new VMessConfig.WebSocketSettings { path = server.Path == string.Empty ? "/" : server.Path, - headers = new VMess.WSHeaders + headers = new VMessConfig.WSHeaders { Host = server.Host == string.Empty ? server.Hostname : server.Host } } : null, tcpSettings = server.FakeType == "http" - ? new VMess.TCPSettings + ? new VMessConfig.TCPSettings { - header = new VMess.TCPHeaders + header = new VMessConfig.TCPHeaders { type = server.FakeType, - request = new VMess.TCPRequest + request = new VMessConfig.TCPRequest { path = server.Path == string.Empty ? "/" : server.Path, - headers = new VMess.TCPRequestHeaders + headers = new VMessConfig.TCPRequestHeaders { Host = server.Host == string.Empty ? server.Hostname : server.Host } @@ -85,61 +86,61 @@ namespace Netch.Controllers } : null, kcpSettings = server.TransferProtocol == "kcp" - ? new VMess.KCPSettings + ? new VMessConfig.KCPSettings { - header = new VMess.TCPHeaders + header = new VMessConfig.TCPHeaders { type = server.FakeType } } : null, quicSettings = server.TransferProtocol == "quic" - ? new VMess.QUICSettings + ? new VMessConfig.QUICSettings { security = server.QUICSecure, key = server.QUICSecret, - header = new VMess.TCPHeaders + header = new VMessConfig.TCPHeaders { type = server.FakeType } } : null, httpSettings = server.TransferProtocol == "h2" - ? new VMess.HTTPSettings + ? new VMessConfig.HTTPSettings { host = server.Host == "" ? server.Hostname : server.Host, path = server.Path == "" ? "/" : server.Path } : null, - tlsSettings = new VMess.TLSSettings + tlsSettings = new VMessConfig.TLSSettings { allowInsecure = true, serverName = server.Host == "" ? server.Hostname : server.Host } }, - mux = new VMess.OutboundMux + mux = new VMessConfig.OutboundMux { enabled = server.UseMux } }, mode.Type == 0 || mode.Type == 1 || mode.Type == 2 - ? new VMess.Outbounds + ? new VMessConfig.Outbounds { tag = "TUNTAP", protocol = "freedom" } - : new VMess.Outbounds + : new VMessConfig.Outbounds { tag = "direct", protocol = "freedom" } }, - routing = new VMess.Routing + routing = new VMessConfig.Routing { - rules = new List + rules = new List { mode.BypassChina - ? new VMess.RoutingRules + ? new VMessConfig.RoutingRules { type = "field", ip = new List @@ -153,7 +154,7 @@ namespace Netch.Controllers }, outboundTag = "direct" } - : new VMess.RoutingRules + : new VMessConfig.RoutingRules { type = "field", ip = new List diff --git a/Netch/ServerEx/VMess/VMessUtil.cs b/Netch/ServerEx/VMess/VMessUtil.cs new file mode 100644 index 00000000..05d33f97 --- /dev/null +++ b/Netch/ServerEx/VMess/VMessUtil.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using Netch.Models; +using Netch.ServerEx.VMess.Form; +using Netch.ServerEx.VMess.Models; +using Netch.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Netch.ServerEx.VMess +{ + public class VMessUtil : IServerUtil + { + public ushort Priority { get; } = 2; + public string TypeName { get; } = "VMess"; + public string FullName { get; } = "VMess"; + public string[] UriScheme { get; } = {"vmess"}; + + public Server ParseJObject(JObject j) + { + return j.ToObject(); + } + + public void Edit(Server s) + { + new VMessForm(s).ShowDialog(); + } + + public void Create() + { + new VMessForm().ShowDialog(); + } + + public string GetShareLink(Server s) + { + var server = (VMess) s; + + var vmessJson = JsonConvert.SerializeObject(new + { + v = "2", + ps = server.Remark, + add = server.Hostname, + port = server.Port, + id = server.UserID, + aid = server.AlterID, + net = server.TransferProtocol, + type = server.FakeType, + host = server.Host, + path = server.Path, + tls = server.TLSSecure ? "tls" : "" + }); + return "vmess://" + ShareLink.URLSafeBase64Encode(vmessJson); + } + + public ServerController GetController() + { + return new VMessController(); + } + + public IEnumerable ParseUri(string text) + { + var data = new VMess(); + + text = text.Substring(8); + var vmess = JsonConvert.DeserializeObject(ShareLink.URLSafeBase64Decode(text)); + + data.Remark = vmess.ps; + data.Hostname = vmess.add; + data.Port = vmess.port; + data.UserID = vmess.id; + data.AlterID = vmess.aid; + data.TransferProtocol = vmess.net; + data.FakeType = vmess.type; + + if (vmess.v == null || vmess.v == "1") + { + var info = vmess.host.Split(';'); + if (info.Length == 2) + { + vmess.host = info[0]; + vmess.path = info[1]; + } + } + + if (data.TransferProtocol == "quic") + { + if (VMessGlobal.QUIC.Contains(vmess.host)) + { + data.QUICSecure = vmess.host; + data.QUICSecret = vmess.path; + } + } + else + { + data.Host = vmess.host; + data.Path = vmess.path; + } + + data.TLSSecure = vmess.tls == "tls"; + + if (vmess.mux == null) + { + data.UseMux = false; + } + else + { + if (vmess.mux.enabled is bool enabled) + { + data.UseMux = enabled; + } + else if (vmess.mux.enabled is string muxEnabled) + { + data.UseMux = muxEnabled == "true"; // 针对使用字符串当作布尔值的情况 + } + else + { + data.UseMux = false; + } + } + + data.EncryptMethod = "auto"; // V2Ray 加密方式不包括在链接中,主动添加一个 + return CheckServer(data) ? new[] {data} : null; + } + + public bool CheckServer(Server s) + { + var server = (VMess) s; + if (!VMessGlobal.TransferProtocols.Contains(server.TransferProtocol)) + { + Logging.Error($"不支持的 VMess 传输协议:{server.TransferProtocol}"); + return false; + } + + if (server.FakeType.Length != 0 && !VMessGlobal.FakeTypes.Contains(server.FakeType)) + { + Logging.Error($"不支持的 VMess 伪装类型:{server.FakeType}"); + return false; + } + + if (server.TransferProtocol == "quic") + { + if (!VMessGlobal.QUIC.Contains(server.QUICSecure)) + { + Logging.Error($"不支持的 VMess QUIC 加密方式:{server.QUICSecure}"); + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Netch/Utils/Bandwidth.cs b/Netch/Utils/Bandwidth.cs index e1fc1530..50c99eb2 100644 --- a/Netch/Utils/Bandwidth.cs +++ b/Netch/Utils/Bandwidth.cs @@ -73,9 +73,9 @@ namespace Netch.Utils { instances.Add(Process.GetCurrentProcess()); } - else if (MainController.EncryptedProxyController != null) + else if (MainController.ServerController != null) { - instances.Add(MainController.EncryptedProxyController.Instance); + instances.Add(MainController.ServerController.Instance); } else if (MainController.ModeController != null) { diff --git a/Netch/Utils/Configuration.cs b/Netch/Utils/Configuration.cs index cdb1950a..b6a0522a 100644 --- a/Netch/Utils/Configuration.cs +++ b/Netch/Utils/Configuration.cs @@ -1,7 +1,7 @@ -using System.Diagnostics; -using System.IO; +using System.IO; using Netch.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Netch.Utils { @@ -26,45 +26,26 @@ namespace Netch.Utils { try { - Global.Settings = JsonConvert.DeserializeObject(File.ReadAllText(SETTINGS_JSON)); - if (Global.Settings.Server != null && Global.Settings.Server.Count > 0) + var settingJObject = (JObject) JsonConvert.DeserializeObject(File.ReadAllText(SETTINGS_JSON)); + Global.Settings = settingJObject?.ToObject() ?? new Setting(); + Global.Settings.Server.Clear(); + + foreach (JObject server in settingJObject["Server"]) { - // 如果是旧版 Server 类,使用旧版 Server 类进行读取 - if (Global.Settings.Server[0].Hostname == null) - { - var LegacySettingConfig = JsonConvert.DeserializeObject(File.ReadAllText(SETTINGS_JSON)); - for (var i = 0; i < LegacySettingConfig.Server.Count; i++) - { - Global.Settings.Server[i].Hostname = LegacySettingConfig.Server[i].Address; - if (Global.Settings.Server[i].Type == "Shadowsocks") - { - Global.Settings.Server[i].Type = "SS"; - Global.Settings.Server[i].Plugin = LegacySettingConfig.Server[i].OBFS; - Global.Settings.Server[i].PluginOption = LegacySettingConfig.Server[i].OBFSParam; - } - else if (Global.Settings.Server[i].Type == "ShadowsocksR") - { - Global.Settings.Server[i].Type = "SSR"; - } - else if (Global.Settings.Server[i].Type == "VMess") - { - Global.Settings.Server[i].QUICSecure = LegacySettingConfig.Server[i].QUICSecurity; - } - } - } + var serverResult = Servers.ParseJObject(server); + if (serverResult != null) + Global.Settings.Server.Add(serverResult); } } catch (JsonException) { - } } else { // 弹出提示 i18N.Load("System"); - // MessageBoxX.Show(i18N.Translate("If this is your first time using this software,\n please check https://netch.org to install supports first,\n or the program may report errors.")); // 创建 data 文件夹并保存默认设置 Save(); @@ -80,35 +61,8 @@ namespace Netch.Utils { Directory.CreateDirectory(DATA_DIR); } + File.WriteAllText(SETTINGS_JSON, JsonConvert.SerializeObject(Global.Settings, Formatting.Indented)); } - - /// - /// 安装tap网卡 - /// - public static void addtap() - { - Logging.Info("正在安装 TUN/TAP 适配器"); - //安装Tap Driver - var installProcess = new Process(); - installProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - installProcess.StartInfo.FileName = Path.Combine("bin/tap-driver", "addtap.bat"); - installProcess.Start(); - installProcess.WaitForExit(); - installProcess.Close(); - } - /// - /// 卸载tap网卡 - /// - public static void deltapall() - { - Logging.Info("正在卸载 TUN/TAP 适配器"); - var installProcess = new Process(); - installProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - installProcess.StartInfo.FileName = Path.Combine("bin/tap-driver", "deltapall.bat"); - installProcess.Start(); - installProcess.WaitForExit(); - installProcess.Close(); - } } -} +} \ No newline at end of file diff --git a/Netch/Utils/Extensions.cs b/Netch/Utils/Extensions.cs deleted file mode 100644 index 933b9021..00000000 --- a/Netch/Utils/Extensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Netch.Utils -{ - public static class Extensions - { - public static IEnumerable GetLines(this string str, bool removeEmptyLines = true) - { - using var sr = new StringReader(str); - string line; - while ((line = sr.ReadLine()) != null) - { - if (removeEmptyLines && string.IsNullOrWhiteSpace(line)) - { - continue; - } - - yield return line; - } - } - } -} diff --git a/Netch/Utils/Firewall.cs b/Netch/Utils/Firewall.cs index 5042e629..9a0233ff 100644 --- a/Netch/Utils/Firewall.cs +++ b/Netch/Utils/Firewall.cs @@ -11,12 +11,10 @@ namespace Netch.Utils { "bin/NTT.exe", "bin/Privoxy.exe", - "bin/Redirector.exe", "bin/Shadowsocks.exe", "bin/ShadowsocksR.exe", "bin/Trojan.exe", "bin/tun2socks.exe", - "bin/unbound.exe", "bin/v2ray.exe", "Netch.exe" }; diff --git a/Netch/Utils/OnlyInstance.cs b/Netch/Utils/OnlyInstance.cs index bbaf4843..ce5dd142 100644 --- a/Netch/Utils/OnlyInstance.cs +++ b/Netch/Utils/OnlyInstance.cs @@ -12,7 +12,7 @@ namespace Netch.Utils Show, Exit } - + public static event EventHandler Called; private static void OnCalled(Commands e) diff --git a/Netch/Utils/Servers.cs b/Netch/Utils/Servers.cs new file mode 100644 index 00000000..de0d4140 --- /dev/null +++ b/Netch/Utils/Servers.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Netch.Models; +using Newtonsoft.Json.Linq; + +namespace Netch.Utils +{ + public static class Servers + { + public static readonly IEnumerable ServerUtils; + + static Servers() + { + var serversUtilsTypes = Assembly.GetExecutingAssembly().GetExportedTypes().Where(type => type.GetInterfaces().Any(t => t == typeof(IServerUtil))); + ServerUtils = serversUtilsTypes.Select(t => (IServerUtil) Activator.CreateInstance(t)).OrderBy(util => util.Priority); + } + + public static Server ParseJObject(JObject o) + { + var handle = GetUtilByTypeName((string) o["Type"]); + if (handle == null) + { + Logging.Warning($"不支持的服务器类型: {o["Type"]}"); + return null; + } + + return handle.ParseJObject(o); + } + + public static IServerUtil GetUtilByTypeName(string typeName) + { + if (string.IsNullOrEmpty(typeName)) + return null; + return ServerUtils.FirstOrDefault(i => (i.TypeName ?? "").Equals(typeName)); + } + + public static IServerUtil GetUtilByFullName(string fullName) + { + if (string.IsNullOrEmpty(fullName)) + return null; + return ServerUtils.FirstOrDefault(i => (i.FullName ?? "").Equals(fullName)); + } + + public static IServerUtil GetUtilByUriScheme(string typeName) + { + return ServerUtils.FirstOrDefault(i => i.UriScheme.Any(s => s.Equals(typeName))); + } + } +} \ No newline at end of file diff --git a/Netch/Utils/ShareLink.cs b/Netch/Utils/ShareLink.cs index 8db51bfb..f4a73737 100644 --- a/Netch/Utils/ShareLink.cs +++ b/Netch/Utils/ShareLink.cs @@ -1,28 +1,30 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using System.Web; -using Netch.Models; -using Netch.Models.SS; -using Netch.Models.SSD; +using Netch.ServerEx.Shadowsocks; +using Netch.ServerEx.Shadowsocks.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Server = Netch.Models.Server; namespace Netch.Utils { public static class ShareLink { + #region Utils + /// - /// URL 传输安全的 Base64 解码 - /// - /// 需要解码的字符串 - /// 解码后的字符串 + /// URL 传输安全的 Base64 解码 + /// + /// 需要解码的字符串 + /// 解码后的字符串 public static string URLSafeBase64Decode(string text) { return Encoding.UTF8.GetString(Convert.FromBase64String(text.Replace("-", "+").Replace("_", "/").PadRight(text.Length + (4 - text.Length % 4) % 4, '='))); } + /// /// URL 传输安全的 Base64 加密 /// @@ -33,82 +35,111 @@ namespace Netch.Utils return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)).Replace("+", "-").Replace("/", "_").Replace("=", ""); } - /// - /// 根据服务器生成分享链接 - /// - /// 需要获取分享链接的服务器 - /// 解码后的字符串 - public static string GetShareLink(Server server) + private static string RemoveEmoji(string text) { - var retLinkStr = ""; - switch (server.Type) - { - case "Socks5": - // https://t.me/socks?server=1.1.1.1&port=443 - retLinkStr = string.Format("https://t.me/socks?server={0}&port={1}", server.Hostname, server.Port); - - break; - case "SS": - // ss://method:password@server:port#Remark - retLinkStr = "ss://" + URLSafeBase64Encode(string.Format("{0}:{1}@{2}:{3}", server.EncryptMethod, server.Password, server.Hostname, server.Port)) + "#" + HttpUtility.UrlEncode(server.Remark); - - break; - case "SSR": - // 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) - var paraStr = string.Format("/?obfsparam={0}&protoparam={1}&remarks={2}", URLSafeBase64Encode(server.OBFSParam), URLSafeBase64Encode(server.ProtocolParam), URLSafeBase64Encode(server.Remark)); - retLinkStr = "ssr://" + URLSafeBase64Encode(string.Format("{0}:{1}:{2}:{3}:{4}:{5}{6}", server.Hostname, server.Port, server.Protocol, server.EncryptMethod, server.OBFS, URLSafeBase64Encode(server.Password), paraStr)); - - break; - case "VMess": - var vmessJson = JsonConvert.SerializeObject(new - { - v = "2", - ps = server.Remark, - add = server.Hostname, - port = server.Port, - id = server.UserID, - aid = server.AlterID, - net = server.TransferProtocol, - type = server.FakeType, - host = server.Host, - path = server.Path, - tls = server.TLSSecure ? "tls" : "" - }); - retLinkStr = "vmess://" + URLSafeBase64Encode(vmessJson); - - break; - default: - return null; - } - return retLinkStr; + byte[] emojiBytes = {240, 159}; + var remark = Encoding.UTF8.GetBytes(text); + var startIndex = 0; + while (remark.Length > startIndex + 1 && remark[startIndex] == emojiBytes[0] && remark[startIndex + 1] == emojiBytes[1]) + startIndex += 4; + return Encoding.UTF8.GetString(remark.Skip(startIndex).ToArray()).Trim(); } - public static List Parse(string text) + + public static string UnBase64String(string value) { + if (string.IsNullOrEmpty(value)) + { + return ""; + } + + var bytes = Convert.FromBase64String(value); + return Encoding.UTF8.GetString(bytes); + } + + public static string ToBase64String(string value) + { + if (string.IsNullOrEmpty(value)) + { + return ""; + } + + var bytes = Encoding.UTF8.GetBytes(value); + return Convert.ToBase64String(bytes); + } + + public static Dictionary ParseParam(string paramStr) + { + var paramsDict = new Dictionary(); + var obfsParams = paramStr.Split('&'); + foreach (var p in obfsParams) + { + if (p.IndexOf('=') > 0) + { + var index = p.IndexOf('='); + var key = p.Substring(0, index); + var val = p.Substring(index + 1); + paramsDict[key] = val; + } + } + + return paramsDict; + } + + public static IEnumerable GetLines(this string str, bool removeEmptyLines = true) + { + using var sr = new StringReader(str); + string line; + while ((line = sr.ReadLine()) != null) + { + if (removeEmptyLines && string.IsNullOrWhiteSpace(line)) + { + continue; + } + + yield return line; + } + } + + #endregion + + public static string GetShareLink(Server server) + { + return Servers.GetUtilByTypeName(server.Type).GetShareLink(server); + } + + public static List ParseText(string text) + { + try + { + text = URLSafeBase64Decode(text); + } + catch + { + // ignored + } + var list = new List(); try { try { - var ssServers = JsonConvert.DeserializeObject>(text); - list.AddRange(ssServers.Select(shadowsocksServer => new Server + list.AddRange(JsonConvert.DeserializeObject>(text).Select(server => new Shadowsocks { - Type = "SS", - Hostname = shadowsocksServer.server, - Port = shadowsocksServer.server_port, - EncryptMethod = shadowsocksServer.method, - Password = shadowsocksServer.password, - Remark = shadowsocksServer.remarks, - Plugin = shadowsocksServer.plugin, - PluginOption = shadowsocksServer.plugin_opts + Hostname = server.server, + Port = server.server_port, + EncryptMethod = server.method, + Password = server.password, + Remark = server.remarks, + Plugin = server.plugin, + PluginOption = server.plugin_opts })); } catch (JsonReaderException) { foreach (var line in text.GetLines()) { - var servers = ParseLine(line); + var servers = ParseUri(line); if (servers != null) { list.AddRange(servers); @@ -130,7 +161,7 @@ namespace Netch.Utils return list; } - private static IEnumerable ParseLine(string text) + private static IEnumerable ParseUri(string text) { var list = new List(); @@ -138,356 +169,23 @@ namespace Netch.Utils { if (text.StartsWith("tg://socks?") || text.StartsWith("https://t.me/socks?")) { - var data = new Server(); - data.Type = "Socks5"; - - var dict = new Dictionary(); - foreach (var str in text.Replace("tg://socks?", "").Replace("https://t.me/socks?", "").Split('&')) - { - var splited = str.Split('='); - - dict.Add(splited[0], splited[1]); - } - - if (!dict.ContainsKey("server") || !dict.ContainsKey("port")) - { - return null; - } - - data.Hostname = dict["server"]; - data.Port = int.Parse(dict["port"]); - if (dict.ContainsKey("user") && !string.IsNullOrWhiteSpace(dict["user"])) - { - data.Username = dict["user"]; - } - if (dict.ContainsKey("pass") && !string.IsNullOrWhiteSpace(dict["pass"])) - { - data.Password = dict["pass"]; - } - - list.Add(data); - } - else if (text.StartsWith("ss://")) - { - var data = new Server(); - data.Type = "SS"; - - text = text.Replace("/?", "?"); - try - { - if (text.Contains("#")) - { - data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); - text = text.Split('#')[0]; - } - if (text.Contains("?")) - { - var finder = new Regex(@"^(?.+?)\?(.+)$"); - var match = finder.Match(text); - - if (match.Success) - { - var plugins = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("plugin")); - if (plugins != null) - { - var plugin = plugins.Substring(0, plugins.IndexOf(";", StringComparison.Ordinal)); - var pluginopts = plugins.Substring(plugins.IndexOf(";", StringComparison.Ordinal) + 1); - if (plugin == "obfs-local" || plugin == "simple-obfs") - { - plugin = "simple-obfs"; - if (!pluginopts.Contains("obfs=")) - pluginopts = "obfs=http;obfs-host=" + pluginopts; - } - else if (plugin == "simple-obfs-tls") - { - plugin = "simple-obfs"; - if (!pluginopts.Contains("obfs=")) - pluginopts = "obfs=tls;obfs-host=" + pluginopts; - } - - data.Plugin = plugin; - data.PluginOption = pluginopts; - } - - text = match.Groups["data"].Value; - } - else - { - throw new FormatException(); - } - } - if (text.Contains("@")) - { - var finder = new Regex(@"^ss://(?.+?)@(?.+):(?\d+)"); - var parser = new Regex(@"^(?.+?):(?.+)$"); - var match = finder.Match(text); - if (!match.Success) - { - throw new FormatException(); - } - - data.Hostname = match.Groups["server"].Value; - data.Port = int.Parse(match.Groups["port"].Value); - - var base64 = URLSafeBase64Decode(match.Groups["base64"].Value); - match = parser.Match(base64); - if (!match.Success) - { - throw new FormatException(); - } - - data.EncryptMethod = match.Groups["method"].Value; - data.Password = match.Groups["password"].Value; - } - else - { - var parser = new Regex(@"^((?.+?):(?.+)@(?.+):(?\d+))"); - var match = parser.Match(URLSafeBase64Decode(text.Replace("ss://", ""))); - if (!match.Success) - { - throw new FormatException(); - } - - data.Hostname = match.Groups["server"].Value; - data.Port = int.Parse(match.Groups["port"].Value); - data.EncryptMethod = match.Groups["method"].Value; - data.Password = match.Groups["password"].Value; - } - - if (!Global.EncryptMethods.SS.Contains(data.EncryptMethod)) - { - Logging.Error($"不支持的 SS 加密方式:{data.EncryptMethod}"); - return null; - } - - list.Add(data); - } - catch (FormatException) - { - return null; - } - } - else if (text.StartsWith("ssd://")) - { - var json = JsonConvert.DeserializeObject
(URLSafeBase64Decode(text.Substring(6))); - - foreach (var server in json.servers) - { - var data = new Server(); - data.Type = "SS"; - - data.Remark = server.remarks; - data.Hostname = server.server; - data.Port = server.port != 0 ? server.port : json.port; - data.Password = server.password != null ? server.password : json.password; - data.EncryptMethod = server.encryption != null ? server.encryption : json.encryption; - data.Plugin = string.IsNullOrEmpty(json.plugin) ? string.IsNullOrEmpty(server.plugin) ? null : server.plugin : json.plugin; - data.PluginOption = string.IsNullOrEmpty(json.plugin_options) ? string.IsNullOrEmpty(server.plugin_options) ? null : server.plugin_options : json.plugin_options; - - if (Global.EncryptMethods.SS.Contains(data.EncryptMethod)) - { - list.Add(data); - } - } - } - else if (text.StartsWith("ssr://")) - { - - list.Add(SsrServerFromLink(text)); - - } - else if (text.StartsWith("vmess://")) - { - var data = new Server(); - data.Type = "VMess"; - - text = text.Substring(8); - var vmess = JsonConvert.DeserializeObject(URLSafeBase64Decode(text)); - - data.Remark = vmess.ps; - data.Hostname = vmess.add; - data.Port = vmess.port; - data.UserID = vmess.id; - data.AlterID = vmess.aid; - - data.TransferProtocol = vmess.net; - if (!Global.TransferProtocols.Contains(data.TransferProtocol)) - { - Logging.Error($"不支持的 VMess 传输协议:{data.TransferProtocol}"); - return null; - } - - data.FakeType = vmess.type; - if (data.FakeType.Length != 0 && !Global.FakeTypes.Contains(data.FakeType)) - { - Logging.Error($"不支持的 VMess 伪装类型:{data.FakeType}"); - return null; - } - - if (vmess.v == null || vmess.v == "1") - { - var info = vmess.host.Split(';'); - if (info.Length == 2) - { - vmess.host = info[0]; - vmess.path = info[1]; - } - } - if (data.TransferProtocol == "quic") - { - if (!Global.EncryptMethods.VMessQUIC.Contains(vmess.host)) - { - Logging.Error($"不支持的 VMess QUIC 加密方式:{vmess.host}"); - return null; - } - - data.QUICSecure = vmess.host; - data.QUICSecret = vmess.path; - - } - else - { - data.Host = vmess.host; - data.Path = vmess.path; - } - data.TLSSecure = vmess.tls == "tls"; - - if (vmess.mux == null) - { - data.UseMux = false; - } - else - { - if (vmess.mux.enabled is bool enabled) - { - data.UseMux = enabled; - } - else if (vmess.mux.enabled is string muxEnabled) - { - data.UseMux = muxEnabled == "true"; // 针对使用字符串当作布尔值的情况 - } - else - { - data.UseMux = false; - } - } - - data.EncryptMethod = "auto"; // V2Ray 加密方式不包括在链接中,主动添加一个 - - list.Add(data); + list.AddRange(Servers.GetUtilByTypeName("Socks5").ParseUri(text)); } else if (text.StartsWith("Netch://")) { - text = text.Substring(8); - var NetchLink = JsonConvert.DeserializeObject(URLSafeBase64Decode(text)); - if (!string.IsNullOrEmpty(NetchLink.Hostname) || NetchLink.Port > 65536 || NetchLink.Port > 0) - { - return null; - } - - switch (NetchLink.Type) - { - case "Socks5": - list.Add(NetchLink); - break; - case "SS": - if (!Global.EncryptMethods.SS.Contains(NetchLink.EncryptMethod)) - { - Logging.Error($"不支持的 SS 加密方式:{NetchLink.EncryptMethod}"); - return null; - } - break; - case "SSR": - if (!Global.EncryptMethods.SSR.Contains(NetchLink.EncryptMethod)) - { - Logging.Error($"不支持的 SSR 加密方式:{NetchLink.EncryptMethod}"); - return null; - } - if (!Global.Protocols.Contains(NetchLink.Protocol)) - { - Logging.Error($"不支持的 SSR 协议:{NetchLink.Protocol}"); - return null; - } - if (!Global.OBFSs.Contains(NetchLink.OBFS)) - { - Logging.Error($"不支持的 SSR 混淆:{NetchLink.OBFS}"); - return null; - } - break; - case "VMess": - if (!Global.TransferProtocols.Contains(NetchLink.TransferProtocol)) - { - Logging.Error($"不支持的 VMess 传输协议:{NetchLink.TransferProtocol}"); - return null; - } - if (!Global.FakeTypes.Contains(NetchLink.FakeType)) - { - Logging.Error($"不支持的 VMess 伪装类型:{NetchLink.FakeType}"); - return null; - } - if (NetchLink.TransferProtocol == "quic") - { - if (!Global.EncryptMethods.VMessQUIC.Contains(NetchLink.QUICSecure)) - { - Logging.Error($"不支持的 VMess QUIC 加密方式:{NetchLink.QUICSecure}"); - return null; - } - } - break; - default: - return null; - } - list.Add(NetchLink); + list.Add(ParseNetchUri(text)); } - else if (text.StartsWith("trojan://")) + else { - var data = new Server(); - data.Type = "Trojan"; - - text = text.Replace("/?", "?"); - try - { - if (text.Contains("#")) - { - data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); - text = text.Split('#')[0]; - } - if (text.Contains("?")) - { - var reg = new Regex(@"^(?.+?)\?(.+)$"); - var regmatch = reg.Match(text); - - if (regmatch.Success) - { - var peer = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("peer")); - - if (peer != null) - data.Host = peer; - - text = regmatch.Groups["data"].Value; - } - else - { - throw new FormatException(); - } - } - var finder = new Regex(@"^trojan://(?.+?)@(?.+):(?\d+)"); - var match = finder.Match(text); - if (!match.Success) - { - throw new FormatException(); - } - - data.Password = match.Groups["psk"].Value; - data.Hostname = match.Groups["server"].Value; - data.Port = int.Parse(match.Groups["port"].Value); - - list.Add(data); - } - catch (FormatException) + var scheme = GetUriScheme(text); + var util = Servers.GetUtilByUriScheme(scheme); + if (util == null) { + Logging.Warning($"无法处理 {scheme} 协议订阅链接"); return null; } + + list.AddRange(util.ParseUri(text)); } } catch (Exception e) @@ -496,135 +194,49 @@ namespace Netch.Utils return null; } - byte[] emoji_bytes = { 240, 159 }; foreach (var node in list) { - var remark = Encoding.UTF8.GetBytes(node.Remark); - var start_index = 0; - while (remark.Length > start_index + 1 && remark[start_index] == emoji_bytes[0] && remark[start_index + 1] == emoji_bytes[1]) - start_index += 4; - node.Remark = Encoding.UTF8.GetString(remark.Skip(start_index).ToArray()).Trim(); + node.Remark = RemoveEmoji(node.Remark); } - return list; - } - public static string UnBase64String(string value) - { - if (value == null || value == "") - { - return ""; - } - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes); - } - public static string ToBase64String(string value) - { - if (value == null || value == "") - { - return ""; - } - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); + return list.Where(s => s != null); } - /// - /// SSR链接解析器 - /// Copy From https://github.com/HMBSbige/ShadowsocksR-Windows/blob/d9dc8d032a6e04c14b9dc6c8f673c9cc5aa9f607/shadowsocks-csharp/Model/Server.cs#L428 - /// Thx :D - /// - /// - /// - public static Server SsrServerFromLink(string ssrUrl) + private static string GetUriScheme(string text) { - // ssr://host:port:protocol:method:obfs:base64pass/?obfsparam=base64&remarks=base64&group=base64&udpport=0&uot=1 - var ssr = Regex.Match(ssrUrl, "ssr://([A-Za-z0-9_-]+)", RegexOptions.IgnoreCase); - if (!ssr.Success) - throw new FormatException(); - - var data = URLSafeBase64Decode(ssr.Groups[1].Value); - var params_dict = new Dictionary(); - - var param_start_pos = data.IndexOf("?", StringComparison.Ordinal); - if (param_start_pos > 0) - { - params_dict = ParseParam(data.Substring(param_start_pos + 1)); - data = data.Substring(0, param_start_pos); - } - if (data.IndexOf("/", StringComparison.Ordinal) >= 0) - { - data = data.Substring(0, data.LastIndexOf("/", StringComparison.Ordinal)); - } - - var UrlFinder = new Regex("^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)"); - var match = UrlFinder.Match(data); - - if (match == null || !match.Success) - throw new FormatException(); - - var serverAddr = match.Groups[1].Value; - var Server_Port = ushort.Parse(match.Groups[2].Value); - var Protocol = match.Groups[3].Value.Length == 0 ? "origin" : match.Groups[3].Value; - Protocol = Protocol.Replace("_compatible", ""); - var Method = match.Groups[4].Value; - var obfs = match.Groups[5].Value.Length == 0 ? "plain" : match.Groups[5].Value; - obfs = obfs.Replace("_compatible", ""); - var Password = URLSafeBase64Decode(match.Groups[6].Value); - var ProtocolParam = ""; - var ObfsParam = ""; - var Remarks = ""; - var Group = ""; - - if (params_dict.ContainsKey("protoparam")) - { - ProtocolParam = URLSafeBase64Decode(params_dict["protoparam"]); - } - if (params_dict.ContainsKey("obfsparam")) - { - ObfsParam = URLSafeBase64Decode(params_dict["obfsparam"]); - } - if (params_dict.ContainsKey("remarks")) - { - Remarks = URLSafeBase64Decode(params_dict["remarks"]); - } - Group = params_dict.ContainsKey("group") ? URLSafeBase64Decode(params_dict["group"]) : string.Empty; - - /*if (params_dict.ContainsKey("uot")) - { - UdpOverTcp = int.Parse(params_dict["uot"]) != 0; - } - if (params_dict.ContainsKey("udpport")) - { - Server_Udp_Port = ushort.Parse(params_dict["udpport"]); - }*/ - var server = new Server(); - server.Type = "SSR"; - server.Hostname = serverAddr; - server.Port = Server_Port; - server.Protocol = Protocol; - server.EncryptMethod = Method; - server.OBFS = obfs; - server.Password = Password; - server.ProtocolParam = ProtocolParam; - server.OBFSParam = ObfsParam; - server.Remark = Remarks; - server.Group = Group; - return server; + var endIndex = text.IndexOf("://", StringComparison.Ordinal); + if (endIndex == -1) + throw new UriFormatException("Text is not a URI"); + return text.Substring(0, endIndex); } - private static Dictionary ParseParam(string paramStr) + + private static Server ParseNetchUri(string text) { - var paramsDict = new Dictionary(); - var obfsParams = paramStr.Split('&'); - foreach (var p in obfsParams) + text = text.Substring(8); + var NetchLink = (JObject) JsonConvert.DeserializeObject(URLSafeBase64Decode(text)); + if (NetchLink == null) { - if (p.IndexOf('=') > 0) - { - var index = p.IndexOf('='); - var key = p.Substring(0, index); - var val = p.Substring(index + 1); - paramsDict[key] = val; - } + return null; } - return paramsDict; + + if (string.IsNullOrEmpty((string) NetchLink["Hostname"])) + { + return null; + } + + if (!ushort.TryParse((string) NetchLink["Port"], out _)) + { + return null; + } + + var type = (string) NetchLink["Type"]; + var s = Servers.GetUtilByTypeName(type).ParseJObject(NetchLink); + return Servers.GetUtilByTypeName(s.Type).CheckServer(s) ? s : null; + } + + public static string GetNetchLink(Server s) + { + return "Netch://" + URLSafeBase64Encode(JsonConvert.SerializeObject(s)); } } -} +} \ No newline at end of file diff --git a/Netch/Utils/TUNTAP.cs b/Netch/Utils/TUNTAP.cs index ca795950..49587b59 100644 --- a/Netch/Utils/TUNTAP.cs +++ b/Netch/Utils/TUNTAP.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.IO; using Microsoft.Win32; namespace Netch.Utils @@ -62,5 +64,30 @@ namespace Netch.Utils { return false; } + + /// + /// 卸载tap网卡 + /// + public static void deltapall() + { + Logging.Info("正在卸载 TUN/TAP 适配器"); + var installProcess = new Process {StartInfo = {WindowStyle = ProcessWindowStyle.Hidden, FileName = Path.Combine("bin/tap-driver", "deltapall.bat")}}; + installProcess.Start(); + installProcess.WaitForExit(); + installProcess.Close(); + } + + /// + /// 安装tap网卡 + /// + public static void addtap() + { + Logging.Info("正在安装 TUN/TAP 适配器"); + //安装Tap Driver + var installProcess = new Process {StartInfo = {WindowStyle = ProcessWindowStyle.Hidden, FileName = Path.Combine("bin/tap-driver", "addtap.bat")}}; + installProcess.Start(); + installProcess.WaitForExit(); + installProcess.Close(); + } } } \ No newline at end of file diff --git a/Netch/Utils/Utils.cs b/Netch/Utils/Utils.cs index a44a88ad..38e678e8 100644 --- a/Netch/Utils/Utils.cs +++ b/Netch/Utils/Utils.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Drawing; using System.IO; using System.Linq; using System.Net; @@ -9,6 +9,7 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; using MaxMind.GeoIP2; namespace Netch.Utils @@ -172,7 +173,7 @@ namespace Netch.Utils } catch (Exception e) { - Logging.Error($"找不到出口IP所在网卡{e}"); + Logging.Error($"找不到出口IP所在网卡: {e}"); return false; } } @@ -182,5 +183,27 @@ namespace Netch.Utils var adapter = NetworkInterface.GetAllNetworkInterfaces().First(adapter => adapter.Id == id); Logging.Warning($"检索此网卡信息出错: {adapter.Name} {adapter.Id} {adapter.Description}"); } + + public static void DrawCenterComboBox(object sender, DrawItemEventArgs e) + { + if (sender is ComboBox cbx) + { + e.DrawBackground(); + + if (e.Index >= 0) + { + var brush = new SolidBrush(cbx.ForeColor); + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + brush = SystemBrushes.HighlightText as SolidBrush; + + e.Graphics.DrawString(cbx.Items[e.Index].ToString(), cbx.Font, brush, e.Bounds, new StringFormat + { + LineAlignment = StringAlignment.Center, + Alignment = StringAlignment.Center + }); + } + } + } } } \ No newline at end of file diff --git a/Netch/Utils/WMI.cs b/Netch/Utils/WMI.cs deleted file mode 100644 index 37def91a..00000000 --- a/Netch/Utils/WMI.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Management; - -namespace Netch.Utils -{ - static internal class WMI - { - public static ManagementObject GetManagementObjectByDeviceNameOrDefault(string deviceName) - { - foreach (ManagementObject mo in new ManagementClass("Win32_NetworkAdapterConfiguration").GetInstances()) - { - if (((string) mo["Caption"]).EndsWith(deviceName)) - { - return mo; - } - } - - return null; - } - } -} \ No newline at end of file