mirror of
https://github.com/snltty/linker.git
synced 2025-09-27 05:25:57 +08:00
465 lines
18 KiB
C#
465 lines
18 KiB
C#
using linker.libs;
|
|
using linker.libs.extends;
|
|
using System.Buffers.Binary;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace linker.tun
|
|
{
|
|
[SupportedOSPlatform("windows")]
|
|
internal sealed class LinkerWinTunDevice : ILinkerTunDevice
|
|
{
|
|
private string name = string.Empty;
|
|
public string Name => name;
|
|
public bool Running => session != 0;
|
|
|
|
private IntPtr waitHandle = IntPtr.Zero, adapter = IntPtr.Zero, session = IntPtr.Zero;
|
|
private int interfaceNumber = 0;
|
|
private IPAddress address;
|
|
private uint address32;
|
|
private byte prefixLength = 24;
|
|
|
|
private string defaultInterfaceName = string.Empty;
|
|
private int defaultInterfaceNumber = 0;
|
|
private IPAddress defaultInterfaceIP;
|
|
private uint defaultInterfaceIP32;
|
|
|
|
private CancellationTokenSource tokenSource;
|
|
|
|
|
|
|
|
public LinkerWinTunDevice()
|
|
{
|
|
}
|
|
|
|
public bool Setup(string name, IPAddress address, byte prefixLength, out string error)
|
|
{
|
|
this.name = name;
|
|
this.address = address;
|
|
this.address32 = NetworkHelper.ToValue(address);
|
|
this.prefixLength = prefixLength;
|
|
|
|
error = string.Empty;
|
|
if (adapter != 0)
|
|
{
|
|
error = ($"Adapter already exists");
|
|
return false;
|
|
}
|
|
Guid guid = Guid.Parse("771EF382-8718-5BC5-EBF0-A28B86142278");
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
if ((adapter = WinTun.WintunCreateAdapter(name, name, ref guid)) == 0 && (adapter = WinTun.WintunOpenAdapter(name)) == 0)
|
|
{
|
|
Shutdown();
|
|
Thread.Sleep(2000);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (adapter == 0)
|
|
{
|
|
error = ($"Failed to create adapter {Marshal.GetLastWin32Error()}");
|
|
return false;
|
|
}
|
|
|
|
uint version = WinTun.WintunGetRunningDriverVersion();
|
|
session = WinTun.WintunStartSession(adapter, 0x400000);
|
|
if (session == 0)
|
|
{
|
|
error = ($"Failed to start session");
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
waitHandle = WinTun.WintunGetReadWaitEvent(session);
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
try
|
|
{
|
|
AddIPV4();
|
|
AddIPV6();
|
|
|
|
GetWindowsInterfaceNum();
|
|
GetDefaultInterface();
|
|
tokenSource = new CancellationTokenSource();
|
|
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Thread.Sleep(1000);
|
|
}
|
|
}
|
|
error = ($"Failed to set adapter ip {Marshal.GetLastWin32Error()}");
|
|
Shutdown();
|
|
return false;
|
|
|
|
}
|
|
|
|
private void AddIPV4()
|
|
{
|
|
try
|
|
{
|
|
if (session == 0) return;
|
|
|
|
WinTun.WintunGetAdapterLUID(adapter, out ulong luid);
|
|
{
|
|
WinTun.MIB_UNICASTIPADDRESS_ROW AddressRow = default;
|
|
WinTun.InitializeUnicastIpAddressEntry(ref AddressRow);
|
|
AddressRow.sin_family = 2;
|
|
AddressRow.sin_addr = BinaryPrimitives.ReadUInt32LittleEndian(address.GetAddressBytes());
|
|
AddressRow.OnLinkPrefixLength = prefixLength;
|
|
AddressRow.DadState = 4;
|
|
AddressRow.InterfaceLuid = luid;
|
|
uint LastError = WinTun.CreateUnicastIpAddressEntry(ref AddressRow);
|
|
if (LastError != 0) throw new InvalidOperationException();
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
private void AddIPV6()
|
|
{
|
|
NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(c => c.Name == Name);
|
|
if (networkInterface != null)
|
|
{
|
|
var commands = networkInterface.GetIPProperties()
|
|
.UnicastAddresses
|
|
.Where(c => c.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
|
|
.Select(c => new IPAddress(c.Address.GetAddressBytes(), 0))
|
|
.Select(c => $"netsh interface ipv6 delete address \"{Name}\" address=\"{c}\"").ToList();
|
|
|
|
byte[] ipv6 = IPAddress.Parse("fe80::1818:1818:1818:1818").GetAddressBytes();
|
|
address.GetAddressBytes().CopyTo(ipv6, ipv6.Length - 4);
|
|
commands.Add($"netsh interface ipv6 add address \"{Name}\" address=\"{new IPAddress(ipv6)}\"");
|
|
CommandHelper.Windows(string.Empty, [.. commands]);
|
|
}
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
tokenSource?.Cancel();
|
|
if (waitHandle != 0)
|
|
{
|
|
WinTun.SetEvent(waitHandle);
|
|
}
|
|
if (session != 0)
|
|
{
|
|
WinTun.WintunEndSession(session);
|
|
}
|
|
if (adapter != 0)
|
|
{
|
|
WinTun.WintunCloseAdapter(adapter);
|
|
}
|
|
waitHandle = 0;
|
|
session = 0;
|
|
adapter = 0;
|
|
interfaceNumber = 0;
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
if (session == 0) return;
|
|
try
|
|
{
|
|
IntPtr oldSession = session;
|
|
IntPtr oldWaitHandle = waitHandle;
|
|
|
|
CommandHelper.Windows(string.Empty, new string[] { $"netsh interface set interface {Name} enable" });
|
|
session = WinTun.WintunStartSession(adapter, 0x400000);
|
|
waitHandle = WinTun.WintunGetReadWaitEvent(session);
|
|
AddIPV4();
|
|
AddIPV6();
|
|
|
|
WinTun.SetEvent(oldWaitHandle);
|
|
WinTun.WintunEndSession(oldSession);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
public void SetMtu(int value)
|
|
{
|
|
CommandHelper.Windows(string.Empty, new string[] {
|
|
$"netsh interface ipv4 set subinterface {interfaceNumber} mtu={value} store=persistent" ,
|
|
$"netsh interface ipv6 set subinterface {interfaceNumber} mtu={value} store=persistent"
|
|
});
|
|
}
|
|
|
|
public void SetNat(out string error)
|
|
{
|
|
error = string.Empty;
|
|
try
|
|
{
|
|
if (address == null || address.Equals(IPAddress.Any) || prefixLength == 0)
|
|
{
|
|
error = "NetNat need CIDR,like 10.18.18.0/24";
|
|
return;
|
|
}
|
|
CommandHelper.PowerShell($"start-service WinNat", [], out error);
|
|
CommandHelper.PowerShell($"Install-WindowsFeature -Name Routing -IncludeManagementTools", [], out error);
|
|
CommandHelper.PowerShell($"Set-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\" -Name \"IPEnableRouter\" -Value 1", [], out error);
|
|
CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", [], out error);
|
|
|
|
IPAddress network = NetworkHelper.ToNetworkIP(this.address, NetworkHelper.ToPrefixValue(prefixLength));
|
|
CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error);
|
|
|
|
string result = CommandHelper.PowerShell($"Get-NetNat", [], out string e);
|
|
if (string.IsNullOrWhiteSpace(result) == false && result.Contains($"{network}/{prefixLength}"))
|
|
{
|
|
return;
|
|
}
|
|
error = $"NetNat not supported,{error}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
}
|
|
}
|
|
|
|
public void RemoveNat(out string error)
|
|
{
|
|
error = string.Empty;
|
|
|
|
try
|
|
{
|
|
CommandHelper.PowerShell($"start-service WinNat", [], out error);
|
|
CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", [], out error);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex.Message;
|
|
}
|
|
}
|
|
|
|
|
|
public List<LinkerTunDeviceForwardItem> GetForward()
|
|
{
|
|
string str = CommandHelper.Windows(string.Empty, new string[] { $"netsh interface portproxy show v4tov4" });
|
|
IEnumerable<LinkerTunDeviceForwardItem> lines = str.Split(Environment.NewLine)
|
|
.Select(c => Regex.Replace(c, @"\s+", " ").Split(' '))
|
|
.Where(c => c.Length > 0 && c[0] == "0.0.0.0")
|
|
.Select(c =>
|
|
{
|
|
IPEndPoint dist = IPEndPoint.Parse($"{c[2]}:{c[3]}");
|
|
int port = int.Parse(c[1]);
|
|
return new LinkerTunDeviceForwardItem { ListenAddr = IPAddress.Any, ListenPort = port, ConnectAddr = dist.Address, ConnectPort = dist.Port };
|
|
});
|
|
return lines.ToList();
|
|
}
|
|
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
|
|
{
|
|
string[] commands = forwards.Where(c => c != null && c.Enable).Select(c =>
|
|
{
|
|
return $"netsh interface portproxy add v4tov4 listenaddress={c.ListenAddr} listenport={c.ListenPort} connectaddress={c.ConnectAddr} connectport={c.ConnectPort}";
|
|
}).ToArray();
|
|
if (commands.Length > 0)
|
|
CommandHelper.Windows(string.Empty, commands);
|
|
}
|
|
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
|
|
{
|
|
string[] commands = forwards.Where(c => c != null && c.Enable).Select(c =>
|
|
{
|
|
return $"netsh interface portproxy delete v4tov4 listenport={c.ListenPort} listenaddress={c.ListenAddr}";
|
|
}).ToArray();
|
|
if (commands.Length > 0)
|
|
CommandHelper.Windows(string.Empty, commands);
|
|
}
|
|
|
|
|
|
public void AddRoute(LinkerTunDeviceRouteItem[] ips)
|
|
{
|
|
if (interfaceNumber > 0)
|
|
{
|
|
string[] commands = ips.Select(item =>
|
|
{
|
|
uint maskValue = NetworkHelper.ToPrefixValue(item.PrefixLength);
|
|
IPAddress mask = NetworkHelper.ToIP(maskValue);
|
|
IPAddress _ip = NetworkHelper.ToNetworkIP(item.Address, maskValue);
|
|
|
|
return $"route add {_ip} mask {mask} {address} metric 5 if {interfaceNumber}";
|
|
}).ToArray();
|
|
if (commands.Length > 0)
|
|
{
|
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
|
LoggerHelper.Instance.Warning($"tuntap win add route: {string.Join("\r\n", commands)}");
|
|
CommandHelper.Windows(string.Empty, commands);
|
|
}
|
|
}
|
|
}
|
|
public void RemoveRoute(LinkerTunDeviceRouteItem[] ips)
|
|
{
|
|
string[] commands = ips.Select(item =>
|
|
{
|
|
uint maskValue = NetworkHelper.ToPrefixValue(item.PrefixLength);
|
|
IPAddress mask = NetworkHelper.ToIP(maskValue);
|
|
IPAddress _ip = NetworkHelper.ToNetworkIP(item.Address, maskValue);
|
|
return $"route delete {_ip}";
|
|
}).ToArray();
|
|
if (commands.Length > 0)
|
|
{
|
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
|
LoggerHelper.Instance.Warning($"tuntap win del route: {string.Join("\r\n", commands)}");
|
|
CommandHelper.Windows(string.Empty, commands.ToArray());
|
|
}
|
|
}
|
|
|
|
|
|
private byte[] buffer = new byte[65 * 1024];
|
|
public unsafe byte[] Read(out int length)
|
|
{
|
|
length = 0;
|
|
if (session == 0) return Helper.EmptyArray;
|
|
for (; tokenSource.IsCancellationRequested == false;)
|
|
{
|
|
IntPtr packetPtr = WinTun.WintunReceivePacket(session, out uint size);
|
|
length = (int)size;
|
|
|
|
if (packetPtr != 0)
|
|
{
|
|
new Span<byte>((byte*)packetPtr, length).CopyTo(buffer.AsSpan(4, length));
|
|
length.ToBytes(buffer);
|
|
WinTun.WintunReleaseReceivePacket(session, packetPtr);
|
|
length += 4;
|
|
return buffer;
|
|
}
|
|
else
|
|
{
|
|
int error = Marshal.GetLastWin32Error();
|
|
|
|
if (error == 0 || error == 259L)
|
|
{
|
|
WinTun.WaitForSingleObject(waitHandle, 0xFFFFFFFF);
|
|
}
|
|
else
|
|
{
|
|
return Helper.EmptyArray;
|
|
}
|
|
}
|
|
}
|
|
return Helper.EmptyArray;
|
|
}
|
|
public unsafe bool Write(ReadOnlyMemory<byte> packet)
|
|
{
|
|
if (session == 0 || tokenSource.IsCancellationRequested) return false;
|
|
|
|
IntPtr packetPtr = WinTun.WintunAllocateSendPacket(session, (uint)packet.Length);
|
|
if (packetPtr != 0)
|
|
{
|
|
packet.Span.CopyTo(new Span<byte>((byte*)packetPtr, packet.Length));
|
|
WinTun.WintunSendPacket(session, packetPtr);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (Marshal.GetLastWin32Error() == 111L)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void GetWindowsInterfaceNum()
|
|
{
|
|
NetworkInterface adapter = NetworkInterface.GetAllNetworkInterfaces()
|
|
.FirstOrDefault(c => c.Name == Name);
|
|
if (adapter != null)
|
|
{
|
|
interfaceNumber = adapter.GetIPProperties().GetIPv4Properties().Index;
|
|
}
|
|
|
|
}
|
|
private void GetDefaultInterface()
|
|
{
|
|
string[] lines = CommandHelper.Windows(string.Empty, new string[] { $"route print" }).Split(Environment.NewLine);
|
|
foreach (var item in lines)
|
|
{
|
|
if (item.Trim().StartsWith("0.0.0.0"))
|
|
{
|
|
string[] arr = Regex.Replace(item.Trim(), @"\s+", " ").Split(' ');
|
|
IPAddress ip = IPAddress.Parse(arr[arr.Length - 2]);
|
|
|
|
foreach (var inter in NetworkInterface.GetAllNetworkInterfaces())
|
|
{
|
|
try
|
|
{
|
|
if (ip.Equals(inter.GetIPProperties().UnicastAddresses.FirstOrDefault(c => c.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).Address))
|
|
{
|
|
defaultInterfaceName = inter.Name;
|
|
defaultInterfaceNumber = inter.GetIPProperties().GetIPv4Properties().Index;
|
|
defaultInterfaceIP = ip;
|
|
defaultInterfaceIP32 = NetworkHelper.ToValue(ip);
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<bool> CheckAvailable(bool order = false)
|
|
{
|
|
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
|
|
|
if (order)
|
|
InterfaceOrder(interfaces);
|
|
NetworkInterface networkInterface = interfaces.FirstOrDefault(c => c.Name == Name || c.Description == $"{Name} Tunnel" || c.Name.Contains(Name));
|
|
|
|
UnicastIPAddressInformation firstIpv4 = networkInterface?.GetIPProperties()
|
|
.UnicastAddresses.FirstOrDefault(c => c.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
|
|
|
|
if (networkInterface == null || firstIpv4 == null || firstIpv4.Address == null || firstIpv4.Address.Equals(address) == false)
|
|
{
|
|
return false;
|
|
}
|
|
return await Task.FromResult(true);
|
|
}
|
|
private void InterfaceOrder(NetworkInterface[] interfaces)
|
|
{
|
|
NetworkInterface linker = interfaces.FirstOrDefault(c => c.Name == Name || c.Description == $"{Name} Tunnel" || c.Name.Contains(Name));
|
|
NetworkInterface first = interfaces.FirstOrDefault();
|
|
|
|
if (linker != null && linker.Name != first.Name)
|
|
{
|
|
UnicastIPAddressInformation firstIpv4 = linker.GetIPProperties().UnicastAddresses.FirstOrDefault(c => c.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
|
|
if (firstIpv4 == null || firstIpv4.Address == null || firstIpv4.Address.Equals(address) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
int metricv4 = 0;
|
|
int metricv6 = 0;
|
|
List<string> commands = new List<string> {
|
|
$"netsh interface ipv4 set interface \"{Name}\" metric={++metricv4}",
|
|
$"netsh interface ipv6 set interface \"{Name}\" metric={++metricv6}"
|
|
};
|
|
commands.AddRange(interfaces
|
|
.Where(c => c.Name != Name)
|
|
.Select(c => $"netsh interface ipv4 set interface \"{c.Name}\" metric={++metricv4}"));
|
|
commands.AddRange(interfaces
|
|
.Where(c => c.Name != Name)
|
|
.Select(c => $"netsh interface ipv6 set interface \"{c.Name}\" metric={++metricv6}"));
|
|
commands.Add(string.Empty);
|
|
foreach (var command in commands)
|
|
{
|
|
CommandHelper.Windows(string.Empty, [command]);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|