Refactor TUNController

This commit is contained in:
ChsBuffer
2021-05-09 15:31:43 +08:00
parent 60dd3c8965
commit 74caeeaf42
10 changed files with 286 additions and 324 deletions

View File

@@ -8,8 +8,8 @@ using System.Threading.Tasks;
using Netch.Forms;
using Netch.Interfaces;
using Netch.Models;
using Netch.Models.Adapter;
using Netch.Servers.Socks5;
using Netch.Utils;
namespace Netch.Controllers
{
@@ -21,8 +21,6 @@ namespace Netch.Controllers
protected override IEnumerable<string> StartedKeywords { get; set; } = new[] { "└" };
private readonly OutboundAdapter _outbound = new();
protected override Encoding? InstanceOutputEncoding { get; } = Encoding.UTF8;
private LogForm? _form;
@@ -34,7 +32,9 @@ namespace Netch.Controllers
_form = new LogForm(Global.MainForm);
_form.CreateControl();
var argument = new StringBuilder($@"-i \Device\NPF_{_outbound.NetworkInterface.Id}");
var outboundNetworkInterface = NetworkInterfaceUtils.GetBest();
var argument = new StringBuilder($@"-i \Device\NPF_{outboundNetworkInterface.Id}");
if (server is Socks5 socks5 && !socks5.Auth())
argument.Append($" --destination {server.AutoResolveHostname()}:{server.Port}");
else

View File

@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Netch.Enums;
using Netch.Interfaces;
using Netch.Interops;
using Netch.Models;
using Netch.Models.Adapter;
using Netch.Servers.Socks5;
using Netch.Utils;
using static Netch.Interops.tun2socks;
@@ -17,29 +14,32 @@ namespace Netch.Controllers
{
public class TUNController : IModeController
{
private readonly List<string> _directIPs = new();
private readonly List<string> _proxyIPs = new();
public readonly DNSController DNSController = new();
public string Name { get; } = "tun2socks";
private readonly OutboundAdapter _outboundAdapter = new();
private IAdapter _tunAdapter = null!;
private IPAddress _serverAddresses = null!;
public string Name => "tun2socks";
private const string DummyDns = "6.6.6.6";
private readonly DNSController _aioDnsController = new();
private NetRoute _outbound;
private NetRoute _tun;
private IPAddress _serverAddresses = null!;
private Mode _mode = null!;
public void Start(in Mode mode)
{
_mode = mode;
var server = MainController.Server!;
_serverAddresses = DnsUtils.Lookup(server.Hostname)!; // server address have been cached when MainController.Start
IPAddress address;
(_outbound, address) = NetRoute.GetBestRouteTemplate();
CheckDriver();
Dial(NameList.TYPE_ADAPMTU, "1500");
Dial(NameList.TYPE_BYPBIND, _outboundAdapter.Address.ToString());
Dial(NameList.TYPE_BYPBIND, address.ToString());
Dial(NameList.TYPE_BYPLIST, "disabled");
#region Server
@@ -83,40 +83,115 @@ namespace Netch.Controllers
else
{
MainController.PortCheck(Global.Settings.AioDNS.ListenPort, "DNS");
DNSController.Start();
_aioDnsController.Start();
Dial(NameList.TYPE_DNSADDR, $"127.0.0.1:{Global.Settings.AioDNS.ListenPort}");
}
#endregion
Global.Logger.Debug("tun2socks init");
if (!Init())
throw new MessageException("tun2socks start failed, reboot your system and start again.");
_tunAdapter = new TunAdapter();
switch (mode.Type)
{
case ModeType.ProxyRuleIPs when Global.Settings.TUNTAP.ProxyDNS:
case ModeType.BypassRuleIPs:
_tunAdapter.NetworkInterface.SetDns(DummyDns);
break;
}
var tunIndex = (int)Interops.RouteHelper.ConvertLuidToIndex(tun_luid());
_tun = NetRoute.TemplateBuilder(Global.Settings.TUNTAP.Gateway, tunIndex);
RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork,
Interops.RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork,
Global.Settings.TUNTAP.Address,
(byte)Utils.Utils.SubnetToCidr(Global.Settings.TUNTAP.Netmask),
_tunAdapter.InterfaceIndex);
(ulong)tunIndex);
SetupRouteTable(mode);
}
private readonly string BinDriver = Path.Combine(Global.NetchDir, @"bin\wintun.dll");
private readonly string SysDriver = $@"{Environment.SystemDirectory}\wintun.dll";
#region Route
private void SetupRouteTable(Mode mode)
{
Global.MainForm.StatusText(i18N.Translate("Setup Route Table Rule"));
Global.Logger.Info("设置路由规则");
if (!IPAddress.IsLoopback(_serverAddresses))
// Server Address
RouteUtils.CreateRoute(_outbound.FillTemplate(_serverAddresses.ToString(), 32));
// Global Bypass IPs
foreach (var ip in Global.Settings.TUNTAP.BypassIPs)
RouteUtils.CreateRouteFill(_outbound, ip);
var tunNetworkInterface = NetworkInterfaceUtils.Get(_tun.InterfaceIndex);
switch (mode.Type)
{
case ModeType.ProxyRuleIPs:
// rules
RouteUtils.CreateRouteFill(_tun, mode.GetRules());
if (Global.Settings.TUNTAP.ProxyDNS)
{
tunNetworkInterface.SetDns(DummyDns);
// proxy dummy dns
RouteUtils.CreateRoute(_tun.FillTemplate(DummyDns, 32));
if (!Global.Settings.TUNTAP.UseCustomDNS)
// proxy AioDNS other dns
RouteUtils.CreateRoute(_tun.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.OtherDNS), 32));
}
break;
case ModeType.BypassRuleIPs:
RouteUtils.CreateRouteFill(_outbound, mode.GetRules());
tunNetworkInterface.SetDns(DummyDns);
if (!Global.Settings.TUNTAP.UseCustomDNS)
// bypass AioDNS other dns
RouteUtils.CreateRoute(_tun.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.ChinaDNS), 32));
NetworkInterfaceUtils.SetInterfaceMetric(_tun.InterfaceIndex, 0);
RouteUtils.CreateRoute(_tun.FillTemplate("0.0.0.0", 0));
break;
}
}
private bool ClearRouteTable()
{
if (!IPAddress.IsLoopback(_serverAddresses))
RouteUtils.DeleteRoute(_outbound.FillTemplate(_serverAddresses.ToString(), 32));
foreach (var ip in Global.Settings.TUNTAP.BypassIPs)
RouteUtils.DeleteRouteFill(_outbound, ip);
switch (_mode.Type)
{
case ModeType.BypassRuleIPs:
RouteUtils.DeleteRouteFill(_outbound, _mode.GetRules());
NetworkInterfaceUtils.SetInterfaceMetric(_outbound.InterfaceIndex);
break;
}
return true;
}
#endregion
public void Stop()
{
var tasks = new[]
{
Task.Run(Free),
Task.Run(ClearRouteTable),
Task.Run(_aioDnsController.Stop)
};
Task.WaitAll(tasks);
}
private void CheckDriver()
{
var binHash = Utils.Utils.SHA256CheckSum(BinDriver);
var sysHash = Utils.Utils.SHA256CheckSum(SysDriver);
string binDriver = Path.Combine(Global.NetchDir, @"bin\wintun.dll");
string sysDriver = $@"{Environment.SystemDirectory}\wintun.dll";
var binHash = Utils.Utils.SHA256CheckSum(binDriver);
var sysHash = Utils.Utils.SHA256CheckSum(sysDriver);
Global.Logger.Info("自带 wintun.dll Hash: " + binHash);
Global.Logger.Info("系统 wintun.dll Hash: " + sysHash);
if (binHash == sysHash)
@@ -124,7 +199,8 @@ namespace Netch.Controllers
try
{
File.Copy(BinDriver, SysDriver, true);
Global.Logger.Info("Copy wintun.dll to System Directory");
File.Copy(binDriver, sysDriver, true);
}
catch (Exception e)
{
@@ -132,194 +208,5 @@ namespace Netch.Controllers
throw new MessageException($"Failed to copy wintun.dll to system directory: {e.Message}");
}
}
/// <summary>
/// TUN/TAP停止
/// </summary>
public void Stop()
{
var tasks = new[]
{
Task.Run(Free),
Task.Run(ClearRouteTable),
Task.Run(DNSController.Stop)
};
Task.WaitAll(tasks);
}
/// <summary>
/// 设置绕行规则
/// </summary>
/// <returns>是否设置成功</returns>
private void SetupRouteTable(Mode mode)
{
Global.MainForm.StatusText(i18N.Translate("SetupBypass"));
Global.Logger.Info("设置路由规则");
Global.Logger.Info("绕行 → 服务器 IP");
if (!IPAddress.IsLoopback(_serverAddresses))
RouteAction(Action.Create, $"{_serverAddresses}/32", RouteType.Outbound);
Global.Logger.Info("绕行 → 全局绕过 IP");
RouteAction(Action.Create, Global.Settings.TUNTAP.BypassIPs, RouteType.Outbound);
#region Rule IPs
switch (mode.Type)
{
case ModeType.ProxyRuleIPs:
// 代理规则 IP
Global.Logger.Info("代理 → 规则 IP");
RouteAction(Action.Create, mode.GetRules(), RouteType.TUNTAP);
if (Global.Settings.TUNTAP.ProxyDNS)
{
Global.Logger.Info("代理 → 占位 DNS");
RouteAction(Action.Create, $"{DummyDns}/32", RouteType.TUNTAP);
if (!Global.Settings.TUNTAP.UseCustomDNS)
{
Global.Logger.Info("代理 → AioDNS OtherDNS");
var otherDns = Global.Settings.AioDNS.OtherDNS;
RouteAction(Action.Create, $"{otherDns[..otherDns.IndexOf(':')]}/32", RouteType.TUNTAP);
}
}
break;
case ModeType.BypassRuleIPs:
// 绕过规则 IP
Global.Logger.Info("绕行 → 规则 IP");
RouteAction(Action.Create, mode.GetRules(), RouteType.Outbound);
break;
}
#endregion
if (mode.Type == ModeType.BypassRuleIPs)
{
Global.Logger.Info("代理 → 全局");
SetInterface(RouteType.TUNTAP, 0);
RouteAction(Action.Create, "0.0.0.0/0", RouteType.TUNTAP, record: false);
}
}
private void SetInterface(RouteType routeType, int? metric = null)
{
var adapter = routeType == RouteType.Outbound ? _outboundAdapter : _tunAdapter;
var arguments = $"interface ip set interface {adapter.InterfaceIndex} ";
if (metric != null)
arguments += $"metric={metric} ";
Utils.Utils.ProcessRunHiddenAsync("netsh", arguments).Wait();
}
/// <summary>
/// 清除绕行规则
/// </summary>
private bool ClearRouteTable()
{
var mode = MainController.Mode!;
RouteAction(Action.Delete, _directIPs, RouteType.Outbound);
RouteAction(Action.Delete, _proxyIPs, RouteType.TUNTAP);
_directIPs.Clear();
_proxyIPs.Clear();
if (mode.Type == ModeType.BypassRuleIPs)
{
SetInterface(RouteType.Outbound);
}
return true;
}
#region Package
private void RouteAction(Action action, in IEnumerable<string> ipNetworks, RouteType routeType, int metric = 0, bool record = true)
{
foreach (var address in ipNetworks)
RouteAction(action, address, routeType, metric);
}
private bool RouteAction(Action action, in string ipNetwork, RouteType routeType, int metric = 0, bool record = true)
{
#region
if (!TryParseIPNetwork(ipNetwork, out var ip, out var cidr))
return false;
IAdapter adapter = routeType switch
{
RouteType.Outbound => _outboundAdapter,
RouteType.TUNTAP => _tunAdapter,
_ => throw new ArgumentOutOfRangeException(nameof(routeType), routeType, null)
};
List<string> ipList = routeType switch
{
RouteType.Outbound => _directIPs,
RouteType.TUNTAP => _proxyIPs,
_ => throw new ArgumentOutOfRangeException(nameof(routeType), routeType, null)
};
string gateway = adapter.Gateway.ToString();
var index = adapter.InterfaceIndex;
#endregion
bool result;
switch (action)
{
case Action.Create:
result = RouteHelper.CreateRoute(AddressFamily.InterNetwork, ip, (byte)cidr, gateway, index, metric);
if (record)
ipList.Add(ipNetwork);
break;
case Action.Delete:
result = RouteHelper.DeleteRoute(AddressFamily.InterNetwork, ip, (byte)cidr, gateway, index, metric);
break;
default:
throw new ArgumentOutOfRangeException(nameof(action), action, null);
}
Global.Logger.Debug($"{action}Route(\"{ip}\", {cidr}, \"{gateway}\", {index}, {metric})");
if (!result)
Global.Logger.Warning($"Failed to invoke {action}Route(\"{ip}\", {cidr}, \"{gateway}\", {index}, {metric})");
return result;
}
bool TryParseIPNetwork(string ipNetwork, out string ip, out int cidr)
{
ip = null!;
cidr = 0;
var s = ipNetwork.Split('/');
if (s.Length != 2)
{
Global.Logger.Warning($"Failed to parse rule {ipNetwork}");
return false;
}
ip = s[0];
cidr = int.Parse(s[1]);
return true;
}
private enum RouteType
{
Outbound,
TUNTAP
}
private enum Action
{
Create,
Delete
}
#endregion
}
}

