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

26
.github/workflows/build.yml vendored Normal file
View File

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

49
.github/workflows/release.yml vendored Normal file
View File

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

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/.vs
/.idea
/packages
/TestResults

30
Netch.sln Normal file
View File

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

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

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
<p align="center"><img src="https://github.com/NetchX/Netch/blob/master/Netch/Resources/Netch.png?raw=true" width="128" /></p>
<div align="center">
# 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)
</div>
## 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
<a href="https://www.jetbrains.com/?from=Netch"><img src="jetbrains.svg" alt="JetBrains" width="200"/></a>
- [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)

3
Tests/.gitignore vendored Normal file
View File

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

15
Tests/Global.cs Normal file
View File

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

18
Tests/Tests.csproj Normal file
View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
</Project>

40
build.ps1 Normal file
View File

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

15
clean.ps1 Normal file
View File

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

43
jetbrains.svg Normal file
View File

@@ -0,0 +1,43 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="263" height="147" viewBox="0 0 263 147">
<defs>
<linearGradient id="linear-gradient" x1="54.4568" y1="122.5936" x2="251.779" y2="10.2057" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00adee"/>
<stop offset="1" stop-color="#9f76a6"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="80.247" y1="38.7607" x2="241.2622" y2="10.9511" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ec037c"/>
<stop offset="1" stop-color="#9f76a6"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="75.7205" y1="33.5582" x2="127.8253" y2="123.9392" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ec037c"/>
<stop offset="1" stop-color="#5c2d90"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="7.4647" y1="44.578" x2="129.4543" y2="125.0813" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#44c7f4"/>
<stop offset="1" stop-color="#5c2d90"/>
</linearGradient>
</defs>
<g>
<path d="M261.1839,10.3622a9.6784,9.6784,0,0,0-14.7448-8.2463l0-.0006L20.6942,136.0746h0a4.6974,4.6974,0,1,0,4.1326,8.4348h0q0.09-.0467.1784-0.097l230.5273-125.25a9.653,9.653,0,0,0,1.1508-.6253l0.0332-.018-0.0014-.0023A9.6682,9.6682,0,0,0,261.1839,10.3622Z" fill="url(#linear-gradient)"/>
<path d="M261.1839,10.3622A9.6782,9.6782,0,0,0,251.5057.684q-0.2747,0-.5456.0157-0.25.0143-.4975,0.041L76.7981,25.4187A13.7347,13.7347,0,1,0,83.5355,51.983L252.8044,19.9512a9.6363,9.6363,0,0,0,1.0358-.196l0.02-.0039,0-.0008A9.6811,9.6811,0,0,0,261.1839,10.3622Z" fill="url(#linear-gradient-2)"/>
<path d="M145.2028,123.63a17.2372,17.2372,0,0,0-3.0637-9.4254L91.3045,32.3521A13.7366,13.7366,0,0,0,66.1132,42.9507h0a13.6332,13.6332,0,0,0,1.043,2.4984s45.2334,86.37,45.5824,86.9979q0.3089,0.556.6567,1.0861h0A17.32,17.32,0,0,0,145.2028,123.63Z" fill="url(#linear-gradient-3)"/>
<path d="M145.2028,123.63a17.2979,17.2979,0,0,0-7.63-13.9419h0a17.3061,17.3061,0,0,0-2.6994-1.4911L9.5484,38.9679a6.064,6.064,0,0,0-6.5074,10.187l114.3963,88.704A17.3191,17.3191,0,0,0,145.2028,123.63Z" fill="url(#linear-gradient-4)"/>
<g>
<rect x="69" y="51" width="70" height="70"/>
<g>
<rect x="75.038" y="107.8746" width="26.2498" height="4.375" fill="#fff"/>
<g>
<path d="M74.7429,69.4315L76.78,67.5082a2.31,2.31,0,0,0,1.7929,1.0594A1.33,1.33,0,0,0,79.8607,66.97V59.75h3.1456v7.2366a4.2386,4.2386,0,0,1-1.1246,3.2108,4.2989,4.2989,0,0,1-3.1293,1.1572A4.6592,4.6592,0,0,1,74.7429,69.4315Z" fill="#fff"/>
<path d="M83.7394,59.75h9.1761v2.673H86.8688v1.744H92.345v2.4937H86.8688V68.47H92.997v2.6893H83.7394V59.75Z" fill="#fff"/>
<path d="M97.049,62.5208H93.6426V59.75h9.9911v2.7708h-3.4227v8.6383H97.049V62.5208Z" fill="#fff"/>
<path d="M75.0363,73.8257h5.8511A4.2728,4.2728,0,0,1,84,74.8363a2.5675,2.5675,0,0,1,.7335,1.858v0.0326a2.6407,2.6407,0,0,1-1.76,2.5425,2.7686,2.7686,0,0,1,2.2655,2.7871v0.0326c0,1.9558-1.5973,3.1456-4.3191,3.1456H75.0363V73.8257Zm6.5846,3.5206c0-.6357-0.5052-0.9779-1.4343-0.9779h-2.07v2.0047h1.9884c0.9616,0,1.5158-.326,1.5158-0.9942V77.3463ZM80.5289,80.59H78.1166v2.1025h2.4448c0.9779,0,1.5158-.3749,1.5158-1.0431V81.6165C82.0773,80.9972,81.5883,80.59,80.5289,80.59Z" fill="#fff"/>
<path d="M85.7116,73.8257h5.3949a5.0512,5.0512,0,0,1,3.7161,1.2224,3.5623,3.5623,0,0,1,1.01,2.6567v0.0326a3.6146,3.6146,0,0,1-2.3469,3.5205l2.7218,3.9769H92.5733l-2.2981-3.4553H88.8735v3.4553H85.7116V73.8257Zm5.2644,5.4764a1.433,1.433,0,0,0,1.6951-1.3528V77.9167c0-.9128-0.6682-1.3691-1.7114-1.3691H88.8735v2.7545H90.976Z" fill="#fff"/>
<path d="M99.5324,73.7443H102.58l4.8571,11.4905h-3.39l-0.815-2.0536H98.8153L98,85.2348H94.6917Zm2.7707,6.9758-1.2712-3.2271L99.7443,80.72h2.5589Z" fill="#fff"/>
<path d="M107.8117,73.8257h3.1619V85.2348h-3.1619V73.8257Z" fill="#fff"/>
<path d="M111.7558,73.8257h2.95l4.694,6.0306V73.8257h3.1294V85.2348h-2.7545l-4.89-6.2587v6.2587h-3.1293V73.8257Z" fill="#fff"/>
<path d="M122.7274,83.54l1.76-2.1025a5.9106,5.9106,0,0,0,3.7,1.3691c0.8638,0,1.32-.2934,1.32-0.7824V81.9914c0-.489-0.3749-0.7335-1.9395-1.1084-2.4285-.5541-4.3029-1.2387-4.3029-3.5694V77.2811c0-2.1188,1.6788-3.6509,4.417-3.6509a7.1807,7.1807,0,0,1,4.694,1.5158l-1.5809,2.2329a5.6006,5.6006,0,0,0-3.1946-1.1246c-0.766,0-1.1409.31-1.1409,0.7334V77.02c0,0.5216.3912,0.75,1.9884,1.1083,2.6077,0.57,4.2377,1.418,4.2377,3.5531v0.0326c0,2.3307-1.8418,3.7161-4.6126,3.7161A7.9992,7.9992,0,0,1,122.7274,83.54Z" fill="#fff"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

39
scripts/download.ps1 Normal file
View File

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

View File

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

View File

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