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 #if PLATFORM_OSX 8 using System; 9 using Antmicro.Renode.Peripherals.Network; 10 using Antmicro.Renode.Peripherals; 11 using Antmicro.Renode.Core; 12 using System.IO; 13 using System.Threading; 14 using Antmicro.Renode.Logging; 15 using System.Threading.Tasks; 16 using Antmicro.Renode.Core.Structure; 17 using Antmicro.Renode.Network; 18 using System.Net.NetworkInformation; 19 using System.Linq; 20 using Antmicro.Renode.Utilities; 21 using Mono.Unix; 22 using Antmicro.Renode.Exceptions; 23 using Antmicro.Migrant.Hooks; 24 using Antmicro.Migrant; 25 26 namespace Antmicro.Renode.HostInterfaces.Network 27 { 28 public sealed class OsXTapInterface : ITapInterface, IHasOwnLife, IDisposable 29 { OsXTapInterface(string interfaceNameOrPath)30 public OsXTapInterface(string interfaceNameOrPath) 31 { 32 originalInterfaceNameOrPath = interfaceNameOrPath; 33 Init(); 34 } 35 ReceiveFrame(EthernetFrame frame)36 public void ReceiveFrame(EthernetFrame frame) 37 { 38 if(deviceFile == null) 39 { 40 return; 41 } 42 var bytes = frame.Bytes; 43 try 44 { 45 // since the file reader operations are buffered, we have to immediately flush writes 46 deviceFile.Write(bytes, 0, bytes.Length); 47 deviceFile.Flush(); 48 } 49 catch(IOException) 50 { 51 if(networkInterface.OperationalStatus != OperationalStatus.Up) 52 { 53 this.DebugLog("Interface is not up during write, frame dropped."); 54 } 55 else 56 { 57 throw; 58 } 59 } 60 this.NoisyLog("Frame of length {0} sent to host.", frame.Length); 61 } 62 Start()63 public void Start() 64 { 65 Resume(); 66 } 67 Pause()68 public void Pause() 69 { 70 if(deviceFile == null) 71 { 72 return; 73 } 74 75 lock(lockObject) 76 { 77 cts.Cancel(); 78 readerTask.Wait(); 79 IsPaused = true; 80 } 81 } 82 Resume()83 public void Resume() 84 { 85 if(deviceFile == null) 86 { 87 return; 88 } 89 90 lock(lockObject) 91 { 92 cts = new CancellationTokenSource(); 93 readerTask = Task.Run(ReadPacketAsync); 94 readerTask.ContinueWith(x => 95 this.Log(LogLevel.Error, "Exception happened on reader task ({0}). Task stopped.", x.Exception.InnerException.GetType().Name), TaskContinuationOptions.OnlyOnFaulted); 96 IsPaused = false; 97 } 98 } 99 Dispose()100 public void Dispose() 101 { 102 if(deviceFile != null) 103 { 104 deviceFile.Close(); 105 } 106 } 107 108 public bool IsPaused { get; private set; } = true; 109 110 public event Action<EthernetFrame> FrameReady; 111 112 public MACAddress MAC 113 { 114 get 115 { 116 return macAddress; 117 } 118 119 set 120 { 121 macAddress = value; 122 } 123 } 124 125 public string InterfaceName 126 { 127 get 128 { 129 return networkInterface.Name; 130 } 131 } 132 ReadPacketAsync()133 private async Task ReadPacketAsync() 134 { 135 var buffer = new byte[Mtu]; 136 while(!cts.IsCancellationRequested) 137 { 138 try 139 { 140 int bytesRead = await deviceFile.ReadAsync(buffer, 0, buffer.Length, cts.Token); 141 142 if(bytesRead > 0) 143 { 144 byte[] packet = new byte[bytesRead]; 145 Array.Copy(buffer, packet, bytesRead); 146 if(!Misc.TryCreateFrameOrLogWarning(this, packet, out var frame, addCrc: true)) 147 { 148 return; 149 } 150 FrameReady?.Invoke(frame); 151 this.NoisyLog("Frame of length {0} received from host.", frame.Bytes.Length); 152 } 153 } 154 catch(IOException) 155 { 156 if(networkInterface.OperationalStatus != OperationalStatus.Up) 157 { 158 this.NoisyLog("I/O exception while interface is not up, waiting {0}s.", Misc.NormalizeDecimal(GracePeriod.TotalSeconds)); 159 // probably the interface is not opened yet 160 await Task.Delay(GracePeriod); 161 } 162 else 163 { 164 throw; 165 } 166 } 167 } 168 } 169 170 [PostDeserialization] Init()171 private void Init() 172 { 173 var interfaceNameOrPath = originalInterfaceNameOrPath; 174 // (1) check if there is an installed kernel extension 175 // (2) there can still be an extension but loaded on demand - Tunnelblick does that, but we won't see anything in the Extensions folder 176 if(!Directory.Exists("/Library/Extensions/tap.kext/") && !File.Exists("/dev/tap0")) 177 { 178 this.Log(LogLevel.Warning, "No TUNTAP kernel extension found, running in dummy mode."); 179 MAC = EmulationManager.Instance.CurrentEmulation.MACRepository.GenerateUniqueMAC(); 180 return; 181 } 182 if(!File.Exists(interfaceNameOrPath)) 183 { 184 var tapDevicePath = ConfigurationManager.Instance.Get<String>("tap", "tap-device-path", "/dev"); 185 interfaceNameOrPath = Path.Combine(tapDevicePath, interfaceNameOrPath); 186 } 187 188 try 189 { 190 deviceFile = File.Open(interfaceNameOrPath, FileMode.Open, FileAccess.ReadWrite); 191 } 192 catch(FileNotFoundException) 193 { 194 throw new RecoverableException($"The requested tap device file at path: {interfaceNameOrPath} was not found."); 195 } 196 catch(UnauthorizedAccessException) 197 { 198 throw new RecoverableException($"Failed to open the requested tap device: {interfaceNameOrPath} due to the lack of permissions. Please make sure that Renode is given a read and write permissions on this file."); 199 } 200 // let's find out to what interface the character device file belongs 201 var deviceType = new UnixFileInfo(interfaceNameOrPath).DeviceType; 202 var majorNumber = deviceType >> 24; 203 var minorNumber = deviceType & 0xFFFFFF; 204 this.DebugLog($"Opening TAP device with major number: {majorNumber} and minor number: {minorNumber}"); 205 try 206 { 207 networkInterface = NetworkInterface.GetAllNetworkInterfaces().Single(x => x.Name == "tap" + minorNumber); 208 } 209 catch(InvalidOperationException) 210 { 211 throw new RecoverableException($"TAP device {interfaceNameOrPath} was not found among network devices."); 212 } 213 MAC = (MACAddress)networkInterface.GetPhysicalAddress(); 214 } 215 216 [Transient] 217 private Task readerTask; 218 219 [Transient] 220 private CancellationTokenSource cts; 221 222 [Transient] 223 private FileStream deviceFile; 224 225 [Transient] 226 private NetworkInterface networkInterface; 227 228 [Transient] 229 // we need to have this field explicitly to put [Transient] attribute on it 230 private MACAddress macAddress; 231 232 private readonly string originalInterfaceNameOrPath; 233 private readonly object lockObject = new object(); 234 235 private static readonly TimeSpan GracePeriod = TimeSpan.FromSeconds(1); 236 private const int Mtu = 1522; 237 } 238 } 239 #endif 240