commit 334c9ba7a74ebe7a1695cc73137c75fe2f2a7918 Author: Netch Date: Tue Jun 15 21:44:03 2021 +0800 Add files via upload diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..ca8fba75 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Netch Build CI +on: [push, pull_request] + +jobs: + build: + name: Build + runs-on: windows-latest + steps: + - name: MSBuild + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Clone + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Build + shell: pwsh + run: | + .\build.ps1 -Configuration Release -OutputPath release + + - name: Upload + uses: actions/upload-artifact@v2 + with: + name: Netch + path: release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..526bb20b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Netch Release CI +on: + push: + tags: + - '*.*.*' + +jobs: + build: + name: Build + runs-on: windows-latest + steps: + - name: MSBuild + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Build + shell: pwsh + run: | + .\build.ps1 -Configuration Release -OutputPath release + + - name: Package + shell: pwsh + run: | + 7z a -mx9 Netch.7z release + 7z rn Netch.7z release Netch + echo "NETCH_SHA256=$(.\sha256.ps1 Netch.7z)" | Out-File -Append -Encoding UTF8 -FilePath $Env:GITHUB_ENV + + - name: Release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + prerelease: ${{ contains(github.ref, '-') }} + draft: false + files: | + Netch.7z + body: | + [![](https://img.shields.io/badge/Telegram-Channel-blue)](https://t.me/netch_channel) [![](https://img.shields.io/badge/Telegram-Group-green)](https://t.me/netch_group) + ## 更新日志 + * 这是 GitHub Actions 自动化部署,更新日志应该很快会手动更新 + + ## 校验和 + | 文件名 | SHA256 | + | :- | :- | + | Netch.7z | ${{ env.Netch_SHA256 }} | diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e0e7e21c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vs +/.idea +/packages +/TestResults diff --git a/Netch.sln b/Netch.sln new file mode 100644 index 00000000..c3ba5c67 --- /dev/null +++ b/Netch.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31205.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Netch", "Netch\Netch.csproj", "{A193DF89-ADCF-4DB4-B75C-729C8BA8A9F3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{09222C6B-2FFB-4DA7-BC75-CB0A80086711}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A193DF89-ADCF-4DB4-B75C-729C8BA8A9F3}.Debug|x64.ActiveCfg = Debug|x64 + {A193DF89-ADCF-4DB4-B75C-729C8BA8A9F3}.Debug|x64.Build.0 = Debug|x64 + {A193DF89-ADCF-4DB4-B75C-729C8BA8A9F3}.Release|x64.ActiveCfg = Release|x64 + {A193DF89-ADCF-4DB4-B75C-729C8BA8A9F3}.Release|x64.Build.0 = Release|x64 + {09222C6B-2FFB-4DA7-BC75-CB0A80086711}.Debug|x64.ActiveCfg = Debug|x64 + {09222C6B-2FFB-4DA7-BC75-CB0A80086711}.Debug|x64.Build.0 = Debug|x64 + {09222C6B-2FFB-4DA7-BC75-CB0A80086711}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E271FD7-9623-47D5-B3AF-309BC23CB48E} + EndGlobalSection +EndGlobal diff --git a/Netch/.gitignore b/Netch/.gitignore new file mode 100644 index 00000000..aeb4af3c --- /dev/null +++ b/Netch/.gitignore @@ -0,0 +1,3 @@ +/bin +/obj +/*.csproj.user diff --git a/Netch/App.manifest b/Netch/App.manifest new file mode 100644 index 00000000..ff307d7a --- /dev/null +++ b/Netch/App.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + True/PM + + + diff --git a/Netch/App.xaml b/Netch/App.xaml new file mode 100644 index 00000000..e489435e --- /dev/null +++ b/Netch/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Netch/App.xaml.cs b/Netch/App.xaml.cs new file mode 100644 index 00000000..32d2f8ea --- /dev/null +++ b/Netch/App.xaml.cs @@ -0,0 +1,13 @@ +using System.Windows; + +namespace Netch +{ + public partial class App : Application + { + private void Application_Startup(object sender, StartupEventArgs e) + { + this.MainWindow = new Forms.MainWindow(); + this.MainWindow.Show(); + } + } +} diff --git a/Netch/AssemblyInfo.cs b/Netch/AssemblyInfo.cs new file mode 100644 index 00000000..74087a1f --- /dev/null +++ b/Netch/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Netch/Controllers/Interface/IController.cs b/Netch/Controllers/Interface/IController.cs new file mode 100644 index 00000000..d37337f4 --- /dev/null +++ b/Netch/Controllers/Interface/IController.cs @@ -0,0 +1,9 @@ +namespace Netch.Controllers.Interface +{ + public interface IController + { + bool Create(Models.Server.Server s, Models.Mode.Mode m); + + bool Delete(); + } +} diff --git a/Netch/Controllers/MainController.cs b/Netch/Controllers/MainController.cs new file mode 100644 index 00000000..c069e950 --- /dev/null +++ b/Netch/Controllers/MainController.cs @@ -0,0 +1,108 @@ +using System; + +namespace Netch.Controllers +{ + public class MainController : Interface.IController + { + /// + /// 节点控制器 + /// + private Interface.IController NodeController; + + /// + /// 模式控制器 + /// + private Interface.IController ModeController; + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + switch (s.Type) + { + case Models.Server.ServerType.Socks: + break; + case Models.Server.ServerType.Shadowsocks: + { + if (m.Type == Models.Mode.ModeType.ProcessMode) + { + var node = s as Models.Server.Shadowsocks.Shadowsocks; + if (String.IsNullOrEmpty(node.OBFS)) + { + break; + } + } + + this.NodeController = new Server.SSController(); + } + break; + case Models.Server.ServerType.ShadowsocksR: + this.NodeController = new Server.SRController(); + break; + case Models.Server.ServerType.Trojan: + this.NodeController = new Server.TRController(); + break; + case Models.Server.ServerType.VLess: + this.NodeController = new Server.VLController(); + break; + case Models.Server.ServerType.VMess: + this.NodeController = new Server.VMController(); + break; + default: + Global.Logger.Error($"未知的节点类型:{s.Type}"); + + return false; + } + + { + var status = this.NodeController?.Create(s, m); + if (status.HasValue && !status.Value) + { + return false; + } + } + + switch (m.Type) + { + case Models.Mode.ModeType.ProcessMode: + this.ModeController = new Mode.ProcessController(); + break; + case Models.Mode.ModeType.ShareMode: + this.ModeController = new Mode.ShareController(); + break; + case Models.Mode.ModeType.TapMode: + this.ModeController = new Mode.TapController(); + break; + case Models.Mode.ModeType.TunMode: + this.ModeController = new Mode.TunController(); + break; + case Models.Mode.ModeType.WebMode: + this.ModeController = new Mode.WebController(); + break; + case Models.Mode.ModeType.WmpMode: + this.ModeController = new Mode.WmpController(); + break; + default: + Global.Logger.Error($"未知的模式类型:{s.Type}"); + + return false; + } + + { + var status = this.ModeController?.Create(s, m); + if (status.HasValue && !status.Value) + { + return false; + } + } + + return true; + } + + public bool Delete() + { + this.NodeController?.Delete(); + this.ModeController?.Delete(); + + return true; + } + } +} diff --git a/Netch/Controllers/Mode/ProcessController.cs b/Netch/Controllers/Mode/ProcessController.cs new file mode 100644 index 00000000..711fa734 --- /dev/null +++ b/Netch/Controllers/Mode/ProcessController.cs @@ -0,0 +1,136 @@ +using System; +using System.Runtime.InteropServices; + +namespace Netch.Controllers.Mode +{ + public class ProcessController : Interface.IController + { + private enum NameList : int + { + TYPE_FILTERLOOPBACK, + TYPE_FILTERTCP, + TYPE_FILTERUDP, + TYPE_CLRNAME, + TYPE_ADDNAME, + TYPE_BYPNAME, + TYPE_DNSHOST, + TYPE_TCPLISN, + TYPE_TCPTYPE, + TYPE_TCPHOST, + TYPE_TCPUSER, + TYPE_TCPPASS, + TYPE_TCPMETH, + TYPE_TCPPROT, + TYPE_TCPPRPA, + TYPE_TCPOBFS, + TYPE_TCPOBPA, + TYPE_UDPLISN, + TYPE_UDPTYPE, + TYPE_UDPHOST, + TYPE_UDPUSER, + TYPE_UDPPASS, + TYPE_UDPMETH, + TYPE_UDPPROT, + TYPE_UDPPRPA, + TYPE_UDPOBFS, + TYPE_UDPOBPA + } + + private static class Methods + { + [DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool aio_dial(NameList name, [MarshalAs(UnmanagedType.LPWStr)] string value); + + [DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool aio_init(); + + [DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool aio_free(); + + [DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong aio_getUP(); + + [DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong aio_getDL(); + } + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + var mode = m as Models.Mode.ProcessMode.ProcessMode; + Methods.aio_dial(NameList.TYPE_FILTERLOOPBACK, mode.Loopback ? "true" : "false"); + Methods.aio_dial(NameList.TYPE_FILTERTCP, mode.TCP ? "true" : "false"); + Methods.aio_dial(NameList.TYPE_FILTERUDP, mode.UDP ? "true" : "false"); + + Methods.aio_dial(NameList.TYPE_CLRNAME, ""); + Methods.aio_dial(NameList.TYPE_BYPNAME, AppDomain.CurrentDomain.BaseDirectory.Replace("\\", "\\\\")); + for (int i = 0; i < mode.HandleList.Count; i++) if (!Methods.aio_dial(NameList.TYPE_ADDNAME, mode.HandleList[i])) return false; + for (int i = 0; i < mode.BypassList.Count; i++) if (!Methods.aio_dial(NameList.TYPE_BYPNAME, mode.BypassList[i])) return false; + + Methods.aio_dial(NameList.TYPE_TCPLISN, Global.Config.Ports.Redir.ToString()); + Methods.aio_dial(NameList.TYPE_UDPLISN, Global.Config.Ports.Redir.ToString()); + + switch (s.Type) + { + case Models.Server.ServerType.Socks: + { + var node = s as Models.Server.Socks.Socks; + Methods.aio_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_TCPHOST, $"{node.Resolve()}:{node.Port}"); + Methods.aio_dial(NameList.TYPE_UDPHOST, $"{node.Resolve()}:{node.Port}"); + + if (!String.IsNullOrEmpty(node.Username)) + { + Methods.aio_dial(NameList.TYPE_TCPUSER, node.Username); + Methods.aio_dial(NameList.TYPE_UDPUSER, node.Username); + } + + if (!String.IsNullOrEmpty(node.Password)) + { + Methods.aio_dial(NameList.TYPE_TCPPASS, node.Password); + Methods.aio_dial(NameList.TYPE_UDPPASS, node.Password); + } + } + break; + case Models.Server.ServerType.Shadowsocks: + { + var node = s as Models.Server.Shadowsocks.Shadowsocks; + if (String.IsNullOrEmpty(node.OBFS)) + { + Methods.aio_dial(NameList.TYPE_TCPTYPE, "Shadowsocks"); + Methods.aio_dial(NameList.TYPE_UDPTYPE, "Shadowsocks"); + Methods.aio_dial(NameList.TYPE_TCPHOST, $"{node.Resolve()}:{node.Port}"); + Methods.aio_dial(NameList.TYPE_UDPHOST, $"{node.Resolve()}:{node.Port}"); + Methods.aio_dial(NameList.TYPE_TCPPASS, node.Passwd); + Methods.aio_dial(NameList.TYPE_UDPPASS, node.Passwd); + Methods.aio_dial(NameList.TYPE_TCPMETH, node.Method); + Methods.aio_dial(NameList.TYPE_UDPMETH, node.Method); + } + else + { + Methods.aio_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + Methods.aio_dial(NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + } + } + break; + default: + { + Methods.aio_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.aio_dial(NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + Methods.aio_dial(NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + } + break; + } + + return Methods.aio_init(); + } + + public bool Delete() + { + return Methods.aio_free(); + } + } +} diff --git a/Netch/Controllers/Mode/ShareController.cs b/Netch/Controllers/Mode/ShareController.cs new file mode 100644 index 00000000..aadfb275 --- /dev/null +++ b/Netch/Controllers/Mode/ShareController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Mode +{ + public class ShareController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Controllers/Mode/TapController.cs b/Netch/Controllers/Mode/TapController.cs new file mode 100644 index 00000000..297b2f6f --- /dev/null +++ b/Netch/Controllers/Mode/TapController.cs @@ -0,0 +1,235 @@ +using System; +using System.IO; +using System.Linq; +using System.Management; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Netch.Controllers.Mode +{ + public class TapController : Interface.IController + { + private enum NameList : int + { + TYPE_BYPBIND, + TYPE_BYPLIST, + TYPE_DNSADDR, + TYPE_ADAPMTU, + TYPE_TCPREST, + TYPE_TCPTYPE, + TYPE_TCPHOST, + TYPE_TCPUSER, + TYPE_TCPPASS, + TYPE_TCPMETH, + TYPE_TCPPROT, + TYPE_TCPPRPA, + TYPE_TCPOBFS, + TYPE_TCPOBPA, + TYPE_UDPREST, + TYPE_UDPTYPE, + TYPE_UDPHOST, + TYPE_UDPUSER, + TYPE_UDPPASS, + TYPE_UDPMETH, + TYPE_UDPPROT, + TYPE_UDPPRPA, + TYPE_UDPOBFS, + TYPE_UDPOBPA + } + + private static class Methods + { + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tap_dial(NameList name, string value); + + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tap_init(); + + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tap_free(); + + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern string tap_name(); + + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong tap_getUP(); + + [DllImport("tap2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong tap_getDL(); + } + + private Tools.TunTap.Outbound Outbound = new(); + private Interface.IController DNSController; + + private bool AssignInterface() + { + var index = Utils.RouteHelper.GetInterfaceIndexByDescription(Methods.tap_name()); + + var address = Global.Config.TunMode.Network.Split('/')[0]; + var netmask = byte.Parse(Global.Config.TunMode.Network.Split('/')[1]); + if (!Utils.RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork, address, netmask, index)) + { + return false; + } + + NetworkInterface adapter = Utils.RouteHelper.GetInterfaceByIndex(index); + if (adapter == null) + { + return false; + } + + using (var wmi = new ManagementClass("Win32_NetworkAdapterConfiguration")) + { + using var ins = wmi.GetInstances(); + var ada = ins.Cast().First(m => m["Description"].ToString() == adapter.Description); + + var dns = new[] { "127.0.0.1" }; + if (Global.Config.TunMode.DNS != "aiodns") + { + dns[0] = Global.Config.TunMode.DNS; + } + + using var ord = wmi.GetMethodParameters("SetDNSServerSearchOrder"); + ord["DNSServerSearchOrder"] = dns; + + ada.InvokeMethod("SetDNSServerSearchOrder", ord, null); + } + + return true; + } + + private bool CreateServerRoute(Models.Server.Server s) + { + var addr = Utils.DNS.Fetch(s.Host); + if (addr == IPAddress.Any) + { + return false; + } + + if (addr.AddressFamily == AddressFamily.InterNetworkV6) + { + return true; + } + + return Utils.RouteHelper.CreateRoute(AddressFamily.InterNetwork, addr.ToString(), 32, this.Outbound.Gateway.ToString(), this.Outbound.Index); + } + + private bool CreateHandleRoute(Models.Mode.TunMode.TunMode mode) + { + var index = Utils.RouteHelper.GetInterfaceIndexByDescription(Methods.tap_name()); + + for (int i = 0; i < mode.HandleList.Count; i++) + { + var address = mode.HandleList[i].Split('/')[0]; + var netmask = byte.Parse(mode.HandleList[i].Split('/')[1]); + + Utils.RouteHelper.CreateRoute(AddressFamily.InterNetwork, address, netmask, Global.Config.TunMode.Gateway, index); + } + + return true; + } + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + if (!this.Outbound.Get()) + { + return false; + } + + Methods.tap_dial(NameList.TYPE_BYPBIND, this.Outbound.Address.ToString()); + + var mode = m as Models.Mode.TunMode.TunMode; + if (mode.BypassList.Count > 0) + { + if (File.Exists("ipcidr.txt")) + { + File.Delete("ipcidr.txt"); + } + File.WriteAllLines("ipcidr.txt", mode.BypassList); + + Methods.tap_dial(NameList.TYPE_BYPLIST, "ipcidr.txt"); + } + else + { + Methods.tap_dial(NameList.TYPE_BYPLIST, "disabled"); + } + + Methods.tap_dial(NameList.TYPE_DNSADDR, (Global.Config.TunMode.DNS == "aiodns") ? "127.0.0.1" : Global.Config.TunMode.DNS); + Methods.tap_dial(NameList.TYPE_TCPREST, ""); + Methods.tap_dial(NameList.TYPE_UDPREST, ""); + + switch (s.Type) + { + case Models.Server.ServerType.Socks: + { + var node = s as Models.Server.Socks.Socks; + + Methods.tap_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.tap_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.tap_dial(NameList.TYPE_TCPHOST, $"{node.Resolve()}:{node.Port}"); + Methods.tap_dial(NameList.TYPE_UDPHOST, $"{node.Resolve()}:{node.Port}"); + + if (!String.IsNullOrEmpty(node.Username)) + { + Methods.tap_dial(NameList.TYPE_TCPUSER, node.Username); + Methods.tap_dial(NameList.TYPE_UDPUSER, node.Username); + } + + if (!String.IsNullOrEmpty(node.Password)) + { + Methods.tap_dial(NameList.TYPE_TCPPASS, node.Password); + Methods.tap_dial(NameList.TYPE_UDPPASS, node.Password); + } + } + break; + default: + Methods.tap_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.tap_dial(NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + Methods.tap_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.tap_dial(NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + break; + } + + if (!Methods.tap_init()) + { + return false; + } + + this.DNSController = new Other.DNS.AioDNSController(); + if (!this.DNSController.Create(s, m)) + { + return false; + } + + if (!this.AssignInterface()) + { + return false; + } + + if (!this.CreateServerRoute(s)) + { + return false; + } + + if (!this.CreateHandleRoute(mode)) + { + return false; + } + + if (File.Exists("ipcidr.txt")) + { + File.Delete("ipcidr.txt"); + } + return true; + } + + public bool Delete() + { + this.DNSController?.Delete(); + + return Methods.tap_free(); + } + } +} diff --git a/Netch/Controllers/Mode/TunController.cs b/Netch/Controllers/Mode/TunController.cs new file mode 100644 index 00000000..80262d02 --- /dev/null +++ b/Netch/Controllers/Mode/TunController.cs @@ -0,0 +1,235 @@ +using System; +using System.IO; +using System.Linq; +using System.Management; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Netch.Controllers.Mode +{ + public class TunController : Interface.IController + { + private enum NameList : int + { + TYPE_BYPBIND, + TYPE_BYPLIST, + TYPE_DNSADDR, + TYPE_ADAPMTU, + TYPE_TCPREST, + TYPE_TCPTYPE, + TYPE_TCPHOST, + TYPE_TCPUSER, + TYPE_TCPPASS, + TYPE_TCPMETH, + TYPE_TCPPROT, + TYPE_TCPPRPA, + TYPE_TCPOBFS, + TYPE_TCPOBPA, + TYPE_UDPREST, + TYPE_UDPTYPE, + TYPE_UDPHOST, + TYPE_UDPUSER, + TYPE_UDPPASS, + TYPE_UDPMETH, + TYPE_UDPPROT, + TYPE_UDPPRPA, + TYPE_UDPOBFS, + TYPE_UDPOBPA + } + + private static class Methods + { + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tun_dial(NameList name, string value); + + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tun_init(); + + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool tun_free(); + + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong tun_luid(); + + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong tun_getUP(); + + [DllImport("tun2socks.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong tun_getDL(); + } + + private Tools.TunTap.Outbound Outbound = new(); + private Interface.IController DNSController; + + private bool AssignInterface() + { + var index = Utils.RouteHelper.ConvertLuidToIndex(Methods.tun_luid()); + + var address = Global.Config.TunMode.Network.Split('/')[0]; + var netmask = byte.Parse(Global.Config.TunMode.Network.Split('/')[1]); + if (!Utils.RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork, address, netmask, index)) + { + return false; + } + + NetworkInterface adapter = Utils.RouteHelper.GetInterfaceByIndex(index); + if (adapter == null) + { + return false; + } + + using (var wmi = new ManagementClass("Win32_NetworkAdapterConfiguration")) + { + using var ins = wmi.GetInstances(); + var ada = ins.Cast().First(m => m["Description"].ToString() == adapter.Description); + + var dns = new[] { "127.0.0.1" }; + if (Global.Config.TunMode.DNS != "aiodns") + { + dns[0] = Global.Config.TunMode.DNS; + } + + using var ord = wmi.GetMethodParameters("SetDNSServerSearchOrder"); + ord["DNSServerSearchOrder"] = dns; + + ada.InvokeMethod("SetDNSServerSearchOrder", ord, null); + } + + return true; + } + + private bool CreateServerRoute(Models.Server.Server s) + { + var addr = Utils.DNS.Fetch(s.Host); + if (addr == IPAddress.Any) + { + return false; + } + + if (addr.AddressFamily == AddressFamily.InterNetworkV6) + { + return true; + } + + return Utils.RouteHelper.CreateRoute(AddressFamily.InterNetwork, addr.ToString(), 32, this.Outbound.Gateway.ToString(), this.Outbound.Index); + } + + private bool CreateHandleRoute(Models.Mode.TunMode.TunMode mode) + { + var index = Utils.RouteHelper.ConvertLuidToIndex(Methods.tun_luid()); + + for (int i = 0; i < mode.HandleList.Count; i++) + { + var address = mode.HandleList[i].Split('/')[0]; + var netmask = byte.Parse(mode.HandleList[i].Split('/')[1]); + + Utils.RouteHelper.CreateRoute(AddressFamily.InterNetwork, address, netmask, Global.Config.TunMode.Gateway, index); + } + + return true; + } + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + if (!this.Outbound.Get()) + { + return false; + } + + Methods.tun_dial(NameList.TYPE_BYPBIND, this.Outbound.Address.ToString()); + + var mode = m as Models.Mode.TunMode.TunMode; + if (mode.BypassList.Count > 0) + { + if (File.Exists("ipcidr.txt")) + { + File.Delete("ipcidr.txt"); + } + File.WriteAllLines("ipcidr.txt", mode.BypassList); + + Methods.tun_dial(NameList.TYPE_BYPLIST, "ipcidr.txt"); + } + else + { + Methods.tun_dial(NameList.TYPE_BYPLIST, "disabled"); + } + + Methods.tun_dial(NameList.TYPE_DNSADDR, (Global.Config.TunMode.DNS == "aiodns") ? "127.0.0.1" : Global.Config.TunMode.DNS); + Methods.tun_dial(NameList.TYPE_TCPREST, ""); + Methods.tun_dial(NameList.TYPE_UDPREST, ""); + + switch (s.Type) + { + case Models.Server.ServerType.Socks: + { + var node = s as Models.Server.Socks.Socks; + + Methods.tun_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.tun_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.tun_dial(NameList.TYPE_TCPHOST, $"{node.Resolve()}:{node.Port}"); + Methods.tun_dial(NameList.TYPE_UDPHOST, $"{node.Resolve()}:{node.Port}"); + + if (!String.IsNullOrEmpty(node.Username)) + { + Methods.tun_dial(NameList.TYPE_TCPUSER, node.Username); + Methods.tun_dial(NameList.TYPE_UDPUSER, node.Username); + } + + if (!String.IsNullOrEmpty(node.Password)) + { + Methods.tun_dial(NameList.TYPE_TCPPASS, node.Password); + Methods.tun_dial(NameList.TYPE_UDPPASS, node.Password); + } + } + break; + default: + Methods.tun_dial(NameList.TYPE_TCPTYPE, "Socks"); + Methods.tun_dial(NameList.TYPE_TCPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + Methods.tun_dial(NameList.TYPE_UDPTYPE, "Socks"); + Methods.tun_dial(NameList.TYPE_UDPHOST, $"127.0.0.1:{Global.Config.Ports.Socks}"); + break; + } + + if (!Methods.tun_init()) + { + return false; + } + + this.DNSController = new Other.DNS.AioDNSController(); + if (!this.DNSController.Create(s, m)) + { + return false; + } + + if (!this.AssignInterface()) + { + return false; + } + + if (!this.CreateServerRoute(s)) + { + return false; + } + + if (!this.CreateHandleRoute(mode)) + { + return false; + } + + if (File.Exists("ipcidr.txt")) + { + File.Delete("ipcidr.txt"); + } + return true; + } + + public bool Delete() + { + this.DNSController?.Delete(); + + return Methods.tun_free(); + } + } +} diff --git a/Netch/Controllers/Mode/WebController.cs b/Netch/Controllers/Mode/WebController.cs new file mode 100644 index 00000000..61ec1e7c --- /dev/null +++ b/Netch/Controllers/Mode/WebController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Mode +{ + public class WebController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Controllers/Mode/WmpController.cs b/Netch/Controllers/Mode/WmpController.cs new file mode 100644 index 00000000..c534a1c1 --- /dev/null +++ b/Netch/Controllers/Mode/WmpController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Mode +{ + public class WmpController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Controllers/Other/DNS/AioDNSController.cs b/Netch/Controllers/Other/DNS/AioDNSController.cs new file mode 100644 index 00000000..3490723e --- /dev/null +++ b/Netch/Controllers/Other/DNS/AioDNSController.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace Netch.Controllers.Other.DNS +{ + public class AioDNSController : Interface.IController + { + private enum NameList : int + { + TYPE_REST, + TYPE_ADDR, + TYPE_LIST, + TYPE_CDNS, + TYPE_ODNS + } + + private static class Methods + { + [DllImport("aiodns.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool aiodns_dial(NameList name, string value); + + [DllImport("aiodns.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool aiodns_init(); + + [DllImport("aiodns.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern void aiodns_free(); + } + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + Methods.aiodns_dial(NameList.TYPE_REST, ""); + Methods.aiodns_dial(NameList.TYPE_ADDR, ":53"); + Methods.aiodns_dial(NameList.TYPE_LIST, "Bin\\aiodns.conf"); + Methods.aiodns_dial(NameList.TYPE_CDNS, Global.Config.AioDNS.ChinaDNS); + Methods.aiodns_dial(NameList.TYPE_ODNS, Global.Config.AioDNS.OtherDNS); + + return Methods.aiodns_init(); + } + + public bool Delete() + { + Methods.aiodns_free(); + + return true; + } + } +} diff --git a/Netch/Controllers/Server/SRController.cs b/Netch/Controllers/Server/SRController.cs new file mode 100644 index 00000000..93d5a095 --- /dev/null +++ b/Netch/Controllers/Server/SRController.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Netch.Controllers.Server +{ + public class SRController : Interface.IController + { + private Tools.Guard Guard = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin\\ShadowsocksR.exe"), + WorkingDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin"), + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + }, + JudgmentStarted = new List() + { + "listening at" + }, + JudgmentStopped = new List() + { + "usage", + "invalid" + }, + AutoRestart = true + }; + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + var node = s as Models.Server.ShadowsocksR.ShadowsocksR; + + var sb = new StringBuilder(); + sb.Append($"-l {Global.Config.Ports.Socks} -s {node.Resolve()} -p {node.Port} -k '{node.Passwd}' -O {node.Prot} -o {node.OBFS} -t 30 -u --fast-open --no-delay"); + if (!String.IsNullOrEmpty(node.ProtParam)) sb.Append($" -G '{node.ProtParam}'"); + if (!String.IsNullOrEmpty(node.OBFSParam)) sb.Append($" -g '{node.OBFSParam}'"); + + this.Guard.StartInfo.Arguments = sb.ToString(); + return this.Guard.Create(); + } + + public bool Delete() + { + this.Guard.Delete(); + + return true; + } + } +} diff --git a/Netch/Controllers/Server/SSController.cs b/Netch/Controllers/Server/SSController.cs new file mode 100644 index 00000000..852be4a5 --- /dev/null +++ b/Netch/Controllers/Server/SSController.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Netch.Controllers.Server +{ + public class SSController : Interface.IController + { + private Tools.Guard Guard = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin\\Shadowsocks.exe"), + WorkingDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin"), + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + }, + JudgmentStarted = new List() + { + "listening at" + }, + JudgmentStopped = new List() + { + "usage", + "invalid", + "plugin service exit unexpectedly" + }, + AutoRestart = true + }; + + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + var node = s as Models.Server.Shadowsocks.Shadowsocks; + + var sb = new StringBuilder(); + sb.Append($"-l {Global.Config.Ports.Socks} -s {node.Resolve()} -p {node.Port} -k '{node.Passwd}' -t 30 -u --fast-open --no-delay"); + if (!String.IsNullOrEmpty(node.OBFS)) sb.Append($" --plugin '{node.OBFS}' --plugin-opts '{node.OBFSParam}'"); + + this.Guard.StartInfo.Arguments = sb.ToString(); + return this.Guard.Create(); + } + + public bool Delete() + { + this.Guard.Delete(); + + return true; + } + } +} diff --git a/Netch/Controllers/Server/TRController.cs b/Netch/Controllers/Server/TRController.cs new file mode 100644 index 00000000..995f6064 --- /dev/null +++ b/Netch/Controllers/Server/TRController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Server +{ + public class TRController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Controllers/Server/VLController.cs b/Netch/Controllers/Server/VLController.cs new file mode 100644 index 00000000..16f2c60a --- /dev/null +++ b/Netch/Controllers/Server/VLController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Server +{ + public class VLController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Controllers/Server/VMController.cs b/Netch/Controllers/Server/VMController.cs new file mode 100644 index 00000000..bdbb4f32 --- /dev/null +++ b/Netch/Controllers/Server/VMController.cs @@ -0,0 +1,15 @@ +namespace Netch.Controllers.Server +{ + public class VMController : Interface.IController + { + public bool Create(Models.Server.Server s, Models.Mode.Mode m) + { + throw new System.NotImplementedException(); + } + + public bool Delete() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Netch/Forms/MainWindow.xaml b/Netch/Forms/MainWindow.xaml new file mode 100644 index 00000000..df05c54c --- /dev/null +++ b/Netch/Forms/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Netch/Forms/MainWindow.xaml.cs b/Netch/Forms/MainWindow.xaml.cs new file mode 100644 index 00000000..e79f1376 --- /dev/null +++ b/Netch/Forms/MainWindow.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows; + +namespace Netch.Forms +{ + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/Netch/Global.cs b/Netch/Global.cs new file mode 100644 index 00000000..081e2ad6 --- /dev/null +++ b/Netch/Global.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Netch +{ + public static class Global + { + /// + /// 版本号 + /// + public static readonly string VerCode = "2.0.0"; + + /// + /// 日志记录 + /// + public static Tools.Logger Logger = new Tools.Logger() { SavePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs\\Netch.log") }; + + /// + /// 配置文件 + /// + public static Models.Config.Config Config; + + /// + /// 节点列表 + /// + public static List NodeList; + + /// + /// 模式列表 + /// + public static List ModeList; + } +} diff --git a/Netch/Models/Config/AioDNS.cs b/Netch/Models/Config/AioDNS.cs new file mode 100644 index 00000000..6e6ed104 --- /dev/null +++ b/Netch/Models/Config/AioDNS.cs @@ -0,0 +1,15 @@ +namespace Netch.Models.Config +{ + public class AioDNS + { + /// + /// 国内 DNS 地址 + /// + public string ChinaDNS = "tcp://119.29.29.29:53"; + + /// + /// 国外 DNS 地址 + /// + public string OtherDNS = "tls://1.1.1.1:853"; + } +} diff --git a/Netch/Models/Config/Config.cs b/Netch/Models/Config/Config.cs new file mode 100644 index 00000000..fbf0e65b --- /dev/null +++ b/Netch/Models/Config/Config.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; + +namespace Netch.Models.Config +{ + public class Config + { + /// + /// 配置 版本 + /// + [Newtonsoft.Json.JsonProperty("verCode")] + public int VerCode = 1; + + /// + /// 通用 配置 + /// + [Newtonsoft.Json.JsonProperty("generic")] + public Generic Generic = new(); + + /// + /// 端口 配置 + /// + [Newtonsoft.Json.JsonProperty("ports")] + public Ports Ports = new(); + + /// + /// ProcessMode 配置 + /// + [Newtonsoft.Json.JsonProperty("processmode")] + public ProcessMode ProcessMode = new(); + + /// + /// ShareMode 配置 + /// + [Newtonsoft.Json.JsonProperty("sharemode")] + public ShareMode ShareMode = new(); + + /// + /// TapMode 配置 + /// + [Newtonsoft.Json.JsonProperty("tapmode")] + public TapMode TapMode = new(); + + /// + /// TunMode 配置 + /// + [Newtonsoft.Json.JsonProperty("tunmode")] + public TunMode TunMode = new(); + + /// + /// AioDNS 配置 + /// + [Newtonsoft.Json.JsonProperty("aiodns")] + public AioDNS AioDNS = new(); + + /// + /// V2Ray 配置 + /// + [Newtonsoft.Json.JsonProperty("v2ray")] + public V2Ray V2Ray = new(); + + /// + /// STUN 配置 + /// + [Newtonsoft.Json.JsonProperty("stun")] + public STUN STUN = new(); + + /// + /// 订阅链接 + /// + [Newtonsoft.Json.JsonProperty("subscriptions")] + public List Subscriptions = new(); + } +} diff --git a/Netch/Models/Config/Generic.cs b/Netch/Models/Config/Generic.cs new file mode 100644 index 00000000..0ec3ac1f --- /dev/null +++ b/Netch/Models/Config/Generic.cs @@ -0,0 +1,15 @@ +namespace Netch.Models.Config +{ + public class Generic + { + /// + /// 检查 Unstable 更新 + /// + public bool Unstable = false; + + /// + /// 使用 ICMP 测试延迟 + /// + public bool ICMPing = true; + } +} diff --git a/Netch/Models/Config/Ports.cs b/Netch/Models/Config/Ports.cs new file mode 100644 index 00000000..6fbe6c41 --- /dev/null +++ b/Netch/Models/Config/Ports.cs @@ -0,0 +1,23 @@ +namespace Netch.Models.Config +{ + public class Ports + { + /// + /// Socks 端口 + /// + [Newtonsoft.Json.JsonProperty("socks")] + public int Socks = 2081; + + /// + /// Mixed 端口 + /// + [Newtonsoft.Json.JsonProperty("mixed")] + public int Mixed = 2082; + + /// + /// Redir 端口 + /// + [Newtonsoft.Json.JsonProperty("redir")] + public int Redir = 2083; + } +} diff --git a/Netch/Models/Config/ProcessMode.cs b/Netch/Models/Config/ProcessMode.cs new file mode 100644 index 00000000..1037739b --- /dev/null +++ b/Netch/Models/Config/ProcessMode.cs @@ -0,0 +1,11 @@ +namespace Netch.Models.Config +{ + public class ProcessMode + { + /// + /// DNS + /// + [Newtonsoft.Json.JsonProperty("dns")] + public string DNS = "1.1.1.1:53"; + } +} diff --git a/Netch/Models/Config/STUN.cs b/Netch/Models/Config/STUN.cs new file mode 100644 index 00000000..6c7ecb38 --- /dev/null +++ b/Netch/Models/Config/STUN.cs @@ -0,0 +1,11 @@ +namespace Netch.Models.Config +{ + public class STUN + { + /// + /// 主机名 + /// + [Newtonsoft.Json.JsonProperty("host")] + public string Host = "stun.ekiga.net"; + } +} diff --git a/Netch/Models/Config/ShareMode.cs b/Netch/Models/Config/ShareMode.cs new file mode 100644 index 00000000..0114ab29 --- /dev/null +++ b/Netch/Models/Config/ShareMode.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace Netch.Models.Config +{ + public class ShareMode + { + /// + /// 硬件地址(用于 ARP 回复) + /// + /// CuteCR + /// 43:75:74:65:43:52 + /// + /// NetchX + /// 4e:65:74:63:68:58 + /// + [Newtonsoft.Json.JsonProperty("hardware")] + public string Hardware = "4e:65:74:63:68:58"; + + /// + /// 地址 + /// + [Newtonsoft.Json.JsonProperty("network")] + public string Network = "100.64.0.0/24"; + + /// + /// 网关 + /// + [Newtonsoft.Json.JsonProperty("gateway")] + public string Gateway = "100.64.0.1"; + + /// + /// DNS + /// + [Newtonsoft.Json.JsonProperty("dns")] + public string DNS = "aiodns"; + + /// + /// 网卡名(默认自动检测) + /// + public string EthernetName = "auto"; + + /// + /// 绕过 IP 地址 + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassIPs = new(); + } +} diff --git a/Netch/Models/Config/Subscription.cs b/Netch/Models/Config/Subscription.cs new file mode 100644 index 00000000..c21e3f91 --- /dev/null +++ b/Netch/Models/Config/Subscription.cs @@ -0,0 +1,23 @@ +namespace Netch.Models.Config +{ + public class Subscription + { + /// + /// 启用 / 禁用 + /// + [Newtonsoft.Json.JsonProperty("enabled")] + public bool Checked = true; + + /// + /// 备注 + /// + [Newtonsoft.Json.JsonProperty("remark")] + public string Remark; + + /// + /// 链接 + /// + [Newtonsoft.Json.JsonProperty("address")] + public string Link; + } +} diff --git a/Netch/Models/Config/TapMode.cs b/Netch/Models/Config/TapMode.cs new file mode 100644 index 00000000..0e52df8b --- /dev/null +++ b/Netch/Models/Config/TapMode.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Netch.Models.Config +{ + public class TapMode + { + /// + /// 地址 + /// + [Newtonsoft.Json.JsonProperty("network")] + public string Network = "100.64.0.100/24"; + + /// + /// 网关 + /// + [Newtonsoft.Json.JsonProperty("gateway")] + public string Gateway = "100.64.0.1"; + + /// + /// DNS + /// + [Newtonsoft.Json.JsonProperty("dns")] + public string DNS = "aiodns"; + + /// + /// 绕过 IP 地址 + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassIPs = new(); + } +} diff --git a/Netch/Models/Config/TunMode.cs b/Netch/Models/Config/TunMode.cs new file mode 100644 index 00000000..df15a54c --- /dev/null +++ b/Netch/Models/Config/TunMode.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Netch.Models.Config +{ + public class TunMode + { + /// + /// 地址 + /// + [Newtonsoft.Json.JsonProperty("network")] + public string Network = "100.64.0.100/24"; + + /// + /// 网关 + /// + [Newtonsoft.Json.JsonProperty("gateway")] + public string Gateway = "100.64.0.1"; + + /// + /// DNS + /// + [Newtonsoft.Json.JsonProperty("dns")] + public string DNS = "aiodns"; + + /// + /// 绕过 IP 地址 + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassIPs = new(); + } +} diff --git a/Netch/Models/Config/V2Ray.cs b/Netch/Models/Config/V2Ray.cs new file mode 100644 index 00000000..68853b7d --- /dev/null +++ b/Netch/Models/Config/V2Ray.cs @@ -0,0 +1,63 @@ +namespace Netch.Models.Config +{ + public class V2Ray + { + /// + /// FullCone 支持(需要 xray-core 服务端版本 v1.3.0+) + /// + public bool FullCone = false; + + /// + /// 跳过证书认证 + /// + public bool Insecure = false; + + /// + /// 多路复用 + /// + public bool Multiplex = false; + + /// + /// KCP 设定 + /// + public V2RayKCP KCP = new(); + } + + public class V2RayKCP + { + /// + /// MTU + /// + public int MTU = 1450; + + /// + /// TTI + /// + public int TTI = 50; + + /// + /// 上行链路流量 + /// + public int UPC = 5; + + /// + /// 下行链路流量 + /// + public int DLC = 20; + + /// + /// 读取缓冲区大小(MB) + /// + public int RBS = 2; + + /// + /// 写入缓冲区大小(MB) + /// + public int WBS = 2; + + /// + /// 拥塞控制 + /// + public bool BBR = false; + } +} diff --git a/Netch/Models/GitHub/Asset.cs b/Netch/Models/GitHub/Asset.cs new file mode 100644 index 00000000..96e2dd01 --- /dev/null +++ b/Netch/Models/GitHub/Asset.cs @@ -0,0 +1,23 @@ +namespace Netch.Models.GitHub +{ + public class Asset + { + /// + /// name + /// + [Newtonsoft.Json.JsonProperty("name")] + public string Name; + + /// + /// browser_download_url + /// + [Newtonsoft.Json.JsonProperty("browser_download_url")] + public string URL; + + /// + /// size + /// + [Newtonsoft.Json.JsonProperty("size")] + public ulong Size; + } +} diff --git a/Netch/Models/GitHub/Release.cs b/Netch/Models/GitHub/Release.cs new file mode 100644 index 00000000..5c0e5e0a --- /dev/null +++ b/Netch/Models/GitHub/Release.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace Netch.Models.GitHub +{ + /// + /// https://api.github.com/repos/{owner}/{repo}/releases + /// + public class Release + { + /// + /// id + /// + [Newtonsoft.Json.JsonProperty("id")] + public int ID; + + /// + /// html_url + /// + [Newtonsoft.Json.JsonProperty("html_url")] + public string URL; + + /// + /// tag_name + /// + [Newtonsoft.Json.JsonProperty("tag_name")] + public string VerCode; + + /// + /// draft + /// + [Newtonsoft.Json.JsonProperty("draft")] + public bool Draft; + + /// + /// prerelease + /// + [Newtonsoft.Json.JsonProperty("prerelease")] + public bool Unstable; + + /// + /// assets + /// + [Newtonsoft.Json.JsonProperty("assets")] + public List Files; + } +} diff --git a/Netch/Models/Mode/Mode.cs b/Netch/Models/Mode/Mode.cs new file mode 100644 index 00000000..e04ffe8a --- /dev/null +++ b/Netch/Models/Mode/Mode.cs @@ -0,0 +1,19 @@ +namespace Netch.Models.Mode +{ + public class Mode + { + /// + /// 类型 + /// + [Newtonsoft.Json.JsonProperty("type")] + public ModeType Type; + + /// + /// 备注 + /// + [Newtonsoft.Json.JsonProperty("remark")] + public string Remark; + + public override string ToString() => $"[{((int)this.Type) + 1}] {this.Remark}"; + } +} diff --git a/Netch/Models/Mode/ModeType.cs b/Netch/Models/Mode/ModeType.cs new file mode 100644 index 00000000..3fcb583a --- /dev/null +++ b/Netch/Models/Mode/ModeType.cs @@ -0,0 +1,35 @@ +namespace Netch.Models.Mode +{ + public enum ModeType : int + { + /// + /// 进程代理 + /// + ProcessMode, + + /// + /// 网络共享 + /// + ShareMode, + + /// + /// 网卡代理 + /// + TapMode, + + /// + /// 网卡代理 + /// + TunMode, + + /// + /// 网页代理 + /// + WebMode, + + /// + /// 代理转发 + /// + WmpMode + } +} diff --git a/Netch/Models/Mode/ProcessMode/ProcessMode.cs b/Netch/Models/Mode/ProcessMode/ProcessMode.cs new file mode 100644 index 00000000..fbb0fa47 --- /dev/null +++ b/Netch/Models/Mode/ProcessMode/ProcessMode.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace Netch.Models.Mode.ProcessMode +{ + public class ProcessMode : Mode + { + public ProcessMode() + { + this.Type = ModeType.ProcessMode; + } + + /// + /// 过滤 IPv4 + IPv6 环路流量 + /// + [Newtonsoft.Json.JsonProperty("filterLoopback")] + public bool Loopback = false; + + /// + /// 过滤 ICMP 流量(伪造 ICMP 回复) + /// + [Newtonsoft.Json.JsonProperty("filterICMP")] + public bool ICMP = true; + + /// + /// 过滤 TCP 流量 + /// + [Newtonsoft.Json.JsonProperty("filterTCP")] + public bool TCP = true; + + /// + /// 过滤 UDP 流量 + /// + [Newtonsoft.Json.JsonProperty("filterUDP")] + public bool UDP = true; + + /// + /// 绕过列表 + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassList; + + /// + /// 代理列表 + /// + [Newtonsoft.Json.JsonProperty("handle")] + public List HandleList; + } +} diff --git a/Netch/Models/Mode/ShareMode/ShareMode.cs b/Netch/Models/Mode/ShareMode/ShareMode.cs new file mode 100644 index 00000000..480f521f --- /dev/null +++ b/Netch/Models/Mode/ShareMode/ShareMode.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Netch.Models.Mode.ShareMode +{ + public class ShareMode : Mode + { + public ShareMode() + { + this.Type = ModeType.ShareMode; + } + + /// + /// 绕过列表(IP CIDR) + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassList; + } +} diff --git a/Netch/Models/Mode/TapMode/TapMode.cs b/Netch/Models/Mode/TapMode/TapMode.cs new file mode 100644 index 00000000..b754ab5e --- /dev/null +++ b/Netch/Models/Mode/TapMode/TapMode.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Netch.Models.Mode.TapMode +{ + public class TapMode : Mode + { + public TapMode() + { + this.Type = ModeType.TapMode; + } + + /// + /// 绕过列表(IP CIDR) + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassList; + + /// + /// 代理列表(IP CIDR) + /// + [Newtonsoft.Json.JsonProperty("handle")] + public List HandleList; + } +} diff --git a/Netch/Models/Mode/TunMode/TunMode.cs b/Netch/Models/Mode/TunMode/TunMode.cs new file mode 100644 index 00000000..d378f2d3 --- /dev/null +++ b/Netch/Models/Mode/TunMode/TunMode.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Netch.Models.Mode.TunMode +{ + public class TunMode : Mode + { + public TunMode() + { + this.Type = ModeType.TunMode; + } + + /// + /// 绕过列表(IP CIDR) + /// + [Newtonsoft.Json.JsonProperty("bypass")] + public List BypassList; + + /// + /// 代理列表(IP CIDR) + /// + [Newtonsoft.Json.JsonProperty("handle")] + public List HandleList; + } +} diff --git a/Netch/Models/Mode/WebMode/WebMode.cs b/Netch/Models/Mode/WebMode/WebMode.cs new file mode 100644 index 00000000..e64f073a --- /dev/null +++ b/Netch/Models/Mode/WebMode/WebMode.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Netch.Models.Mode.WebMode +{ + public class WebMode : Mode + { + public WebMode() + { + this.Type = ModeType.WebMode; + } + + /// + /// 设置系统代理 + /// + [Newtonsoft.Json.JsonProperty("setSystemProxy")] + public bool SetSystemProxy; + + /// + /// 绕过域名后缀 + /// + [Newtonsoft.Json.JsonProperty("bypassDomainSuffix")] + public List BypassDomainSuffix; + + /// + /// 绕过 IP 地址 + /// + [Newtonsoft.Json.JsonProperty("bypassIPs")] + public List BypassIPs; + } +} diff --git a/Netch/Models/Mode/WmpMode/WmpMode.cs b/Netch/Models/Mode/WmpMode/WmpMode.cs new file mode 100644 index 00000000..b1e7cf45 --- /dev/null +++ b/Netch/Models/Mode/WmpMode/WmpMode.cs @@ -0,0 +1,30 @@ +namespace Netch.Models.Mode.WmpMode +{ + public class WmpMode : Mode + { + public WmpMode() + { + this.Type = ModeType.WmpMode; + } + + /// + /// 监听地址(为空则监听所有 IPv4 + IPv6 地址) + /// + public string ListenAddr; + + /// + /// 监听端口 + /// + public ushort ListenPort; + + /// + /// 远端地址 + /// + public string RemoteAddr; + + /// + /// 远端端口 + /// + public ushort RemotePort; + } +} diff --git a/Netch/Models/Server/Server.cs b/Netch/Models/Server/Server.cs new file mode 100644 index 00000000..8d99f4fa --- /dev/null +++ b/Netch/Models/Server/Server.cs @@ -0,0 +1,70 @@ +using System; +using System.Net; + +namespace Netch.Models.Server +{ + public class Server + { + /// + /// 类型 + /// + [Newtonsoft.Json.JsonProperty("type")] + public ServerType Type; + + /// + /// 备注 + /// + [Newtonsoft.Json.JsonProperty("remark")] + public string Remark; + + /// + /// 地址 + /// + [Newtonsoft.Json.JsonProperty("host")] + public string Host; + + /// + /// 端口 + /// + [Newtonsoft.Json.JsonProperty("port")] + public ushort Port; + + /// + /// 延迟 + /// + [Newtonsoft.Json.JsonIgnore] + public int Ping = -1; + + /// + /// 测试延迟 + /// + /// + public void TestPing() => this.Ping = Utils.Ping.Fetch(this); + + /// + /// 解析地址 + /// + /// + public string Resolve() => (Utils.DNS.Fetch(this.Host) != IPAddress.Any) ? Utils.DNS.Fetch(this.Host).ToString() : this.Host; + + /// + /// 获取备注 + /// + /// + public override string ToString() + { + string name = this.Type switch + { + ServerType.Socks => "S5", + ServerType.Shadowsocks => "SS", + ServerType.ShadowsocksR => "SR", + ServerType.Trojan => "TR", + ServerType.VLess => "VL", + ServerType.VMess => "VM", + _ => "UN", + }; + + return String.Format("[{0}] {1}", name, String.IsNullOrEmpty(this.Remark) ? $"{this.Host}:{this.Port}" : this.Remark); + } + } +} diff --git a/Netch/Models/Server/ServerList.cs b/Netch/Models/Server/ServerList.cs new file mode 100644 index 00000000..2f166473 --- /dev/null +++ b/Netch/Models/Server/ServerList.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Netch.Models.Server +{ + public class ServerList + { + /// + /// 群组 + /// + [Newtonsoft.Json.JsonProperty("name")] + public string Group; + + /// + /// 节点 + /// + [Newtonsoft.Json.JsonProperty("list")] + public List List; + } +} diff --git a/Netch/Models/Server/ServerType.cs b/Netch/Models/Server/ServerType.cs new file mode 100644 index 00000000..e5bfde2e --- /dev/null +++ b/Netch/Models/Server/ServerType.cs @@ -0,0 +1,35 @@ +namespace Netch.Models.Server +{ + public enum ServerType : int + { + /// + /// Socks5 + /// + Socks, + + /// + /// Shadowsocks + /// + Shadowsocks, + + /// + /// ShadowsocksR + /// + ShadowsocksR, + + /// + /// Trojan + /// + Trojan, + + /// + /// VLess + /// + VLess, + + /// + /// VMess + /// + VMess + } +} diff --git a/Netch/Models/Server/Shadowsocks/Global.cs b/Netch/Models/Server/Shadowsocks/Global.cs new file mode 100644 index 00000000..0877d760 --- /dev/null +++ b/Netch/Models/Server/Shadowsocks/Global.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Netch.Models.Server.Shadowsocks +{ + public static class Global + { + public static readonly List Methods = new List() + { + "bf-cfb", + "rc4-md5", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "salsa20", + "chacha20", + "chacha20-ietf", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + }; + } +} diff --git a/Netch/Models/Server/Shadowsocks/Shadowsocks.cs b/Netch/Models/Server/Shadowsocks/Shadowsocks.cs new file mode 100644 index 00000000..e1a2b919 --- /dev/null +++ b/Netch/Models/Server/Shadowsocks/Shadowsocks.cs @@ -0,0 +1,125 @@ +using System; +using System.Text.RegularExpressions; +using System.Web; + +namespace Netch.Models.Server.Shadowsocks +{ + public class Shadowsocks : Server + { + public Shadowsocks() + { + this.Type = ServerType.Shadowsocks; + } + + /// + /// 密码 + /// + [Newtonsoft.Json.JsonProperty("passwd")] + public string Passwd; + + /// + /// 加密 + /// + [Newtonsoft.Json.JsonProperty("method")] + public string Method; + + /// + /// 插件 + /// + [Newtonsoft.Json.JsonProperty("obfs")] + public string OBFS; + + /// + /// 插件参数 + /// + [Newtonsoft.Json.JsonProperty("obfsparam")] + public string OBFSParam; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + if (link.Contains("#")) + { + this.Remark = HttpUtility.UrlDecode(link.Split('#')[1]); + + link = link.Split('#')[0]; + } + + if (link.Contains("?")) + { + var finder = new Regex(@"^(?.+?)\?(.+)$"); + + var matches = finder.Match(link); + if (matches.Success) + { + var plugin = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(link).Query).Get("plugin")); + if (plugin != null) + { + var obfs = plugin.Substring(0, plugin.IndexOf(";")); + var opts = plugin.Substring(plugin.IndexOf(";") + 1); + switch (obfs) + { + case "obfs-local": + case "simple-obfs": + case "simple-obfs-tls": + obfs = "simple-obfs"; + break; + } + + this.OBFS = obfs; + this.OBFSParam = opts; + } + + link = matches.Groups["data"].Value; + } + else + { + return false; + } + } + + if (link.Contains("@")) + { + var finder = new Regex(@"^ss://(?.+?)@(?.+):(?\d+)"); + var parser = new Regex(@"^(?.+?):(?.+)$"); + + var matches = finder.Match(link); + if (!matches.Success) + { + return false; + } + + this.Host = matches.Groups["server"].Value; + + if (ushort.TryParse(matches.Groups["port"].Value, out var result)) + { + this.Port = result; + } + else + { + return false; + } + + matches = parser.Match(Utils.Base64.Decode.URLSafe(matches.Groups["base64"].Value)); + if (!matches.Success) + { + return false; + } + + this.Passwd = matches.Groups["password"].Value; + this.Method = matches.Groups["method"].Value; + } + else + { + return false; + } + + this.Method = this.Method.ToLower(); + return Global.Methods.Contains(this.Method); + } + } +} diff --git a/Netch/Models/Server/ShadowsocksR/Global.cs b/Netch/Models/Server/ShadowsocksR/Global.cs new file mode 100644 index 00000000..b317d2bf --- /dev/null +++ b/Netch/Models/Server/ShadowsocksR/Global.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Netch.Models.Server.ShadowsocksR +{ + public static class Global + { + public static readonly List Methods = new List() + { + "rc4", + "bf-cfb", + "des-cfb", + "rc2-cfb", + "rc4-md5", + "idea-cfb", + "seed-cfb", + "cast5-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "chacha20", + "chacha20-ietf" + }; + + public static readonly List Prots = new List() + { + "origin", + "auth_sha1_v4", + "auth_aes128_md5", + "auth_aes128_sha1", + "auth_chain_a", + }; + + public static readonly List OBFSs = new List() + { + "plain", + "http_post", + "http_simple", + "tls1.2_ticket_auth" + }; + } +} diff --git a/Netch/Models/Server/ShadowsocksR/ShadowsocksR.cs b/Netch/Models/Server/ShadowsocksR/ShadowsocksR.cs new file mode 100644 index 00000000..7f5b2cfa --- /dev/null +++ b/Netch/Models/Server/ShadowsocksR/ShadowsocksR.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Netch.Models.Server.ShadowsocksR +{ + public class ShadowsocksR : Server + { + public ShadowsocksR() + { + this.Type = ServerType.ShadowsocksR; + } + + /// + /// 密码 + /// + [Newtonsoft.Json.JsonProperty("passwd")] + public string Passwd; + + /// + /// 加密 + /// + [Newtonsoft.Json.JsonProperty("method")] + public string Method; + + /// + /// 协议 + /// + [Newtonsoft.Json.JsonProperty("prot")] + public string Prot; + + /// + /// 协议参数 + /// + [Newtonsoft.Json.JsonProperty("protparam")] + public string ProtParam; + + /// + /// 混淆 + /// + [Newtonsoft.Json.JsonProperty("obfs")] + public string OBFS; + + /// + /// 混淆参数 + /// + [Newtonsoft.Json.JsonProperty("obfsparam")] + public string OBFSParam; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + try + { + var ssr = new Regex(@"ssr://([A-Za-z0-9+/=_-]+)", RegexOptions.IgnoreCase).Match(link); + if (!ssr.Success) + { + return false; + } + + var data = Utils.Base64.Decode.URLSafe(ssr.Groups[1].Value); + var dict = new Dictionary(); + + var offset = data.IndexOf(@"?", StringComparison.Ordinal); + if (offset > 0) + { + dict = ParseParam(data.Substring(offset + 1)); + data = data.Substring(0, offset); + } + + if (data.IndexOf("/", StringComparison.Ordinal) >= 0) + { + data = data.Substring(0, data.LastIndexOf("/", StringComparison.Ordinal)); + } + + var matches = new Regex(@"^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)").Match(data); + if (!matches.Success) + { + return false; + } + + if (dict.ContainsKey("remarks")) + { + this.Remark = Utils.Base64.Decode.URLSafe(dict["remarks"]); + } + + this.Host = matches.Groups[1].Value; + if (!ushort.TryParse(matches.Groups[2].Value, out this.Port)) + { + return false; + } + this.Passwd = Utils.Base64.Decode.URLSafe(matches.Groups[6].Value); + this.Method = matches.Groups[4].Value.ToLower(); + + this.Prot = (matches.Groups[3].Value.Length == 0 ? "origin" : matches.Groups[3].Value).Replace("_compatible", String.Empty).ToLower(); + if (dict.ContainsKey("protoparam")) + { + this.ProtParam = Utils.Base64.Decode.URLSafe(dict["protoparam"]); + } + + this.OBFS = (matches.Groups[5].Value.Length == 0 ? @"plain" : matches.Groups[5].Value).Replace("_compatible", String.Empty).ToLower(); + if (dict.ContainsKey("obfsparam")) + { + this.OBFSParam = Utils.Base64.Decode.URLSafe(dict["obfsparam"]); + } + } + catch (Exception e) + { + global::Netch.Global.Logger.Warning(e.ToString()); + + return false; + } + + return true; + } + + private static Dictionary ParseParam(string str) + { + var dict = new Dictionary(); + var obfs = str.Split('&'); + for (int i = 0; i < str.Length; i++) + { + if (obfs[i].IndexOf('=') > 0) + { + var index = obfs[i].IndexOf('='); + + var k = obfs[i].Substring(0, index); + var v = obfs[i].Substring(index + 1); + dict[k] = v; + } + } + + return dict; + } + } +} diff --git a/Netch/Models/Server/Socks/Socks.cs b/Netch/Models/Server/Socks/Socks.cs new file mode 100644 index 00000000..2c09e9de --- /dev/null +++ b/Netch/Models/Server/Socks/Socks.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace Netch.Models.Server.Socks +{ + public class Socks : Server + { + public Socks() + { + this.Type = ServerType.Socks; + } + + /// + /// 账号 + /// + [Newtonsoft.Json.JsonProperty("username")] + public string Username; + + /// + /// 密码 + /// + [Newtonsoft.Json.JsonProperty("password")] + public string Password; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + var list = link + .Replace("tg://socks?", "") + .Replace("https://t.me/socks?", "") + .Split('&'); + + var dict = new Dictionary(); + for (int i = 0; i < list.Length; i++) + { + var s = list[i].Split('='); + if (s.Length != 2) + { + continue; + } + + dict[s[0]] = s[1]; + } + + if (!dict.ContainsKey("server") || !dict.ContainsKey("port") || !ushort.TryParse(dict["port"], out _)) + { + return false; + } + + this.Host = dict["server"]; + this.Port = ushort.Parse(dict["port"]); + + if (dict.ContainsKey("user") && !String.IsNullOrEmpty(dict["user"])) + { + this.Username = dict["user"]; + } + + if (dict.ContainsKey("pass") && !String.IsNullOrEmpty(dict["pass"])) + { + this.Username = dict["pass"]; + } + + return true; + } + } +} diff --git a/Netch/Models/Server/Trojan/Trojan.cs b/Netch/Models/Server/Trojan/Trojan.cs new file mode 100644 index 00000000..16293a47 --- /dev/null +++ b/Netch/Models/Server/Trojan/Trojan.cs @@ -0,0 +1,45 @@ +namespace Netch.Models.Server.Trojan +{ + public class Trojan : Server + { + public Trojan() + { + this.Type = ServerType.Trojan; + } + + /// + /// 密码 + /// + public string Passwd; + + /// + /// 伪装 SNI 标头 + /// + public string SNI; + + /// + /// 复用会话 + /// + public bool Reuse = true; + + /// + /// Session Ticket + /// + public bool Ticket = false; + + /// + /// 不安全模式(跳过证书验证、跳过主机名验证) + /// + public bool Insecure = true; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + return false; + } + } +} diff --git a/Netch/Models/Server/VLess/VLess.cs b/Netch/Models/Server/VLess/VLess.cs new file mode 100644 index 00000000..3304e080 --- /dev/null +++ b/Netch/Models/Server/VLess/VLess.cs @@ -0,0 +1,30 @@ +namespace Netch.Models.Server.VLess +{ + public class VLess : Server + { + public VLess() + { + this.Type = ServerType.VLess; + } + + /// + /// 自定义配置 + /// + public bool Custom = true; + + /// + /// 自定义配置文件路径 + /// + public string FilePath; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + return false; + } + } +} diff --git a/Netch/Models/Server/VMess/VMess.cs b/Netch/Models/Server/VMess/VMess.cs new file mode 100644 index 00000000..bd5936ca --- /dev/null +++ b/Netch/Models/Server/VMess/VMess.cs @@ -0,0 +1,30 @@ +namespace Netch.Models.Server.VMess +{ + public class VMess : Server + { + public VMess() + { + this.Type = ServerType.VMess; + } + + /// + /// 自定义配置 + /// + public bool Custom = true; + + /// + /// 自定义配置文件路径 + /// + public string FilePath; + + /// + /// 解析链接 + /// + /// 链接 + /// 是否成功 + public bool ParseLink(string link) + { + return false; + } + } +} diff --git a/Netch/NativeMethods.cs b/Netch/NativeMethods.cs new file mode 100644 index 00000000..f1983437 --- /dev/null +++ b/Netch/NativeMethods.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Netch +{ + public static class NativeMethods + { + [DllImport("kernel32")] + public static extern bool AllocConsole(); + + [DllImport("kernel32")] + public static extern bool AttachConsole(uint dwProcessId); + } +} diff --git a/Netch/Netch.csproj b/Netch/Netch.csproj new file mode 100644 index 00000000..897d380d --- /dev/null +++ b/Netch/Netch.csproj @@ -0,0 +1,16 @@ + + + + WinExe + net5.0-windows + true + x64 + + + + + + + + + diff --git a/Netch/Tools/Guard.cs b/Netch/Tools/Guard.cs new file mode 100644 index 00000000..58658ffb --- /dev/null +++ b/Netch/Tools/Guard.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Netch.Tools +{ + public class Guard + { + /// + /// 启动信息 + /// + public ProcessStartInfo StartInfo; + + /// + /// 标准模式 + /// + public bool Standard = true; + + /// + /// 判定启动的字符串 + /// + public List JudgmentStarted; + + /// + /// 判定停止的字符串 + /// + public List JudgmentStopped; + + /// + /// 自动重启 + /// + public bool AutoRestart; + + /// + /// 启动 + /// + /// + public bool Create() + { + this.instance = new Process + { + StartInfo = this.StartInfo, + EnableRaisingEvents = true + }; + this.instance.StartInfo.RedirectStandardError = true; + this.instance.StartInfo.RedirectStandardOutput = true; + + this.instance.Exited += this.OnExited; + this.instance.ErrorDataReceived += this.OnOutputDataReceived; + this.instance.OutputDataReceived += this.OnOutputDataReceived; + + this.Started = false; + this.Starting = true; + this.instance.Start(); + + if (!this.Standard) + { + this.Started = true; + this.instance.BeginErrorReadLine(); + this.instance.BeginOutputReadLine(); + + return true; + } + + this.instance.BeginErrorReadLine(); + this.instance.BeginOutputReadLine(); + + for (var i = 0; i < 1000; i++) + { + Thread.Sleep(10); + + if (this.Started) + { + return true; + } + + if (!this.Starting) + { + return false; + } + } + + this.Delete(); + return false; + } + + /// + /// 停止 + /// + public void Delete() + { + this.AutoRestart = false; + + try + { + this.instance?.Kill(); + this.instance?.WaitForExit(); + } + catch (Exception) + { + // ignore + } + } + + /// + /// 进程 + /// + private Process instance; + + /// + /// 是否已启动 + /// + private bool Started = false; + + /// + /// 是否正在启动中 + /// + private bool Starting = false; + + private void OnExited(object sender, EventArgs e) + { + if (this.Started && this.AutoRestart) + { + Thread.Sleep(200); + + this.Create(); + } + } + + private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) + { + if (this.Starting) + { + if (this.instance.HasExited) + { + this.Starting = false; + } + + for (int i = 0; i < this.JudgmentStarted.Count; i++) + { + if (e.Data.ToLower().Contains(this.JudgmentStarted[i])) + { + this.Started = true; + this.Starting = false; + return; + } + } + + for (int i = 0; i < this.JudgmentStopped.Count; i++) + { + if (e.Data.ToLower().Contains(this.JudgmentStopped[i])) + { + this.Starting = false; + return; + } + } + } + + Console.WriteLine($"[Netch][Tools.Guard] {e.Data}"); + } + } +} diff --git a/Netch/Tools/Logger.cs b/Netch/Tools/Logger.cs new file mode 100644 index 00000000..be062259 --- /dev/null +++ b/Netch/Tools/Logger.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Netch.Tools +{ + public class Logger + { + /// + /// 互斥锁 + /// + private object mutex = new(); + + /// + /// 写入日志 + /// + /// + /// + private void WriteLine(string name, string text) + { + var method = new StackTrace().GetFrame(2).GetMethod(); + var content = $"[{DateTime.Now}][{method.ReflectedType.Name}.{method.Name}][{name}] {text}"; + + lock (mutex) + { + File.AppendAllText(this.SavePath, $"{content}\n"); + } + + Console.WriteLine($"[Netch]{content}"); + } + + /// + /// 保存路径 + /// + public string SavePath; + + /// + /// 调试 + /// + /// + public void Debug(string text) + { + this.WriteLine("DEBUG", text); + } + + /// + /// 信息 + /// + /// + public void Info(string text) + { + this.WriteLine("INFO", text); + } + + /// + /// 警告 + /// + /// + public void Warning(string text) + { + this.WriteLine("WARNING", text); + } + + /// + /// 错误 + /// + /// + public void Error(string text) + { + this.WriteLine("ERROR", text); + } + + /// + /// 崩溃 + /// + /// + public void Fatal(string text) + { + this.WriteLine("FATAL", text); + + Environment.Exit(1); + } + } +} diff --git a/Netch/Tools/TunTap/Outbound.cs b/Netch/Tools/TunTap/Outbound.cs new file mode 100644 index 00000000..754b9d57 --- /dev/null +++ b/Netch/Tools/TunTap/Outbound.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace Netch.Tools.TunTap +{ + public class Outbound + { + /// + /// 索引 + /// + public uint Index; + + /// + /// 适配器 + /// + public NetworkInterface Interface; + + /// + /// 地址 + /// + public IPAddress Address; + + /// + /// 掩码 + /// + public IPAddress Netmask; + + /// + /// 网关 + /// + public IPAddress Gateway; + + /// + /// 获取数据 + /// + /// + public bool Get() + { + if (Vanara.PInvoke.Win32Error.NO_ERROR != Vanara.PInvoke.IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route)) + { + return false; + } + + this.Index = route.dwForwardIfIndex; + this.Interface = NetworkInterface.GetAllNetworkInterfaces() + .First(nic => + { + var ipp = nic.GetIPProperties(); + if (nic.Supports(NetworkInterfaceComponent.IPv4)) + { + return ipp.GetIPv4Properties().Index == this.Index; + } + + return false; + }); + + var addr = this.Interface.GetIPProperties().UnicastAddresses.First(ipf => + { + return ipf.Address.AddressFamily == AddressFamily.InterNetwork; + }); + + this.Address = addr.Address; + this.Netmask = addr.IPv4Mask; + this.Gateway = new IPAddress(route.dwForwardNextHop.S_un_b); + return true; + } + } +} diff --git a/Netch/Utils/Base64.cs b/Netch/Utils/Base64.cs new file mode 100644 index 00000000..0af07379 --- /dev/null +++ b/Netch/Utils/Base64.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; + +namespace Netch.Utils +{ + public static class Base64 + { + public static class Encode + { + public static string Normal(string text) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)); + } + + public static string URLSafe(string text) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)).Replace("+", "-").Replace("/", "_").Replace("=", ""); + } + } + + public static class Decode + { + public static string Normal(string text) + { + return Encoding.UTF8.GetString(Convert.FromBase64String(text.PadRight(text.Length + (4 - text.Length % 4) % 4, '='))); + } + + public static string URLSafe(string text) + { + return Encoding.UTF8.GetString(Convert.FromBase64String(text.Replace("-", "+").Replace("_", "/").PadRight(text.Length + (4 - text.Length % 4) % 4, '='))); + } + } + } +} diff --git a/Netch/Utils/Config.cs b/Netch/Utils/Config.cs new file mode 100644 index 00000000..b349a064 --- /dev/null +++ b/Netch/Utils/Config.cs @@ -0,0 +1,6 @@ +namespace Netch.Utils +{ + public static class Config + { + } +} diff --git a/Netch/Utils/DNS.cs b/Netch/Utils/DNS.cs new file mode 100644 index 00000000..bf18abea --- /dev/null +++ b/Netch/Utils/DNS.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Net; + +namespace Netch.Utils +{ + public static class DNS + { + /// + /// 缓存表 + /// + private static readonly Hashtable Cache = new Hashtable(); + + /// + /// 获取 IP 地址 + /// + /// + /// + public static IPAddress Fetch(string name) + { + try + { + if (Cache.Contains(name)) + { + return Cache[name] as IPAddress; + } + + var task = Dns.GetHostAddressesAsync(name); + if (!task.Wait(1000)) + { + return IPAddress.Any; + } + + if (task.Result.Length == 0) + { + return IPAddress.Any; + } + + Cache.Add(name, task.Result[0]); + return task.Result[0]; + } + catch (Exception e) + { + Global.Logger.Warning(e.ToString()); + + return IPAddress.Any; + } + } + } +} diff --git a/Netch/Utils/FileHelper.cs b/Netch/Utils/FileHelper.cs new file mode 100644 index 00000000..182a25cd --- /dev/null +++ b/Netch/Utils/FileHelper.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Netch.Utils +{ + public static class FileHelper + { + /// + /// 计算文件 SHA256 校验和 + /// + /// 文件路径 + /// + public static byte[] Checksum(string name) + { + using (var algo = SHA256.Create()) + { + using (var fs = File.OpenRead(name)) + { + return algo.ComputeHash(fs); + } + } + } + + /// + /// 比较两个文件是否完全相同 + /// + /// 文件路径 + /// 文件路径 + /// + public static bool Equals(string oPath, string nPath) => Checksum(oPath).SequenceEqual(Checksum(nPath)); + } +} diff --git a/Netch/Utils/GitHub.cs b/Netch/Utils/GitHub.cs new file mode 100644 index 00000000..769a9b75 --- /dev/null +++ b/Netch/Utils/GitHub.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; + +namespace Netch.Utils +{ + public static class GitHub + { + /// + /// 地址 + /// + public static readonly string URL = "https://api.github.com/repos/NetchX/Netch/releases"; + + /// + /// 检查是否有更新 + /// + /// + public static int HasUpdate() + { + var list = GetReleaseList(); + if (list.Count < 1) + { + return 0; + } + + for (int i = 0; i < list.Count; i++) + { + if (list[i].Draft) + { + continue; + } + + if (Global.Config.Generic.Unstable) + { + if (list[i].Unstable) + { + continue; + } + } + + if (list[i].VerCode.Equals(Global.VerCode)) + { + return 0; + } + + return list[i].ID; + } + + return 0; + } + + /// + /// 获取单个发布 + /// + /// + /// + public static Models.GitHub.Release GetRelease(int id) + { + var data = HTTP.GetString($"{URL}/{id}"); + + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + /// + /// 获取发布列表 + /// + /// + public static List GetReleaseList() + { + var data = HTTP.GetString(URL); + + return Newtonsoft.Json.JsonConvert.DeserializeObject>(data); + } + } +} diff --git a/Netch/Utils/HTTP.cs b/Netch/Utils/HTTP.cs new file mode 100644 index 00000000..14eccba4 --- /dev/null +++ b/Netch/Utils/HTTP.cs @@ -0,0 +1,72 @@ +using System.IO; +using System.Net; +using System.Text; + +namespace Netch.Utils +{ + public static class HTTP + { + /// + /// User Agent + /// + public static readonly string DefaultUA = $"Netch/{Global.VerCode}"; + + /// + /// 创建请求 + /// + /// 地址 + /// 超时 + /// + public static HttpWebRequest CreateRequest(string url, int timeout = 0) + { + var request = (HttpWebRequest)WebRequest.Create(url); + request.UserAgent = DefaultUA; + request.Accept = "*/*"; + request.KeepAlive = true; + request.Timeout = timeout; + + return request; + } + + public static string GetString(string url) + { + var request = CreateRequest(url, 10000); + var response = (HttpWebResponse)request.GetResponse(); + + using (var rs = response.GetResponseStream()) + { + using (var sr = new StreamReader(rs, Encoding.UTF8)) + { + return sr.ReadToEnd(); + } + } + } + + public static string GetString(string url, int timeout) + { + var request = CreateRequest(url, timeout); + var response = (HttpWebResponse)request.GetResponse(); + + using (var rs = response.GetResponseStream()) + { + using (var sr = new StreamReader(rs, Encoding.UTF8)) + { + return sr.ReadToEnd(); + } + } + } + + public static string GetString(HttpWebRequest request) + { + var response = (HttpWebResponse)request.GetResponse(); + + using (var rs = response.GetResponseStream()) + { + using (var sr = new StreamReader(rs, Encoding.UTF8)) + { + return sr.ReadToEnd(); + } + } + } + } +} diff --git a/Netch/Utils/Netfilter.cs b/Netch/Utils/Netfilter.cs new file mode 100644 index 00000000..1ad2947c --- /dev/null +++ b/Netch/Utils/Netfilter.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Netch.Utils +{ + public static class Netfilter + { + public static class Methods + { + public enum NF_STATUS : int + { + NF_STATUS_SUCCESS = 0, + NF_STATUS_FAIL = -1, + NF_STATUS_INVALID_ENDPOINT_ID = -2, + NF_STATUS_NOT_INITIALIZED = -3, + NF_STATUS_IO_ERROR = -4 + } + + [DllImport("nfapinet", CallingConvention = CallingConvention.Cdecl)] + public static extern NF_STATUS nf_registerDriver(string name); + + [DllImport("nfapinet", CallingConvention = CallingConvention.Cdecl)] + public static extern NF_STATUS nf_unRegisterDriver(string driverName); + } + + public static readonly string dName = "netfilter2"; + public static readonly string oPath = Path.Combine(Environment.SystemDirectory, "drivers\\netfilter2.sys"); + public static readonly string nPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin\\netfilter2.sys"); + + /// + /// 注册 Netfilter 驱动 + /// + /// + public static bool Create() + { + try + { + if (!Delete()) + { + return false; + } + + File.Copy(nPath, oPath); + var status = Methods.nf_registerDriver(dName); + if (status != Methods.NF_STATUS.NF_STATUS_SUCCESS) + { + Global.Logger.Error($"注册 Netfilter 驱动失败:{status}"); + return false; + } + + return true; + } + catch (Exception e) + { + Global.Logger.Error(e.ToString()); + + return false; + } + } + + /// + /// 更新 Netfilter 驱动 + /// + /// + public static bool Update() + { + if (!File.Exists(oPath)) + { + return Create(); + } + + if (!FileHelper.Equals(oPath, nPath)) + { + return Create(); + } + + return true; + } + + /// + /// 删除 Netfilter 驱动 + /// + /// + public static bool Delete() + { + try + { + if (File.Exists(oPath)) + { + var status = Methods.nf_unRegisterDriver(dName); + if (status != Methods.NF_STATUS.NF_STATUS_SUCCESS) + { + Global.Logger.Error($"取消注册 Netfilter 驱动失败:{status}"); + return false; + } + + File.Delete(oPath); + } + } + catch (Exception e) + { + Global.Logger.Error($"删除 Netfilter 驱动失败:{e}"); + } + + return true; + } + } +} diff --git a/Netch/Utils/Ping.cs b/Netch/Utils/Ping.cs new file mode 100644 index 00000000..6b4b2699 --- /dev/null +++ b/Netch/Utils/Ping.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace Netch.Utils +{ + public static class Ping + { + /// + /// 缓存内容 + /// + private class CacheEntry + { + /// + /// 缓存时间 + /// + public long Unix; + + /// + /// 延迟 + /// + public int Time; + } + + /// + /// 缓存表 + /// + private static Hashtable Cache = new Hashtable(); + + /// + /// 测试 ICMP 延迟 + /// + /// + /// + private static int ICMPing(IPAddress addr) + { + using (var client = new System.Net.NetworkInformation.Ping()) + { + var tk = client.SendPingAsync(addr); + if (!tk.Wait(1000)) + { + return 999; + } + + return Convert.ToInt32(tk.Result.RoundtripTime); + } + } + + /// + /// 测试 TCP 延迟 + /// + /// + /// + /// + private static int TCPPing(IPAddress addr, ushort port) + { + using (var client = new TcpClient()) + { + var sw = Stopwatch.StartNew(); + var tk = client.ConnectAsync(addr, port); + + if (!tk.Wait(1000)) + { + sw.Stop(); + return 999; + } + + sw.Stop(); + return Convert.ToInt32(sw.Elapsed.TotalMilliseconds); + } + } + + /// + /// 获取延迟 + /// + /// + /// + public static int Fetch(Models.Server.Server s) + { + /* + * -1 : Not Test + * -2 : DNS Exception + * -3 : Exception + * 999 : Timeout + */ + + try + { + var addr = DNS.Fetch(s.Host); + if (addr == IPAddress.Any) + { + return -2; + } + + if (Cache.Contains(addr)) + { + var rule = Cache[addr] as CacheEntry; + if (DateTimeOffset.Now.ToUnixTimeSeconds() - rule.Unix < 30) + { + return rule.Time; + } + else + { + Cache.Remove(addr); + } + } + + var time = 0; + if (Global.Config.Generic.ICMPing) + { + time = ICMPing(addr); + } + else + { + return TCPPing(addr, s.Port); + } + + Cache.Add(addr, new CacheEntry() { Unix = DateTimeOffset.Now.ToUnixTimeSeconds(), Time = time }); + return time; + } + catch (Exception e) + { + Global.Logger.Warning(e.ToString()); + return -3; + } + } + } +} diff --git a/Netch/Utils/RouteHelper.cs b/Netch/Utils/RouteHelper.cs new file mode 100644 index 00000000..22c1ecbe --- /dev/null +++ b/Netch/Utils/RouteHelper.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Netch.Utils +{ + public static class RouteHelper + { + /// + /// 将 Luid 转换为 Index 索引 + /// + /// + /// + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong ConvertLuidToIndex(ulong id); + + /// + /// 绑定 IP 地址(Windows XP) + /// + /// + /// + /// + /// + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool CreateIPv4(string address, string netmask, ulong index); + + /// + /// 绑定 IP 地址 + /// + /// + /// + /// + /// + /// + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool CreateUnicastIP(AddressFamily inet, string address, byte cidr, ulong index); + + /// + /// 创建路由 + /// + /// + /// + /// + /// + /// + /// + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool CreateRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index); + + /// + /// 删除路由 + /// + /// + /// + /// + /// + /// + /// + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool DeleteRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index); + + /// + /// 使用索引获取适配器 + /// + /// + /// + public static NetworkInterface GetInterfaceByIndex(ulong index) + { + NetworkInterface adapter = null; + + var list = NetworkInterface.GetAllNetworkInterfaces(); + for (int i = 0; i < list.Length; i++) + { + if (list[i].Supports(NetworkInterfaceComponent.IPv4)) + { + if (list[i].GetIPProperties().GetIPv4Properties().Index == Convert.ToInt32(index)) + { + adapter = list[i]; + break; + } + } + else if (list[i].Supports(NetworkInterfaceComponent.IPv6)) + { + if (list[i].GetIPProperties().GetIPv6Properties().Index == Convert.ToInt32(index)) + { + adapter = list[i]; + break; + } + } + else + { + return null; + } + } + + return adapter; + } + + /// + /// 使用名称获取适配器索引 + /// + /// + /// + public static ulong GetInterfaceIndexByDescription(string name) + { + var ada = NetworkInterface.GetAllNetworkInterfaces() + .First(nic => + { + if (nic.Description.Equals(name)) + { + return true; + } + + return false; + }); + + int index = 0; + if (ada.Supports(NetworkInterfaceComponent.IPv4)) + { + index = ada.GetIPProperties().GetIPv4Properties().Index; + } + else if (ada.Supports(NetworkInterfaceComponent.IPv6)) + { + index = ada.GetIPProperties().GetIPv6Properties().Index; + } + + return Convert.ToUInt64(index); + } + } +} diff --git a/Netch/Utils/WinTUN.cs b/Netch/Utils/WinTUN.cs new file mode 100644 index 00000000..80f7dc50 --- /dev/null +++ b/Netch/Utils/WinTUN.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; + +namespace Netch.Utils +{ + public static class WinTUN + { + public static string oPath = Path.Combine(Environment.SystemDirectory, "wintun.dll"); + public static string nPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin\\wintun.bin"); + + /// + /// 注册 WinTUN 驱动 + /// + /// + public static bool Create() + { + try + { + if (!Delete()) + { + return false; + } + + File.Copy(nPath, oPath); + } + catch (Exception e) + { + Global.Logger.Error($"注册 WinTUN 驱动失败:{e}"); + + return false; + } + + return true; + } + + /// + /// 更新 WinTUN 驱动 + /// + /// + public static bool Update() + { + if (!File.Exists(oPath)) + { + return Create(); + } + + if (!FileHelper.Equals(oPath, nPath)) + { + return Create(); + } + + return true; + } + + /// + /// 删除 WinTUN 驱动 + /// + /// + public static bool Delete() + { + try + { + if (File.Exists(oPath)) + { + File.Delete(oPath); + } + } + catch (Exception e) + { + Global.Logger.Error($"删除 WinTUN 驱动失败:{e}"); + + return false; + } + + return true; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 00000000..c6d12afe --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +

+ +
+ +# Netch +A simple proxy client + +[![](https://img.shields.io/badge/telegram-group-green?style=flat-square)](https://t.me/netch_group) +[![](https://img.shields.io/badge/telegram-channel-blue?style=flat-square)](https://t.me/netch_channel) +[![](https://img.shields.io/github/downloads/NetchX/Netch/total.svg?style=flat-square)](https://github.com/NetchX/Netch/releases) +[![](https://img.shields.io/github/v/release/NetchX/Netch?style=flat-square)](https://github.com/NetchX/Netch/releases) +
+ +## Features +Some features may not be implemented in version 1 + +### Modes +- ProcessMode - Use Netfilter driver to intercept process traffic +- ShareMode - Share your network based on WinPcap / Npcap +- TapMode - Use TAP-Windows driver to create virtual adapter +- TunMode - Use WinTUN driver to create virtual adapter +- WebMode - Web proxy mode +- WmpMode - Proxy forwarding (eg. OBS Streaming) + +### Protocols +- [Socks5](https://www.wikiwand.com/en/SOCKS) +- [Shadowsocks](https://github.com/shadowsocks/shadowsocks-libev) +- [ShadowsocksR](https://github.com/shadowsocksrr/shadowsocksr-libev) +- [Trojan](https://trojan-gfw.github.io/trojan/) +- [VLess](https://github.com/xtls/xray-core) +- [VMess](https://github.com/v2fly/v2ray-core) + +### Others +- UDP NAT FullCone (May limited by your server) +- .NET 5.0 x64 + +## Sponsor +JetBrains + +- [NeroCloud](https://nerocloud.io) + +## Donate +- XMR *48ju3ELNZEa6wwPBMexCJ9G218BGY2XwhH6B6bmkFuJ3QgM4hPw2Pra35jPtuBZSc7SLNWeBpiWJZWjQeMAiLnTx2tH2Efx* +- ETH *0x23dac0a93bcd71fec7a95833ad030338f167f185* + +## Credit +- [TAP-Windows](https://github.com/OpenVPN/tap-windows6) +- [WinTUN](https://www.wintun.net) +- [NetFilter](https://netfiltersdk.com) +- [aioCloud](https://github.com/aiocloud) +- [Shadowsocks](https://github.com/shadowsocks/shadowsocks-libev) +- [ShadowsocksR](https://github.com/shadowsocksrr/shadowsocksr-libev) +- [Trojan](https://github.com/trojan-gfw/trojan) +- [V2Ray](https://github.com/v2fly/v2ray-core) +- [XRay](https://github.com/xtls/xray-core) diff --git a/Tests/.gitignore b/Tests/.gitignore new file mode 100644 index 00000000..aeb4af3c --- /dev/null +++ b/Tests/.gitignore @@ -0,0 +1,3 @@ +/bin +/obj +/*.csproj.user diff --git a/Tests/Global.cs b/Tests/Global.cs new file mode 100644 index 00000000..d5ad4a11 --- /dev/null +++ b/Tests/Global.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Tests +{ + [TestClass] + public class Global + { + [TestMethod] + public void Test() + { + Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory); + } + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 00000000..670a7626 --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,18 @@ + + + + net5.0 + + false + + x64 + + + + + + + + + + diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..72ada54c --- /dev/null +++ b/build.ps1 @@ -0,0 +1,40 @@ +param ( + [Parameter()] + [ValidateSet('Debug', 'Release')] + [string] + $Configuration = 'Release', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] + $OutputPath = 'release', + + [Parameter()] + [bool] + $SelfContained = $False, + + [Parameter()] + [bool] + $PublishReadyToRun = $False, + + [Parameter()] + [bool] + $PublishSingleFile = $True +) + +.\scripts\download.ps1 $OutputPath +if ( -Not $? ) { exit 1 } + +Write-Host "Building $Configuration to $OutputPath" +dotnet publish ` + -c $Configuration ` + -r "win-x64" ` + -p:Platform="x64" ` + -p:PublishSingleFile=$PublishSingleFile ` + -p:SelfContained=$SelfContained ` + -p:PublishTrimmed=$SelfContained ` + -p:PublishReadyToRun=$PublishReadyToRun ` + -o $OutputPath ` + Netch\Netch.csproj + +exit $lastExitCode \ No newline at end of file diff --git a/clean.ps1 b/clean.ps1 new file mode 100644 index 00000000..068b7399 --- /dev/null +++ b/clean.ps1 @@ -0,0 +1,15 @@ +function Delete { + param([string]$Path) + + if (Test-Path $Path) { + Remove-Item -Recurse -Force $Path | Out-Null + } +} + +Delete ".vs" +Delete "Netch\bin" +Delete "Netch\obj" +Delete "Tests\bin" +Delete "Tests\obj" +Delete "TestResults" +exit 0 diff --git a/jetbrains.svg b/jetbrains.svg new file mode 100644 index 00000000..e02b5595 --- /dev/null +++ b/jetbrains.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/download.ps1 b/scripts/download.ps1 new file mode 100644 index 00000000..91368654 --- /dev/null +++ b/scripts/download.ps1 @@ -0,0 +1,39 @@ +param([string]$OutputPath) + +$NetchDataURL="https://github.com/NetchX/NetchData/archive/refs/heads/master.zip" +$NetchModeURL="https://github.com/NetchX/NetchMode/archive/refs/heads/master.zip" +$NetchI18NURL="https://github.com/NetchX/NetchI18N/archive/refs/heads/master.zip" + +$last=$(Get-Location) +New-Item -ItemType Directory -Name $OutputPath | Out-Null +Set-Location $OutputPath + +Invoke-WebRequest -Uri $NetchDataURL -OutFile data.zip +Invoke-WebRequest -Uri $NetchModeURL -OutFile mode.zip +Invoke-WebRequest -Uri $NetchI18NURL -OutFile i18n.zip + +Expand-Archive -Force -Path data.zip -DestinationPath . +Expand-Archive -Force -Path mode.zip -DestinationPath . +Expand-Archive -Force -Path i18n.zip -DestinationPath . + +New-Item -ItemType Directory -Name bin | Out-Null +New-Item -ItemType Directory -Name mode | Out-Null +New-Item -ItemType Directory -Name i18n | Out-Null + +Copy-Item -Recurse -Force .\NetchData-master\* .\bin +Copy-Item -Recurse -Force .\NetchMode-master\mode\* .\mode +Copy-Item -Recurse -Force .\NetchI18N-master\i18n\* .\i18n + +Remove-Item -Recurse -Force NetchData-master +Remove-Item -Recurse -Force NetchMode-master +Remove-Item -Recurse -Force NetchI18N-master +Remove-Item -Force data.zip +Remove-Item -Force mode.zip +Remove-Item -Force i18n.zip + +..\scripts\download\cloak.ps1 -OutputPath bin +..\scripts\download\xray-core.ps1 -OutputPath bin + +Get-Item * +Set-Location $last +exit 0 \ No newline at end of file diff --git a/scripts/download/cloak.ps1 b/scripts/download/cloak.ps1 new file mode 100644 index 00000000..54eedeeb --- /dev/null +++ b/scripts/download/cloak.ps1 @@ -0,0 +1,7 @@ +param([string]$OutputPath) +$address="https://github.com/cbeuw/Cloak/releases/download/v2.5.4/ck-client-windows-amd64-v2.5.4.exe" + +Invoke-WebRequest -Uri $address -OutFile ck-client.exe + +Move-Item -Force ck-client.exe $OutputPath +exit 0 \ No newline at end of file diff --git a/scripts/download/xray-core.ps1 b/scripts/download/xray-core.ps1 new file mode 100644 index 00000000..a8ebb728 --- /dev/null +++ b/scripts/download/xray-core.ps1 @@ -0,0 +1,13 @@ +param([string]$OutputPath) +$address="https://github.com/XTLS/Xray-core/releases/download/v1.4.2/Xray-windows-64.zip" + +Invoke-WebRequest -Uri $address -OutFile xray-core.zip +Expand-Archive -Force -Path xray-core.zip -DestinationPath xray-core + +Move-Item -Force xray-core\xray.exe $OutputPath +Move-Item -Force xray-core\geoip.dat $OutputPath +Move-Item -Force xray-core\geosite.dat $OutputPath + +Remove-Item -Recurse -Force xray-core +Remove-Item -Recurse -Force xray-core.zip +exit 0 \ No newline at end of file