View File

@@ -1,14 +0,0 @@
using System.Net;
using System.Net.NetworkInformation;
namespace Netch.Interfaces
{
public interface IAdapter
{
ulong InterfaceIndex { get; }
IPAddress Gateway { get; }
NetworkInterface NetworkInterface { get; }
}
}

View File

@@ -41,6 +41,7 @@ namespace Netch.Interops
public static bool Init()
{
Global.Logger.Debug("tun2socks init");
return tun_init();
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Net;
using System.Net.NetworkInformation;
using Netch.Interfaces;
using Netch.Utils;
using Vanara.PInvoke;
namespace Netch.Models.Adapter
{
public class OutboundAdapter : IAdapter
{
public OutboundAdapter()
{
// 寻找出口适配器
if (IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var pRoute) != 0)
{
throw new MessageException("GetBestRoute 搜索失败");
}
NetworkInterface = NetworkInterfaceUtils.Get((int)pRoute.dwForwardIfIndex);
Address = new IPAddress(pRoute.dwForwardNextHop.S_addr);
InterfaceIndex = (ulong)pRoute.dwForwardIfIndex;
Gateway = new IPAddress(pRoute.dwForwardNextHop.S_un_b);
Global.Logger.Info($"出口 网关 地址:{Gateway}");
Global.Logger.Info($"出口适配器:{NetworkInterface.Name} {NetworkInterface.Id} {NetworkInterface.Description}, index: {InterfaceIndex}");
}
public IPAddress Address { get; }
/// <summary>
/// 索引
/// </summary>
public ulong InterfaceIndex { get; }
/// <summary>
/// 网关
/// </summary>
public IPAddress Gateway { get; }
public NetworkInterface NetworkInterface { get; }
}
}

