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