Add files via upload

This commit is contained in:
Netch
2021-06-15 21:44:03 +08:00
commit 334c9ba7a7
83 changed files with 3785 additions and 0 deletions

3
Netch/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/bin
/obj
/*.csproj.user

24
Netch/App.manifest Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="Netch"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
</windowsSettings>
</application>
</assembly>

9
Netch/App.xaml Normal file
View File

@@ -0,0 +1,9 @@
<Application x:Class="Netch.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Netch"
Startup="Application_Startup">
<Application.Resources>
</Application.Resources>
</Application>

13
Netch/App.xaml.cs Normal file
View File

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

10
Netch/AssemblyInfo.cs Normal file
View File

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

View File

@@ -0,0 +1,9 @@
namespace Netch.Controllers.Interface
{
public interface IController
{
bool Create(Models.Server.Server s, Models.Mode.Mode m);
bool Delete();
}
}

View File

@@ -0,0 +1,108 @@
using System;
namespace Netch.Controllers
{
public class MainController : Interface.IController
{
/// <summary>
/// 节点控制器
/// </summary>
private Interface.IController NodeController;
/// <summary>
/// 模式控制器
/// </summary>
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;
}
}
}

View File

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

View File

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

View File

@@ -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<ManagementObject>().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();
}
}
}

View File

@@ -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<ManagementObject>().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();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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<string>()
{
"listening at"
},
JudgmentStopped = new List<string>()
{
"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;
}
}
}

View File

@@ -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<string>()
{
"listening at"
},
JudgmentStopped = new List<string>()
{
"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;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
<Window x:Class="Netch.Forms.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Netch.Forms"
mc:Ignorable="d"
Title="Netch" Height="450" Width="800">
<Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,12 @@
using System.Windows;
namespace Netch.Forms
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

34
Netch/Global.cs Normal file
View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Netch
{
public static class Global
{
/// <summary>
/// 版本号
/// </summary>
public static readonly string VerCode = "2.0.0";
/// <summary>
/// 日志记录
/// </summary>
public static Tools.Logger Logger = new Tools.Logger() { SavePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs\\Netch.log") };
/// <summary>
/// 配置文件
/// </summary>
public static Models.Config.Config Config;
/// <summary>
/// 节点列表
/// </summary>
public static List<Models.Server.ServerList> NodeList;
/// <summary>
/// 模式列表
/// </summary>
public static List<Models.Mode.Mode> ModeList;
}
}

View File

@@ -0,0 +1,15 @@
namespace Netch.Models.Config
{
public class AioDNS
{
/// <summary>
/// 国内 DNS 地址
/// </summary>
public string ChinaDNS = "tcp://119.29.29.29:53";
/// <summary>
/// 国外 DNS 地址
/// </summary>
public string OtherDNS = "tls://1.1.1.1:853";
}
}

View File

@@ -0,0 +1,73 @@
using System.Collections.Generic;
namespace Netch.Models.Config
{
public class Config
{
/// <summary>
/// 配置 版本
/// </summary>
[Newtonsoft.Json.JsonProperty("verCode")]
public int VerCode = 1;
/// <summary>
/// 通用 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("generic")]
public Generic Generic = new();
/// <summary>
/// 端口 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("ports")]
public Ports Ports = new();
/// <summary>
/// ProcessMode 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("processmode")]
public ProcessMode ProcessMode = new();
/// <summary>
/// ShareMode 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("sharemode")]
public ShareMode ShareMode = new();
/// <summary>
/// TapMode 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("tapmode")]
public TapMode TapMode = new();
/// <summary>
/// TunMode 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("tunmode")]
public TunMode TunMode = new();
/// <summary>
/// AioDNS 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("aiodns")]
public AioDNS AioDNS = new();
/// <summary>
/// V2Ray 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("v2ray")]
public V2Ray V2Ray = new();
/// <summary>
/// STUN 配置
/// </summary>
[Newtonsoft.Json.JsonProperty("stun")]
public STUN STUN = new();
/// <summary>
/// 订阅链接
/// </summary>
[Newtonsoft.Json.JsonProperty("subscriptions")]
public List<Subscription> Subscriptions = new();
}
}

View File

@@ -0,0 +1,15 @@
namespace Netch.Models.Config
{
public class Generic
{
/// <summary>
/// 检查 Unstable 更新
/// </summary>
public bool Unstable = false;
/// <summary>
/// 使用 ICMP 测试延迟
/// </summary>
public bool ICMPing = true;
}
}

View File

@@ -0,0 +1,23 @@
namespace Netch.Models.Config
{
public class Ports
{
/// <summary>
/// Socks 端口
/// </summary>
[Newtonsoft.Json.JsonProperty("socks")]
public int Socks = 2081;
/// <summary>
/// Mixed 端口
/// </summary>
[Newtonsoft.Json.JsonProperty("mixed")]
public int Mixed = 2082;
/// <summary>
/// Redir 端口
/// </summary>
[Newtonsoft.Json.JsonProperty("redir")]
public int Redir = 2083;
}
}

View File

@@ -0,0 +1,11 @@
namespace Netch.Models.Config
{
public class ProcessMode
{
/// <summary>
/// DNS
/// </summary>
[Newtonsoft.Json.JsonProperty("dns")]
public string DNS = "1.1.1.1:53";
}
}

View File

@@ -0,0 +1,11 @@
namespace Netch.Models.Config
{
public class STUN
{
/// <summary>
/// 主机名
/// </summary>
[Newtonsoft.Json.JsonProperty("host")]
public string Host = "stun.ekiga.net";
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
namespace Netch.Models.Config
{
public class ShareMode
{
/// <summary>
/// 硬件地址(用于 ARP 回复)
///
/// CuteCR
/// 43:75:74:65:43:52
///
/// NetchX
/// 4e:65:74:63:68:58
/// </summary>
[Newtonsoft.Json.JsonProperty("hardware")]
public string Hardware = "4e:65:74:63:68:58";
/// <summary>
/// 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("network")]
public string Network = "100.64.0.0/24";
/// <summary>
/// 网关
/// </summary>
[Newtonsoft.Json.JsonProperty("gateway")]
public string Gateway = "100.64.0.1";
/// <summary>
/// DNS
/// </summary>
[Newtonsoft.Json.JsonProperty("dns")]
public string DNS = "aiodns";
/// <summary>
/// 网卡名(默认自动检测)
/// </summary>
public string EthernetName = "auto";
/// <summary>
/// 绕过 IP 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassIPs = new();
}
}

View File

@@ -0,0 +1,23 @@
namespace Netch.Models.Config
{
public class Subscription
{
/// <summary>
/// 启用 / 禁用
/// </summary>
[Newtonsoft.Json.JsonProperty("enabled")]
public bool Checked = true;
/// <summary>
/// 备注
/// </summary>
[Newtonsoft.Json.JsonProperty("remark")]
public string Remark;
/// <summary>
/// 链接
/// </summary>
[Newtonsoft.Json.JsonProperty("address")]
public string Link;
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Netch.Models.Config
{
public class TapMode
{
/// <summary>
/// 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("network")]
public string Network = "100.64.0.100/24";
/// <summary>
/// 网关
/// </summary>
[Newtonsoft.Json.JsonProperty("gateway")]
public string Gateway = "100.64.0.1";
/// <summary>
/// DNS
/// </summary>
[Newtonsoft.Json.JsonProperty("dns")]
public string DNS = "aiodns";
/// <summary>
/// 绕过 IP 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassIPs = new();
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Netch.Models.Config
{
public class TunMode
{
/// <summary>
/// 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("network")]
public string Network = "100.64.0.100/24";
/// <summary>
/// 网关
/// </summary>
[Newtonsoft.Json.JsonProperty("gateway")]
public string Gateway = "100.64.0.1";
/// <summary>
/// DNS
/// </summary>
[Newtonsoft.Json.JsonProperty("dns")]
public string DNS = "aiodns";
/// <summary>
/// 绕过 IP 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassIPs = new();
}
}

View File

@@ -0,0 +1,63 @@
namespace Netch.Models.Config
{
public class V2Ray
{
/// <summary>
/// FullCone 支持(需要 xray-core 服务端版本 v1.3.0+
/// </summary>
public bool FullCone = false;
/// <summary>
/// 跳过证书认证
/// </summary>
public bool Insecure = false;
/// <summary>
/// 多路复用
/// </summary>
public bool Multiplex = false;
/// <summary>
/// KCP 设定
/// </summary>
public V2RayKCP KCP = new();
}
public class V2RayKCP
{
/// <summary>
/// MTU
/// </summary>
public int MTU = 1450;
/// <summary>
/// TTI
/// </summary>
public int TTI = 50;
/// <summary>
/// 上行链路流量
/// </summary>
public int UPC = 5;
/// <summary>
/// 下行链路流量
/// </summary>
public int DLC = 20;
/// <summary>
/// 读取缓冲区大小MB
/// </summary>
public int RBS = 2;
/// <summary>
/// 写入缓冲区大小MB
/// </summary>
public int WBS = 2;
/// <summary>
/// 拥塞控制
/// </summary>
public bool BBR = false;
}
}

View File

@@ -0,0 +1,23 @@
namespace Netch.Models.GitHub
{
public class Asset
{
/// <summary>
/// name
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
public string Name;
/// <summary>
/// browser_download_url
/// </summary>
[Newtonsoft.Json.JsonProperty("browser_download_url")]
public string URL;
/// <summary>
/// size
/// </summary>
[Newtonsoft.Json.JsonProperty("size")]
public ulong Size;
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
namespace Netch.Models.GitHub
{
/// <summary>
/// https://api.github.com/repos/{owner}/{repo}/releases
/// </summary>
public class Release
{
/// <summary>
/// id
/// </summary>
[Newtonsoft.Json.JsonProperty("id")]
public int ID;
/// <summary>
/// html_url
/// </summary>
[Newtonsoft.Json.JsonProperty("html_url")]
public string URL;
/// <summary>
/// tag_name
/// </summary>
[Newtonsoft.Json.JsonProperty("tag_name")]
public string VerCode;
/// <summary>
/// draft
/// </summary>
[Newtonsoft.Json.JsonProperty("draft")]
public bool Draft;
/// <summary>
/// prerelease
/// </summary>
[Newtonsoft.Json.JsonProperty("prerelease")]
public bool Unstable;
/// <summary>
/// assets
/// </summary>
[Newtonsoft.Json.JsonProperty("assets")]
public List<Asset> Files;
}
}

19
Netch/Models/Mode/Mode.cs Normal file
View File

@@ -0,0 +1,19 @@
namespace Netch.Models.Mode
{
public class Mode
{
/// <summary>
/// 类型
/// </summary>
[Newtonsoft.Json.JsonProperty("type")]
public ModeType Type;
/// <summary>
/// 备注
/// </summary>
[Newtonsoft.Json.JsonProperty("remark")]
public string Remark;
public override string ToString() => $"[{((int)this.Type) + 1}] {this.Remark}";
}
}

View File

@@ -0,0 +1,35 @@
namespace Netch.Models.Mode
{
public enum ModeType : int
{
/// <summary>
/// 进程代理
/// </summary>
ProcessMode,
/// <summary>
/// 网络共享
/// </summary>
ShareMode,
/// <summary>
/// 网卡代理
/// </summary>
TapMode,
/// <summary>
/// 网卡代理
/// </summary>
TunMode,
/// <summary>
/// 网页代理
/// </summary>
WebMode,
/// <summary>
/// 代理转发
/// </summary>
WmpMode
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
namespace Netch.Models.Mode.ProcessMode
{
public class ProcessMode : Mode
{
public ProcessMode()
{
this.Type = ModeType.ProcessMode;
}
/// <summary>
/// 过滤 IPv4 + IPv6 环路流量
/// </summary>
[Newtonsoft.Json.JsonProperty("filterLoopback")]
public bool Loopback = false;
/// <summary>
/// 过滤 ICMP 流量(伪造 ICMP 回复)
/// </summary>
[Newtonsoft.Json.JsonProperty("filterICMP")]
public bool ICMP = true;
/// <summary>
/// 过滤 TCP 流量
/// </summary>
[Newtonsoft.Json.JsonProperty("filterTCP")]
public bool TCP = true;
/// <summary>
/// 过滤 UDP 流量
/// </summary>
[Newtonsoft.Json.JsonProperty("filterUDP")]
public bool UDP = true;
/// <summary>
/// 绕过列表
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassList;
/// <summary>
/// 代理列表
/// </summary>
[Newtonsoft.Json.JsonProperty("handle")]
public List<string> HandleList;
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Netch.Models.Mode.ShareMode
{
public class ShareMode : Mode
{
public ShareMode()
{
this.Type = ModeType.ShareMode;
}
/// <summary>
/// 绕过列表IP CIDR
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassList;
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace Netch.Models.Mode.TapMode
{
public class TapMode : Mode
{
public TapMode()
{
this.Type = ModeType.TapMode;
}
/// <summary>
/// 绕过列表IP CIDR
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassList;
/// <summary>
/// 代理列表IP CIDR
/// </summary>
[Newtonsoft.Json.JsonProperty("handle")]
public List<string> HandleList;
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace Netch.Models.Mode.TunMode
{
public class TunMode : Mode
{
public TunMode()
{
this.Type = ModeType.TunMode;
}
/// <summary>
/// 绕过列表IP CIDR
/// </summary>
[Newtonsoft.Json.JsonProperty("bypass")]
public List<string> BypassList;
/// <summary>
/// 代理列表IP CIDR
/// </summary>
[Newtonsoft.Json.JsonProperty("handle")]
public List<string> HandleList;
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace Netch.Models.Mode.WebMode
{
public class WebMode : Mode
{
public WebMode()
{
this.Type = ModeType.WebMode;
}
/// <summary>
/// 设置系统代理
/// </summary>
[Newtonsoft.Json.JsonProperty("setSystemProxy")]
public bool SetSystemProxy;
/// <summary>
/// 绕过域名后缀
/// </summary>
[Newtonsoft.Json.JsonProperty("bypassDomainSuffix")]
public List<string> BypassDomainSuffix;
/// <summary>
/// 绕过 IP 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("bypassIPs")]
public List<string> BypassIPs;
}
}

View File

@@ -0,0 +1,30 @@
namespace Netch.Models.Mode.WmpMode
{
public class WmpMode : Mode
{
public WmpMode()
{
this.Type = ModeType.WmpMode;
}
/// <summary>
/// 监听地址(为空则监听所有 IPv4 + IPv6 地址)
/// </summary>
public string ListenAddr;
/// <summary>
/// 监听端口
/// </summary>
public ushort ListenPort;
/// <summary>
/// 远端地址
/// </summary>
public string RemoteAddr;
/// <summary>
/// 远端端口
/// </summary>
public ushort RemotePort;
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Net;
namespace Netch.Models.Server
{
public class Server
{
/// <summary>
/// 类型
/// </summary>
[Newtonsoft.Json.JsonProperty("type")]
public ServerType Type;
/// <summary>
/// 备注
/// </summary>
[Newtonsoft.Json.JsonProperty("remark")]
public string Remark;
/// <summary>
/// 地址
/// </summary>
[Newtonsoft.Json.JsonProperty("host")]
public string Host;
/// <summary>
/// 端口
/// </summary>
[Newtonsoft.Json.JsonProperty("port")]
public ushort Port;
/// <summary>
/// 延迟
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public int Ping = -1;
/// <summary>
/// 测试延迟
/// </summary>
/// <returns></returns>
public void TestPing() => this.Ping = Utils.Ping.Fetch(this);
/// <summary>
/// 解析地址
/// </summary>
/// <returns></returns>
public string Resolve() => (Utils.DNS.Fetch(this.Host) != IPAddress.Any) ? Utils.DNS.Fetch(this.Host).ToString() : this.Host;
/// <summary>
/// 获取备注
/// </summary>
/// <returns></returns>
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);
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Netch.Models.Server
{
public class ServerList
{
/// <summary>
/// 群组
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
public string Group;
/// <summary>
/// 节点
/// </summary>
[Newtonsoft.Json.JsonProperty("list")]
public List<Server> List;
}
}

View File

@@ -0,0 +1,35 @@
namespace Netch.Models.Server
{
public enum ServerType : int
{
/// <summary>
/// Socks5
/// </summary>
Socks,
/// <summary>
/// Shadowsocks
/// </summary>
Shadowsocks,
/// <summary>
/// ShadowsocksR
/// </summary>
ShadowsocksR,
/// <summary>
/// Trojan
/// </summary>
Trojan,
/// <summary>
/// VLess
/// </summary>
VLess,
/// <summary>
/// VMess
/// </summary>
VMess
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace Netch.Models.Server.Shadowsocks
{
public static class Global
{
public static readonly List<string> Methods = new List<string>()
{
"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",
};
}
}

View File

@@ -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;
}
/// <summary>
/// 密码
/// </summary>
[Newtonsoft.Json.JsonProperty("passwd")]
public string Passwd;
/// <summary>
/// 加密
/// </summary>
[Newtonsoft.Json.JsonProperty("method")]
public string Method;
/// <summary>
/// 插件
/// </summary>
[Newtonsoft.Json.JsonProperty("obfs")]
public string OBFS;
/// <summary>
/// 插件参数
/// </summary>
[Newtonsoft.Json.JsonProperty("obfsparam")]
public string OBFSParam;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
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(@"^(?<data>.+?)\?(.+)$");
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://(?<base64>.+?)@(?<server>.+):(?<port>\d+)");
var parser = new Regex(@"^(?<method>.+?):(?<password>.+)$");
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);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace Netch.Models.Server.ShadowsocksR
{
public static class Global
{
public static readonly List<string> Methods = new List<string>()
{
"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<string> Prots = new List<string>()
{
"origin",
"auth_sha1_v4",
"auth_aes128_md5",
"auth_aes128_sha1",
"auth_chain_a",
};
public static readonly List<string> OBFSs = new List<string>()
{
"plain",
"http_post",
"http_simple",
"tls1.2_ticket_auth"
};
}
}

View File

@@ -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;
}
/// <summary>
/// 密码
/// </summary>
[Newtonsoft.Json.JsonProperty("passwd")]
public string Passwd;
/// <summary>
/// 加密
/// </summary>
[Newtonsoft.Json.JsonProperty("method")]
public string Method;
/// <summary>
/// 协议
/// </summary>
[Newtonsoft.Json.JsonProperty("prot")]
public string Prot;
/// <summary>
/// 协议参数
/// </summary>
[Newtonsoft.Json.JsonProperty("protparam")]
public string ProtParam;
/// <summary>
/// 混淆
/// </summary>
[Newtonsoft.Json.JsonProperty("obfs")]
public string OBFS;
/// <summary>
/// 混淆参数
/// </summary>
[Newtonsoft.Json.JsonProperty("obfsparam")]
public string OBFSParam;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
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<string, string>();
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<string, string> ParseParam(string str)
{
var dict = new Dictionary<string, string>();
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;
}
}
}

View File

@@ -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;
}
/// <summary>
/// 账号
/// </summary>
[Newtonsoft.Json.JsonProperty("username")]
public string Username;
/// <summary>
/// 密码
/// </summary>
[Newtonsoft.Json.JsonProperty("password")]
public string Password;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
public bool ParseLink(string link)
{
var list = link
.Replace("tg://socks?", "")
.Replace("https://t.me/socks?", "")
.Split('&');
var dict = new Dictionary<string, string>();
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;
}
}
}

View File

@@ -0,0 +1,45 @@
namespace Netch.Models.Server.Trojan
{
public class Trojan : Server
{
public Trojan()
{
this.Type = ServerType.Trojan;
}
/// <summary>
/// 密码
/// </summary>
public string Passwd;
/// <summary>
/// 伪装 SNI 标头
/// </summary>
public string SNI;
/// <summary>
/// 复用会话
/// </summary>
public bool Reuse = true;
/// <summary>
/// Session Ticket
/// </summary>
public bool Ticket = false;
/// <summary>
/// 不安全模式(跳过证书验证、跳过主机名验证)
/// </summary>
public bool Insecure = true;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
public bool ParseLink(string link)
{
return false;
}
}
}

View File

@@ -0,0 +1,30 @@
namespace Netch.Models.Server.VLess
{
public class VLess : Server
{
public VLess()
{
this.Type = ServerType.VLess;
}
/// <summary>
/// 自定义配置
/// </summary>
public bool Custom = true;
/// <summary>
/// 自定义配置文件路径
/// </summary>
public string FilePath;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
public bool ParseLink(string link)
{
return false;
}
}
}

View File

@@ -0,0 +1,30 @@
namespace Netch.Models.Server.VMess
{
public class VMess : Server
{
public VMess()
{
this.Type = ServerType.VMess;
}
/// <summary>
/// 自定义配置
/// </summary>
public bool Custom = true;
/// <summary>
/// 自定义配置文件路径
/// </summary>
public string FilePath;
/// <summary>
/// 解析链接
/// </summary>
/// <param name="link">链接</param>
/// <returns>是否成功</returns>
public bool ParseLink(string link)
{
return false;
}
}
}

13
Netch/NativeMethods.cs Normal file
View File

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

16
Netch/Netch.csproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Management" Version="5.0.0" />
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.3.8" />
</ItemGroup>
</Project>

163
Netch/Tools/Guard.cs Normal file
View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace Netch.Tools
{
public class Guard
{
/// <summary>
/// 启动信息
/// </summary>
public ProcessStartInfo StartInfo;
/// <summary>
/// 标准模式
/// </summary>
public bool Standard = true;
/// <summary>
/// 判定启动的字符串
/// </summary>
public List<string> JudgmentStarted;
/// <summary>
/// 判定停止的字符串
/// </summary>
public List<string> JudgmentStopped;
/// <summary>
/// 自动重启
/// </summary>
public bool AutoRestart;
/// <summary>
/// 启动
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 停止
/// </summary>
public void Delete()
{
this.AutoRestart = false;
try
{
this.instance?.Kill();
this.instance?.WaitForExit();
}
catch (Exception)
{
// ignore
}
}
/// <summary>
/// 进程
/// </summary>
private Process instance;
/// <summary>
/// 是否已启动
/// </summary>
private bool Started = false;
/// <summary>
/// 是否正在启动中
/// </summary>
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}");
}
}
}

84
Netch/Tools/Logger.cs Normal file
View File

@@ -0,0 +1,84 @@
using System;
using System.Diagnostics;
using System.IO;
namespace Netch.Tools
{
public class Logger
{
/// <summary>
/// 互斥锁
/// </summary>
private object mutex = new();
/// <summary>
/// 写入日志
/// </summary>
/// <param name="name"></param>
/// <param name="text"></param>
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}");
}
/// <summary>
/// 保存路径
/// </summary>
public string SavePath;
/// <summary>
/// 调试
/// </summary>
/// <param name="text"></param>
public void Debug(string text)
{
this.WriteLine("DEBUG", text);
}
/// <summary>
/// 信息
/// </summary>
/// <param name="text"></param>
public void Info(string text)
{
this.WriteLine("INFO", text);
}
/// <summary>
/// 警告
/// </summary>
/// <param name="text"></param>
public void Warning(string text)
{
this.WriteLine("WARNING", text);
}
/// <summary>
/// 错误
/// </summary>
/// <param name="text"></param>
public void Error(string text)
{
this.WriteLine("ERROR", text);
}
/// <summary>
/// 崩溃
/// </summary>
/// <param name="text"></param>
public void Fatal(string text)
{
this.WriteLine("FATAL", text);
Environment.Exit(1);
}
}
}

View File

@@ -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
{
/// <summary>
/// 索引
/// </summary>
public uint Index;
/// <summary>
/// 适配器
/// </summary>
public NetworkInterface Interface;
/// <summary>
/// 地址
/// </summary>
public IPAddress Address;
/// <summary>
/// 掩码
/// </summary>
public IPAddress Netmask;
/// <summary>
/// 网关
/// </summary>
public IPAddress Gateway;
/// <summary>
/// 获取数据
/// </summary>
/// <returns></returns>
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;
}
}
}

34
Netch/Utils/Base64.cs Normal file
View File

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

6
Netch/Utils/Config.cs Normal file
View File

@@ -0,0 +1,6 @@
namespace Netch.Utils
{
public static class Config
{
}
}

50
Netch/Utils/DNS.cs Normal file
View File

@@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Net;
namespace Netch.Utils
{
public static class DNS
{
/// <summary>
/// 缓存表
/// </summary>
private static readonly Hashtable Cache = new Hashtable();
/// <summary>
/// 获取 IP 地址
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
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;
}
}
}
}

33
Netch/Utils/FileHelper.cs Normal file
View File

@@ -0,0 +1,33 @@
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace Netch.Utils
{
public static class FileHelper
{
/// <summary>
/// 计算文件 SHA256 校验和
/// </summary>
/// <param name="name">文件路径</param>
/// <returns></returns>
public static byte[] Checksum(string name)
{
using (var algo = SHA256.Create())
{
using (var fs = File.OpenRead(name))
{
return algo.ComputeHash(fs);
}
}
}
/// <summary>
/// 比较两个文件是否完全相同
/// </summary>
/// <param name="oPath">文件路径</param>
/// <param name="nPath">文件路径</param>
/// <returns></returns>
public static bool Equals(string oPath, string nPath) => Checksum(oPath).SequenceEqual(Checksum(nPath));
}
}

73
Netch/Utils/GitHub.cs Normal file
View File

@@ -0,0 +1,73 @@
using System.Collections.Generic;
namespace Netch.Utils
{
public static class GitHub
{
/// <summary>
/// 地址
/// </summary>
public static readonly string URL = "https://api.github.com/repos/NetchX/Netch/releases";
/// <summary>
/// 检查是否有更新
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 获取单个发布
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static Models.GitHub.Release GetRelease(int id)
{
var data = HTTP.GetString($"{URL}/{id}");
return Newtonsoft.Json.JsonConvert.DeserializeObject<Models.GitHub.Release>(data);
}
/// <summary>
/// 获取发布列表
/// </summary>
/// <returns></returns>
public static List<Models.GitHub.Release> GetReleaseList()
{
var data = HTTP.GetString(URL);
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Models.GitHub.Release>>(data);
}
}
}

72
Netch/Utils/HTTP.cs Normal file
View File

@@ -0,0 +1,72 @@
using System.IO;
using System.Net;
using System.Text;
namespace Netch.Utils
{
public static class HTTP
{
/// <summary>
/// User Agent
/// </summary>
public static readonly string DefaultUA = $"Netch/{Global.VerCode}";
/// <summary>
/// 创建请求
/// </summary>
/// <param name="url">地址</param>
/// <param name="timeout">超时</param>
/// <returns></returns>
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();
}
}
}
}
}

109
Netch/Utils/Netfilter.cs Normal file
View File

@@ -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");
/// <summary>
/// 注册 Netfilter 驱动
/// </summary>
/// <returns></returns>
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;
}
}
/// <summary>
/// 更新 Netfilter 驱动
/// </summary>
/// <returns></returns>
public static bool Update()
{
if (!File.Exists(oPath))
{
return Create();
}
if (!FileHelper.Equals(oPath, nPath))
{
return Create();
}
return true;
}
/// <summary>
/// 删除 Netfilter 驱动
/// </summary>
/// <returns></returns>
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;
}
}
}

130
Netch/Utils/Ping.cs Normal file
View File

@@ -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
{
/// <summary>
/// 缓存内容
/// </summary>
private class CacheEntry
{
/// <summary>
/// 缓存时间
/// </summary>
public long Unix;
/// <summary>
/// 延迟
/// </summary>
public int Time;
}
/// <summary>
/// 缓存表
/// </summary>
private static Hashtable Cache = new Hashtable();
/// <summary>
/// 测试 ICMP 延迟
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 测试 TCP 延迟
/// </summary>
/// <param name="addr"></param>
/// <param name="port"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 获取延迟
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
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;
}
}
}
}

132
Netch/Utils/RouteHelper.cs Normal file
View File

@@ -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
{
/// <summary>
/// 将 Luid 转换为 Index 索引
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)]
public static extern ulong ConvertLuidToIndex(ulong id);
/// <summary>
/// 绑定 IP 地址Windows XP
/// </summary>
/// <param name="address"></param>
/// <param name="netmask"></param>
/// <param name="index"></param>
/// <returns></returns>
[DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CreateIPv4(string address, string netmask, ulong index);
/// <summary>
/// 绑定 IP 地址
/// </summary>
/// <param name="inet"></param>
/// <param name="address"></param>
/// <param name="cidr"></param>
/// <param name="index"></param>
/// <returns></returns>
[DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CreateUnicastIP(AddressFamily inet, string address, byte cidr, ulong index);
/// <summary>
/// 创建路由
/// </summary>
/// <param name="inet"></param>
/// <param name="address"></param>
/// <param name="cidr"></param>
/// <param name="gateway"></param>
/// <param name="index"></param>
/// <returns></returns>
[DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CreateRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index);
/// <summary>
/// 删除路由
/// </summary>
/// <param name="inet"></param>
/// <param name="address"></param>
/// <param name="cidr"></param>
/// <param name="gateway"></param>
/// <param name="index"></param>
/// <returns></returns>
[DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)]
public static extern bool DeleteRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index);
/// <summary>
/// 使用索引获取适配器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 使用名称获取适配器索引
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
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);
}
}
}

78
Netch/Utils/WinTUN.cs Normal file
View File

@@ -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");
/// <summary>
/// 注册 WinTUN 驱动
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 更新 WinTUN 驱动
/// </summary>
/// <returns></returns>
public static bool Update()
{
if (!File.Exists(oPath))
{
return Create();
}
if (!FileHelper.Equals(oPath, nPath))
{
return Create();
}
return true;
}
/// <summary>
/// 删除 WinTUN 驱动
/// </summary>
/// <returns></returns>
public static bool Delete()
{
try
{
if (File.Exists(oPath))
{
File.Delete(oPath);
}
}
catch (Exception e)
{
Global.Logger.Error($"删除 WinTUN 驱动失败:{e}");
return false;
}
return true;
}
}
}