View File

@@ -1,28 +0,0 @@
using System.Net;
using System.Net.NetworkInformation;
using Netch.Interfaces;
using Netch.Interops;
using Netch.Utils;
namespace Netch.Models.Adapter
{
public class TunAdapter : IAdapter
{
public TunAdapter()
{
InterfaceIndex = RouteHelper.ConvertLuidToIndex(tun2socks.tun_luid());
NetworkInterface = NetworkInterfaceUtils.Get((int)InterfaceIndex);
Gateway = IPAddress.Parse(Global.Settings.TUNTAP.Gateway);
Global.Logger.Info($"WinTUN 适配器:{NetworkInterface.Name} {NetworkInterface.Id} {NetworkInterface.Description}, index: {InterfaceIndex}");
}
public ulong InterfaceIndex { get; }
public IPAddress Gateway { get; }
public NetworkInterface NetworkInterface { get; }
}
}

49
Netch/Models/NetRoute.cs Normal file
View File

@@ -0,0 +1,49 @@
using System;
using System.Net;
using Vanara.PInvoke;
namespace Netch.Models
{
public struct NetRoute
{
public static NetRoute TemplateBuilder(string gateway, int interfaceIndex, int metric = 0)
{
return new()
{
Gateway = gateway,
InterfaceIndex = interfaceIndex,
Metric = metric
};
}
public static (NetRoute, IPAddress address) GetBestRouteTemplate()
{
if (IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route) != 0)
throw new MessageException("GetBestRoute 搜索失败");
var address = new IPAddress(route.dwForwardNextHop.S_addr);
var gateway = new IPAddress(route.dwForwardNextHop.S_un_b);
return (TemplateBuilder(gateway.ToString(), (int)route.dwForwardIfIndex), address);
}
public int InterfaceIndex;
public string Gateway;
public string Network;
public byte Cidr;
public int Metric;
public NetRoute FillTemplate(string network, byte cidr, int? metric = null)
{
Network = network;
Cidr = cidr;
if (metric != null)
Metric = (int)metric;
return this;
}
}
}

