1 // 2 // Copyright (c) 2010-2022 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 8 #if PLATFORM_WINDOWS 9 using System; 10 using System.Net.NetworkInformation; 11 using System.Linq; 12 using System.Threading; 13 using System.IO; 14 using System.Runtime.InteropServices; 15 using System.Windows; 16 using Microsoft.Win32; 17 using Microsoft.Win32.SafeHandles; 18 using Antmicro.Renode.Core; 19 using Antmicro.Renode.Core.Structure; 20 using Antmicro.Renode.Peripherals; 21 using Antmicro.Migrant.Hooks; 22 using Antmicro.Renode.Logging; 23 using Antmicro.Renode.Utilities; 24 using Antmicro.Renode.Network; 25 using Antmicro.Migrant; 26 using System.Diagnostics; 27 using Antmicro.Renode.Exceptions; 28 using System.Security; 29 30 namespace Antmicro.Renode.HostInterfaces.Network 31 { 32 public sealed class WindowsTapInterface : ITapInterface, IHasOwnLife, IDisposable 33 { WindowsTapInterface(string name)34 public WindowsTapInterface(string name) 35 { 36 MAC = EmulationManager.Instance.CurrentEmulation.MACRepository.GenerateUniqueMAC(); 37 InterfaceName = name ?? ""; 38 Init(); 39 } 40 Dispose()41 public void Dispose() 42 { 43 if(isInDummyMode) 44 { 45 return; 46 } 47 48 if(handle != null) 49 { 50 // inform the device that it is disconnected 51 ChangeDeviceStatus(false); 52 } 53 stream?.Close(); 54 handle?.Close(); 55 } 56 Pause()57 public void Pause() 58 { 59 lock(lockObject) 60 { 61 if(thread != null) 62 { 63 cancellationTokenSource.Cancel(); //notify the thread to finish its work 64 thread.Join(); 65 thread = null; 66 } 67 } 68 } 69 ReceiveFrame(EthernetFrame frame)70 public void ReceiveFrame(EthernetFrame frame) 71 { 72 if(stream == null) 73 { 74 this.Log(LogLevel.Error, "Stream null on sending the frame to the TAP interface"); 75 return; 76 } 77 stream.Write(frame.Bytes, 0, frame.Bytes.Length); 78 stream.Flush(); 79 this.Log(LogLevel.Noisy, "{0} byte frame sent to the TAP interface", frame.Bytes.Length); 80 } 81 Resume()82 public void Resume() 83 { 84 lock(lockObject) 85 { 86 if(thread == null) 87 { 88 cancellationTokenSource = new CancellationTokenSource(); 89 thread = new Thread(() => TransmitLoop(cancellationTokenSource.Token)) 90 { 91 Name = this.GetType().Name, 92 IsBackground = true 93 }; 94 thread.Start(); 95 } 96 } 97 } 98 Start()99 public void Start() 100 { 101 Resume(); 102 } 103 104 public bool IsPaused => thread == null; 105 106 public string InterfaceName { get; } 107 108 public MACAddress MAC { get; set; } 109 public event Action<EthernetFrame> FrameReady; 110 GetDeviceGuid(string name)111 private Guid? GetDeviceGuid(string name) 112 { 113 var adapters = Registry.LocalMachine.OpenSubKey(AdapterRegistryBranch); 114 if(adapters == null) 115 { 116 return null; 117 } 118 var connections = Registry.LocalMachine.OpenSubKey(ConnectionRegistryBranch); 119 foreach(string subkey in adapters.GetSubKeyNames()) 120 { 121 try 122 { 123 var adapter = adapters.OpenSubKey(subkey); 124 125 if(adapter == null) 126 { 127 return null; 128 } 129 var adapterType = (string)adapter.GetValue("ComponentId", ""); 130 //make sure whether the adapter listed in the registry is a tap interface 131 //the tap-Windows6 driver lists its adapter type as "root\tap0901" 132 if(adapterType == AdapterType) 133 { 134 string connectionGuid = (string)adapter.GetValue("NetCfgInstanceId"); 135 var connection = connections.OpenSubKey($"{connectionGuid}\\Connection"); 136 //check for the interface's name 137 if((string)connection.GetValue("Name") == name) 138 { 139 return new Guid(connectionGuid); 140 } 141 } 142 } 143 catch(SecurityException) 144 { 145 //There is a registry branch ({AdapterRegistryBranch}\Properties) that by default even the Administrator account doesn't have permissions to read. 146 //Branches like these should be skipped. 147 } 148 } 149 return null; 150 } 151 152 [PostDeserialization] Init()153 private void Init() 154 { 155 this.Log(LogLevel.Debug, "Initializing Windows TAP device"); 156 var tapctlPath = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\OpenVPN", "", ""); 157 if(String.IsNullOrEmpty(tapctlPath)) 158 { 159 isInDummyMode = true; 160 this.Log(LogLevel.Warning, "tapctl.exe utility not found - running in the dummy mode!"); 161 return; 162 } 163 isInDummyMode = false; 164 tapctlPath += @"\bin\tapctl.exe"; 165 //check whether the interface with such name exists 166 var deviceGuid = GetDeviceGuid(InterfaceName); 167 //create interface if it doesn't exist 168 if(deviceGuid == null) 169 { 170 using(var tapctlProcess = new Process()) 171 { 172 tapctlProcess.StartInfo.Verb = "runas"; 173 tapctlProcess.StartInfo.FileName = tapctlPath; 174 tapctlProcess.StartInfo.Arguments = $"create --name {InterfaceName}"; 175 tapctlProcess.StartInfo.UseShellExecute = true; 176 tapctlProcess.Start(); 177 tapctlProcess.WaitForExit(); 178 } 179 } 180 deviceGuid = GetDeviceGuid(InterfaceName); 181 if(deviceGuid == null) 182 { 183 throw new RecoverableException("Failed to retrieve the GUID of the TAP device. Please use the tapctl.exe tool manually to create the TAP device."); 184 } 185 this.Log(LogLevel.Debug, "device GUID: {0}", deviceGuid.ToString().ToUpper()); 186 string deviceFilePath = $"\\\\.\\Global\\{{{deviceGuid.ToString().ToUpper()}}}.tap"; 187 handle = new SafeFileHandle( 188 CreateFile( 189 deviceFilePath, 190 0x2000000, //MAXIMUM_ALLOWED constant 191 0, 192 IntPtr.Zero, 193 FileMode.Open, 194 (int)(FileAttributes.System) | 0x40000000, //FILE_FLAG_OVERLAPPED constant 195 IntPtr.Zero), 196 true); 197 int error = Marshal.GetLastWin32Error(); 198 if(error != 0) 199 { 200 throw new RecoverableException($"Win32 error when opening the handle to the device file at path: {deviceFilePath} \n error id: {error}"); 201 } 202 stream = new FileStream(handle, FileAccess.ReadWrite, MTU, true); 203 // inform the device that it is connected 204 ChangeDeviceStatus(true); 205 } 206 TransmitLoop(CancellationToken token)207 private void TransmitLoop(CancellationToken token) 208 { 209 while(true) 210 { 211 if(token.IsCancellationRequested) 212 { 213 this.Log(LogLevel.Noisy, "Requested thread cancellation - stopping reading from the TAP device file."); 214 return; 215 } 216 217 if(stream == null) 218 { 219 this.Log(LogLevel.Error, "Stream null on receiving the frame from the TAP interface - stopping reading from the TAP device file"); 220 return; 221 } 222 try 223 { 224 var buffer = new byte[MTU]; 225 int bytesRead = stream.Read(buffer, 0, MTU); 226 if(bytesRead > 0) 227 { 228 var packet = new byte[bytesRead]; 229 Array.Copy(buffer, packet, bytesRead); 230 this.Log(LogLevel.Noisy, "Received {0} bytes frame", bytesRead); 231 if(Misc.TryCreateFrameOrLogWarning(this, packet, out var frame, addCrc: true)) 232 { 233 FrameReady?.Invoke(frame); 234 } 235 } 236 } 237 catch(ArgumentException e) 238 { 239 this.Log(LogLevel.Error, "Stream was most likely closed - stopping reading from the TAP device file. Exception message: {0}", e.Message); 240 return; 241 } 242 catch(ObjectDisposedException e) 243 { 244 this.Log(LogLevel.Error, "Error reading data - stopping reading from the TAP device file. Exception message: {0}", e.Message); 245 return; 246 } 247 } 248 } 249 ChangeDeviceStatus(bool isOn)250 private void ChangeDeviceStatus(bool isOn) 251 { 252 IntPtr deviceStatus = Marshal.AllocHGlobal(4); 253 Marshal.WriteInt32(deviceStatus, isOn ? 1 : 0); 254 DeviceIoControl(handle.DangerousGetHandle(), TapDriverControlCode(6, 0), deviceStatus, 4, deviceStatus, 4, out int len, IntPtr.Zero); 255 Marshal.FreeHGlobal(deviceStatus); 256 } 257 CalculateControlCode(uint deviceType, uint function, uint method, uint access)258 private static uint CalculateControlCode(uint deviceType, uint function, uint method, uint access) 259 { 260 return ((deviceType << 16) | (access << 14) | (function << 2) | method); 261 } 262 TapDriverControlCode(uint request, uint method)263 private static uint TapDriverControlCode(uint request, uint method) 264 { 265 return CalculateControlCode(0x22, request, method, 0); 266 } 267 268 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile )269 private static extern IntPtr CreateFile( 270 string lpFileName, 271 uint dwDesiredAccess, 272 uint dwShareMode, 273 IntPtr lpSecurityAttributes, 274 [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, 275 int dwFlagsAndAttributes, 276 IntPtr hTemplateFile 277 ); 278 279 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out int lpBytesReturned, IntPtr lpOverlapped )280 private static extern bool DeviceIoControl( 281 IntPtr hDevice, 282 uint dwIoControlCode, 283 IntPtr lpInBuffer, 284 uint nInBufferSize, 285 IntPtr lpOutBuffer, 286 uint nOutBufferSize, 287 out int lpBytesReturned, 288 IntPtr lpOverlapped 289 ); 290 291 //The following GUIDs are guaranteed by Microsoft 292 //Microsoft docs link: https://docs.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors 293 private const string AdapterRegistryBranch = @"SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}"; 294 private const string ConnectionRegistryBranch = @"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}"; 295 private const int MTU = 1522; 296 private const string AdapterType = @"root\tap0901"; 297 298 private bool isInDummyMode; 299 private readonly object lockObject = new object(); 300 301 [Transient] 302 private CancellationTokenSource cancellationTokenSource; 303 [Transient] 304 private FileStream stream; 305 [Transient] 306 private SafeFileHandle handle; 307 [Transient] 308 private Thread thread; 309 } 310 } 311 #endif 312