.NET借助虚拟网卡实现一个简单异地组网工具
由于工作需要,经常需要远程客户的服务器,但是并不是所有服务器都能开外网端口,使用向日葵等软件终究还是不太方便,于是找了很多工具,包括zerotier 等,但是由于服务器在国外等有时候还不同,
于是开始自己想办法研究一个属于自己的组网工具,最后找到snltty大佬的 https://github.com/snltty/p2p-tunnel ,学习后发现是基于tun2socks实现的,
tun2socks 的优点是 把虚拟网卡的数据都打包到socket代理了,但是过滤了ping (ICmp)的包,他自行返回了 成功,这不是我要的效果
于是看了一下tun2socks 的实现,是基于tun/tap实现的,于是研究了一下,手动基于tun/tap实现了一个简易的
核心代码
1 [SupportedOSPlatform("windows")] 2 public class WinTunDriveHostedService : TunDriveHostedService 3 { 4 private readonly static string DriverPath = AppDomain.CurrentDomain.BaseDirectory + "Drivers"; 5 private const string AdapterKey = "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"; 6 private const string ConnectionKey = "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"; 7 8 9 public const int TAP_WIN_IOCTL_GET_MAC = 1; 10 public const int TAP_WIN_IOCTL_GET_VERSION = 2; 11 public const int TAP_WIN_IOCTL_GET_MTU = 3; 12 public const int TAP_WIN_IOCTL_GET_INFO = 4; 13 public const int TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT = 5; 14 public const int TAP_WIN_IOCTL_SET_MEDIA_STATUS = 6; 15 public const int TAP_WIN_IOCTL_CONFIG_DHCP_MASQ = 7; 16 public const int TAP_WIN_IOCTL_GET_LOG_LINE = 8; 17 public const int TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT = 9; 18 public const int TAP_WIN_IOCTL_CONFIG_TUN = 10; 19 20 public const uint FILE_ATTRIBUTE_SYSTEM = 0x4; 21 public const uint FILE_FLAG_OVERLAPPED = 0x40000000; 22 public const uint METHOD_BUFFERED = 0; 23 public const uint FILE_ANY_ACCESS = 0; 24 public const uint FILE_DEVICE_UNKNOWN = 0x22; 25 public WinTunDriveHostedService(IOptions<TunDriveConfig> tunDriveConfigOptions, ILogger<WinTunDriveHostedService> logger) : base(tunDriveConfigOptions, logger) 26 { 27 } 28 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 29 public static extern bool DeviceIoControl(SafeHandle device, uint IoControlCode, IntPtr InBuffer, uint InBufferSize, IntPtr OutBuffer, uint OutBufferSize, ref uint BytesReturned, IntPtr Overlapped); 30 31 32 protected override FileStream OpenDrive() 33 { 34 var className = InstallOrGetClassNameDrive(); 35 var safeFileHandle = System.IO.File.OpenHandle($@"\\.\\Global\\{className}.tap", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous); 36 return new FileStream(safeFileHandle, FileAccess.ReadWrite, 1500); 37 } 38 protected virtual string InstallOrGetClassNameDrive() 39 { 40 using (RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(ConnectionKey)) 41 { 42 var names = registryKey.GetSubKeyNames(); 43 foreach (var name in names) 44 { 45 using (var connectionRegistryKey = registryKey.OpenSubKey(name).OpenSubKey("Connection")) 46 { 47 if (connectionRegistryKey != null && connectionRegistryKey.GetValue("Name").ToString() == TunDriveConfig.TunDriveName) 48 { 49 return name; 50 } 51 } 52 } 53 54 Directory.CreateDirectory(DriverPath); 55 ZipArchive zipArchive = new ZipArchive(typeof(WinTunDriveHostedService).Assembly.GetManifestResourceStream($"RemoteNetwork.{(Environment.Is64BitOperatingSystem ? "amd64" : "i386")}.zip"), ZipArchiveMode.Read); 56 foreach (ZipArchiveEntry entry in zipArchive.Entries) 57 { 58 entry.ExtractToFile(Path.Combine(DriverPath, entry.FullName), overwrite: true); 59 } 60 StartProcess(Path.Combine(DriverPath, "tapinstall.exe"), $"install OemVista.inf TAP0901", "runas", DriverPath); 61 foreach (var name in registryKey.GetSubKeyNames()) 62 { 63 if (!names.Contains(name)) 64 { 65 using (var connectionRegistryKey = registryKey.OpenSubKey(name).OpenSubKey("Connection")) 66 { 67 if (connectionRegistryKey != null) 68 { 69 StartProcess("netsh", @$"interface set interface name=""{connectionRegistryKey.GetValue("Name")}"" newname=""{TunDriveConfig.TunDriveName}"""); 70 return name; 71 } 72 } 73 } 74 } 75 return string.Empty; 76 } 77 } 78 private static int ParseIP(string address) 79 { 80 byte[] addressBytes = address.Split('.').Select(s => byte.Parse(s)).ToArray(); 81 return addressBytes[0] | (addressBytes[1] << 8) | (addressBytes[2] << 16) | (addressBytes[3] << 24); 82 } 83 protected override void ConfigIP(string ip, string netmask) 84 { 85 StartProcess("netsh", $"interface ip set address name=\"{TunDriveConfig.TunDriveName}\" source=static addr={ip} mask={netmask} gateway=none"); 86 IntPtr intPtr = Marshal.AllocHGlobal(12); 87 Marshal.WriteInt32(intPtr, 0, ParseIP(ip)); 88 Marshal.WriteInt32(intPtr, 4, 0); 89 Marshal.WriteInt32(intPtr, 8,0); 90 uint lpBytesReturned = 0; 91 bool result = DeviceIoControl(TunStream.SafeFileHandle, 2228264, intPtr, 12u, intPtr, 12u, ref lpBytesReturned, IntPtr.Zero); 92 Marshal.FreeHGlobal(intPtr); 93 } 94 private static uint CTL_CODE(uint iDeviceType, uint iFunction, uint iMethod, uint iAccess) 95 { 96 return ((iDeviceType << 16) | (iAccess << 14) | (iFunction << 2) | iMethod); 97 } 98 public override bool ConnectionState(bool connection) 99 { 100 uint Length = 0; 101 IntPtr cconfig = Marshal.AllocHGlobal(4); 102 Marshal.WriteInt32(cconfig, connection ? 1 : 0); 103 104 var b = DeviceIoControl(TunStream.SafeFileHandle, CTL_CODE(FILE_DEVICE_UNKNOWN, TAP_WIN_IOCTL_SET_MEDIA_STATUS, METHOD_BUFFERED, FILE_ANY_ACCESS), cconfig, 4, cconfig, 4, ref Length, IntPtr.Zero); 105 StartProcess("netsh", $"netsh interface ipv4 set subinterface \"{TunDriveConfig.TunDriveName}\" mtu=\"1400\" store=persistent"); 106 return b; 107 } 108 }
liunx 核心代码
1 public class TunNetWorkFrameHostedService : BackgroundService 2 { 3 private readonly string exchangeHostName = ""; 4 private readonly int P2PPort = 61000; 5 protected readonly ILogger<TunNetWorkFrameHostedService> _logger; 6 public static TunNetWorkFrameHostedService Instance { get; private set; } 7 private readonly UdpClient udpClient; 8 private readonly System.Net.IPEndPoint remoteEndPoint = new System.Net.IPEndPoint(0, 0); 9 public TunNetWorkFrameHostedService(ILogger<TunNetWorkFrameHostedService> logger, IOptions<TunDriveConfig> tunDriveConfigOptions) 10 { 11 exchangeHostName = tunDriveConfigOptions.Value.DataExchangeHostName; 12 _logger = logger; 13 Instance = this; 14 udpClient = new UdpClient(0); if (Environment.OSVersion.Platform == PlatformID.Win32NT) 15 { 16 const int SIP_UDP_CONNRESET = -1744830452; 17 udpClient.Client.IOControl(SIP_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); 18 } 19 } 20 21 22 protected override async Task ExecuteAsync(CancellationToken stoppingToken) 23 { 24 udpClient.BeginReceive(ReceiveCallback, udpClient); 25 while (!stoppingToken.IsCancellationRequested) 26 { 27 await udpClient.SendAsync(TunDriveHostedService.Instance.Id, exchangeHostName, P2PPort, stoppingToken).ConfigureAwait(false); 28 await Task.Delay(1000*30, stoppingToken).ConfigureAwait(false); 29 } 30 } 31 void ReceiveCallback(IAsyncResult ar) 32 { 33 System.Net.IPEndPoint remoteEndPoint = new System.Net.IPEndPoint(0, 0); 34 byte[] bytes = null; 35 try 36 { 37 38 bytes = udpClient.EndReceive(ar, ref remoteEndPoint); 39 40 } 41 finally 42 { 43 udpClient.BeginReceive(ReceiveCallback, udpClient); 44 } 45 if (bytes.Length == 4) 46 { 47 return; 48 } 49 if (bytes.Length == 5) 50 { 51 if (bytes[0] == 2) 52 { 53 P2PUDPSocketHostedService.Instance.TestP2P(bytes.Skip(1).ToArray(),false); 54 } 55 return; 56 } 57 58 TunDriveHostedService.Instance.WriteFrameBuffer(bytes); 59 } 60 public virtual async Task WriteFrameBufferAsync(Memory<byte> buffer, CancellationToken stoppingToken) 61 { 62 var destId = BitConverter.ToInt32(buffer.Slice(16, 4).ToArray(), 0); 63 64 var tunNetWorkFrameSend= P2PUDPSocketHostedService.Instance.GetP2PClient(buffer.Slice(16, 4).ToArray()); 65 if (tunNetWorkFrameSend != null) 66 { 67 await tunNetWorkFrameSend.SendAsync(buffer, stoppingToken).ConfigureAwait(false); 68 return; 69 } 70 var bytes = new byte[buffer.Length + 8]; 71 buffer.Slice(12, 8).CopyTo(bytes); 72 Array.Copy(buffer.ToArray(), 0,bytes,8,buffer.Length); 73 await udpClient.SendAsync(bytes, exchangeHostName, P2PPort, stoppingToken).ConfigureAwait(false); 74 //var destId = BitConverter.ToInt32(buffer.Slice(16, 4).ToArray(), 0);// string.Join(".", buffer.Slice(16, 4).ToArray());// span[16] << 24 | span[17] << 16 | span[18] << 8 | span[19]; 75 //var sourceId = BitConverter.ToInt32(buffer.Slice(12, 4).ToArray(), 0); 76 //_logger.LogInformation($"{sourceId} 发送到{destId}"); 77 } 78 /// <summary> 79 /// 发送打洞请求 80 /// </summary> 81 /// <param name="destId"></param> 82 /// <param name="stoppingToken"></param> 83 /// <returns></returns> 84 public virtual async Task SendP2PRequestAsync(byte[] destId, CancellationToken stoppingToken) 85 { 86 using (MemoryStream memoryStream = new MemoryStream()) { 87 memoryStream.Write(TunDriveHostedService.Instance.Id); 88 memoryStream.Write(destId); 89 memoryStream.WriteByte(2); 90 memoryStream.Write(TunDriveHostedService.Instance.Id); 91 await udpClient.SendAsync(memoryStream.ToArray(), exchangeHostName, P2PPort, stoppingToken).ConfigureAwait(false); 92 } 93 94 } 95 }
以下是远程桌面的效果
客户端运行
打洞成功
测速
代码地址
https://github.com/hn-lyf/RemoteNetwork
测试客户端
https://files.cnblogs.com/files/dotnet-org-cn/linux-x64.zip?t=1717937932&download=true
https://files.cnblogs.com/files/dotnet-org-cn/win-x64.zip?t=1717937932&download=true
https://files.cnblogs.com/files/dotnet-org-cn/win-x86.zip?t=1717937932&download=true