View File

@@ -1,12 +1,31 @@
using System;
using System.Linq;
using System.Management;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using Netch.Models;
using Vanara.PInvoke;
namespace Netch.Utils
{
public static class NetworkInterfaceUtils
{
public static NetworkInterface GetBest(AddressFamily addressFamily = AddressFamily.InterNetwork)
{
var ipAddress = addressFamily switch
{
AddressFamily.InterNetwork => "114.114.114.114",
AddressFamily.InterNetworkV6 => throw new NotImplementedException(),
_ => throw new ArgumentOutOfRangeException(nameof(addressFamily), addressFamily, null)
};
if (IpHlpApi.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0), 0, out var route) != 0)
throw new MessageException("GetBestRoute 搜索失败");
return Get((int)route.dwForwardIfIndex);
}
/// <summary>
/// </summary>
/// <param name="interfaceIndex"></param>
@@ -14,25 +33,38 @@ namespace Netch.Utils
/// <returns></returns>
public static NetworkInterface Get(int interfaceIndex)
{
return NetworkInterface.GetAllNetworkInterfaces()
.First(n =>
{
var ipProperties = n.GetIPProperties();
int index;
if (n.Supports(NetworkInterfaceComponent.IPv4))
index = ipProperties.GetIPv4Properties().Index;
else if (n.Supports(NetworkInterfaceComponent.IPv6))
index = ipProperties.GetIPv6Properties().Index;
else
return false;
return NetworkInterface.GetAllNetworkInterfaces().First(n => n.GetIndex() == interfaceIndex);
}
return index == interfaceIndex;
});
public static NetworkInterface Get(string description)
{
return NetworkInterface.GetAllNetworkInterfaces().First(n => n.Description == description);
}
public static void SetInterfaceMetric(int interfaceIndex, int? metric = null)
{
var arguments = $"interface ip set interface {interfaceIndex} ";
if (metric != null)
arguments += $"metric={metric} ";
Utils.ProcessRunHiddenAsync("netsh", arguments).Wait();
}
}
public static class NetworkInterfaceExtension
{
public static int GetIndex(this NetworkInterface ni)
{
var ipProperties = ni.GetIPProperties();
if (ni.Supports(NetworkInterfaceComponent.IPv4))
return ipProperties.GetIPv4Properties().Index;
if (ni.Supports(NetworkInterfaceComponent.IPv6))
return ipProperties.GetIPv6Properties().Index;
throw new Exception();
}
public static void SetDns(this NetworkInterface ni, string primaryDns, string? secondDns = null)
{
void VerifyDns(ref string s)

69
Netch/Utils/RouteUtils.cs Normal file
View File

@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using Netch.Interops;
using Netch.Models;
namespace Netch.Utils
{
public static class RouteUtils
{
public static void CreateRouteFill(NetRoute template, IEnumerable<string> rules, int? metric = null)
{
foreach (var rule in rules)
CreateRouteFill(template, rule, metric);
}
public static bool CreateRouteFill(NetRoute template, string rule, int? metric = null)
{
if (!TryParseIPNetwork(rule, out var network, out var cidr))
{
Global.Logger.Warning($"invalid rule {rule}");
return false;
}
return CreateRoute(template.FillTemplate(network, (byte)cidr, metric));
}
public static bool CreateRoute(NetRoute o)
{
return RouteHelper.CreateRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric);
}
public static void DeleteRouteFill(NetRoute template, IEnumerable<string> rules, int? metric = null)
{
foreach (var rule in rules)
DeleteRouteFill(template, rule, metric);
}
public static bool DeleteRouteFill(NetRoute template, string rule, int? metric = null)
{
if (!TryParseIPNetwork(rule, out var network, out var cidr))
{
Global.Logger.Warning($"invalid rule {rule}");
return false;
}
return DeleteRoute(template.FillTemplate(network, (byte)cidr, metric));
}
public static bool DeleteRoute(NetRoute o)
{
return RouteHelper.DeleteRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric);
}
public static bool TryParseIPNetwork(string ipNetwork, [NotNullWhen(true)] out string? ip, out int cidr)
{
ip = null;
cidr = 0;
var s = ipNetwork.Split('/');
if (s.Length != 2)
return false;
ip = s[0];
cidr = int.Parse(s[1]);
return true;
}
}
}

View File

@@ -275,5 +275,15 @@ namespace Netch.Utils
return host;
}
public static string GetHostFromUri(string str)
{
var startIndex = str.LastIndexOf('/');
if (startIndex != -1)
str = str[(startIndex + 1)..];
var endIndex = str.IndexOf(':');
return endIndex == -1 ? str : str[..endIndex];
}
}
}