using linker.libs; using linker.libs.extends; using Microsoft.Win32; using System.Buffers.Binary; using System.Net; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Runtime.Versioning; 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 Guid guid; private int interfaceNumber = 0; private IPAddress address; private byte prefixLength = 24; private CancellationTokenSource tokenSource; public LinkerWinTunDevice(string name, Guid guid) { this.name = name; this.guid = guid; } public bool Setup(IPAddress address, IPAddress gateway, byte prefixLength, out string error) { this.address = address; this.prefixLength = prefixLength; error = string.Empty; if (adapter != 0) { error = ($"Adapter already exists"); return false; } adapter = WinTun.WintunCreateAdapter(name, name, ref guid); if (adapter == 0) { error = ($"Failed to create adapter {Marshal.GetLastWin32Error():x2}"); Shutdown(); 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(); break; } catch (Exception) { Thread.Sleep(1000); } if (i == 4) { error = ($"Failed to set adapter ip {Marshal.GetLastWin32Error():x2}"); Shutdown(); return false; } } GetWindowsInterfaceNum(); tokenSource = new CancellationTokenSource(); return true; } private void AddIPV4() { 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(); } } 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 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 { CommandHelper.PowerShell($"start-service WinNat", [], out error); IPAddress network = NetworkHelper.ToNetworkIp(this.address, NetworkHelper.MaskValue(prefixLength)); CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error); if (string.IsNullOrWhiteSpace(error) == false) { error = "NetNat Not Supported"; string result = CommandHelper.Windows(string.Empty, ["netsh routing"]); if (result.Contains("netsh routing ip")) { error = string.Empty; } else { error += ",RRAS Not Supported"; } } } 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 void AddForward(List 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(); CommandHelper.Windows(string.Empty, commands); } public void RemoveForward(List 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(); CommandHelper.Windows(string.Empty, commands); } public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway) { if (interfaceNumber > 0) { string[] commands = ips.Select(item => { uint maskValue = NetworkHelper.MaskValue(item.PrefixLength); IPAddress mask = NetworkHelper.GetMaskIp(maskValue); IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, maskValue); return $"route add {_ip} mask {mask} {ip} metric 5 if {interfaceNumber}"; }).ToArray(); if (commands.Length > 0) { CommandHelper.Windows(string.Empty, commands); } } } public void DelRoute(LinkerTunDeviceRouteItem[] ip, bool gateway) { string[] commands = ip.Select(item => { uint maskValue = NetworkHelper.MaskValue(item.PrefixLength); IPAddress mask = NetworkHelper.GetMaskIp(maskValue); IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, maskValue); return $"route delete {_ip}"; }).ToArray(); if (commands.Length > 0) { CommandHelper.Windows(string.Empty, commands.ToArray()); } } private byte[] buffer = new byte[2 * 1024]; public unsafe ReadOnlyMemory Read() { for (; tokenSource.IsCancellationRequested == false;) { IntPtr packet = WinTun.WintunReceivePacket(session, out var packetSize); if (packet != 0) { new Span((byte*)packet, (int)packetSize).CopyTo(buffer.AsSpan(4, (int)packetSize)); ((int)packetSize).ToBytes(buffer); WinTun.WintunReleaseReceivePacket(session, packet); return buffer.AsMemory(0, (int)packetSize + 4); } 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 buffer) { if (session == 0 || tokenSource.IsCancellationRequested) return false; IntPtr packet = WinTun.WintunAllocateSendPacket(session, (uint)buffer.Length); if (packet != 0) { buffer.Span.CopyTo(new Span((byte*)packet, buffer.Length)); WinTun.WintunSendPacket(session, packet); 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; } } public void Clear() { ClearRegistry(); } private void ClearRegistry() { string[] delValues = [Name]; try { RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles"); foreach (var item in key.GetSubKeyNames()) { RegistryKey itemKey = key.OpenSubKey(item); string value = itemKey.GetValue("Description", string.Empty).ToString(); itemKey.Close(); if (delValues.Contains(value)) { try { Registry.LocalMachine.DeleteSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\{item}"); } catch (Exception) { } } } key.Close(); key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged"); foreach (var item in key.GetSubKeyNames()) { RegistryKey itemKey = key.OpenSubKey(item); string value = itemKey.GetValue("Description", string.Empty).ToString(); itemKey.Close(); if (delValues.Any(c => value.Contains($"{c} ") || value == c)) { try { Registry.LocalMachine.DeleteSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged\\{item}"); } catch (Exception) { } } } key.Close(); } catch (Exception) { } } } }