1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 #if PLATFORM_LINUX 9 using System; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Core.Structure; 12 using Antmicro.Renode.Peripherals.Network; 13 using System.Net.NetworkInformation; 14 using System.Linq; 15 using Antmicro.Renode.TAPHelper; 16 using Antmicro.Renode.Peripherals; 17 using System.Threading; 18 using Antmicro.Migrant.Hooks; 19 using System.IO; 20 using Antmicro.Renode.Logging; 21 using System.Runtime.InteropServices; 22 using System.Diagnostics; 23 using Antmicro.Renode.Utilities; 24 using Antmicro.Renode.Exceptions; 25 using Mono.Unix; 26 using Antmicro.Renode.Network; 27 using Antmicro.Migrant; 28 29 namespace Antmicro.Renode.HostInterfaces.Network 30 { 31 public sealed class LinuxTapInterface : ITapInterface, IHasOwnLife, IDisposable 32 { LinuxTapInterface(string name, bool persistent)33 public LinuxTapInterface(string name, bool persistent) 34 { 35 backupMAC = EmulationManager.Instance.CurrentEmulation.MACRepository.GenerateUniqueMAC(); 36 mac = backupMAC; 37 deviceName = name ?? ""; 38 this.persistent = persistent; 39 Init(); 40 } 41 Dispose()42 public void Dispose() 43 { 44 if(stream != null) 45 { 46 stream.Close(); 47 } 48 49 if(tapFileDescriptor != -1) 50 { 51 LibCWrapper.Close(tapFileDescriptor); 52 tapFileDescriptor = -1; 53 } 54 } 55 Pause()56 public void Pause() 57 { 58 if(!active) 59 { 60 return; 61 } 62 63 lock(lockObject) 64 { 65 var token = cts; 66 token.Cancel(); 67 IsPaused = true; 68 // we're not joining the read thread as it's canceled and will return after Read times out; 69 // we might end up with multiple TransmitLoop threads running at the same time as a result of a quick Pause/Resume actions, 70 // but only the last one will process data - the rest will terminate unconditionally after leaving the Read function call 71 } 72 } 73 ReceiveFrame(EthernetFrame frame)74 public void ReceiveFrame(EthernetFrame frame) 75 { 76 // TODO: non blocking 77 if(stream == null) 78 { 79 return; 80 } 81 var handle = GCHandle.Alloc(frame.Bytes, GCHandleType.Pinned); 82 try 83 { 84 var result = LibCWrapper.Write(stream.Handle, handle.AddrOfPinnedObject(), frame.Bytes.Length); 85 if(!result) 86 { 87 this.Log(LogLevel.Error, 88 "Error while writing to TUN interface: {0}.", result); 89 } 90 } 91 finally 92 { 93 handle.Free(); 94 } 95 } 96 Resume()97 public void Resume() 98 { 99 if(!active) 100 { 101 return; 102 } 103 104 lock(lockObject) 105 { 106 cts = new CancellationTokenSource(); 107 thread = new Thread(() => TransmitLoop(cts.Token)) 108 { 109 Name = this.GetType().Name, 110 IsBackground = true 111 }; 112 thread.Start(); 113 IsPaused = false; 114 } 115 } 116 Start()117 public void Start() 118 { 119 Resume(); 120 } 121 122 public string InterfaceName { get; private set; } 123 public event Action<EthernetFrame> FrameReady; 124 125 public bool IsPaused { get; private set; } = true; 126 127 public MACAddress MAC 128 { 129 get 130 { 131 return mac; 132 } 133 set 134 { 135 throw new NotSupportedException("Cannot change the MAC of the host machine."); 136 } 137 } 138 139 [PostDeserialization] Init()140 private void Init() 141 { 142 active = false; 143 // if there is no /dev/net/tun, run in a "dummy" mode 144 if(!File.Exists("/dev/net/tun")) 145 { 146 this.Log(LogLevel.Warning, "No TUN device found, running in dummy mode."); 147 return; 148 } 149 150 IntPtr devName; 151 if(deviceName != "") 152 { 153 // non-anonymous mapping 154 devName = Marshal.StringToHGlobalAnsi(deviceName); 155 } 156 else 157 { 158 devName = Marshal.AllocHGlobal(DeviceNameBufferSize); 159 Marshal.WriteByte(devName, 0); // null termination 160 } 161 try 162 { 163 // If we don't have rw access to /dev/net/tun we will loop here indefinitely 164 // because we won't be able to open tap 165 // Fortunately /dev/net/tun has rw by default 166 tapFileDescriptor = TAPTools.OpenTAP(devName, persistent); 167 if(tapFileDescriptor < 0) 168 { 169 var process = new Process(); 170 var output = string.Empty; 171 #if NET 172 process.StartInfo.FileName = "dotnet"; 173 #else 174 process.StartInfo.FileName = "mono"; 175 #endif 176 process.StartInfo.Arguments = string.Format("{0} {1} true", DynamicModuleSpawner.GetTAPHelper(), deviceName); 177 178 try 179 { 180 SudoTools.EnsureSudoProcess(process, "TAP creator"); 181 } 182 catch(Exception ex) 183 { 184 throw new RecoverableException("Process elevation failed: " + ex.Message); 185 } 186 187 process.EnableRaisingEvents = true; 188 process.StartInfo.CreateNoWindow = false; 189 process.StartInfo.UseShellExecute = false; 190 process.StartInfo.RedirectStandardError = true; 191 process.StartInfo.RedirectStandardOutput = true; 192 193 var started = process.Start(); 194 if(started) 195 { 196 output = process.StandardError.ReadToEnd(); 197 process.WaitForExit(); 198 } 199 if(!started || process.ExitCode != 0) 200 { 201 this.Log(LogLevel.Warning, "Could not create TUN/TAP interface, running in dummy mode."); 202 this.Log(LogLevel.Debug, "Error {0} while opening tun device '{1}'. {2}", process.ExitCode, deviceName, output); 203 return; 204 } 205 Init(); 206 return; 207 } 208 stream = new UnixStream(tapFileDescriptor, true); 209 InterfaceName = Marshal.PtrToStringAnsi(devName); 210 this.Log(LogLevel.Info, 211 "Opened interface {0}.", InterfaceName); 212 } 213 finally 214 { 215 Marshal.FreeHGlobal(devName); 216 } 217 active = true; 218 var ourInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(x => x.Name == InterfaceName); 219 if(ourInterface != null) 220 { 221 mac = (MACAddress)ourInterface.GetPhysicalAddress(); 222 } 223 } 224 TransmitLoop(CancellationToken token)225 private void TransmitLoop(CancellationToken token) 226 { 227 while(true) 228 { 229 byte[] buffer = null; 230 if(stream == null) 231 { 232 return; 233 } 234 try 235 { 236 buffer = LibCWrapper.Read(stream.Handle, MTU, ReadTimeout, () => token.IsCancellationRequested); 237 } 238 catch(ArgumentException) 239 { 240 // stream was closed 241 return; 242 } 243 catch(ObjectDisposedException) 244 { 245 return; 246 } 247 248 if(token.IsCancellationRequested) 249 { 250 return; 251 } 252 if(buffer == null || buffer.Length == 0) 253 { 254 continue; 255 } 256 if(Misc.TryCreateFrameOrLogWarning(this, buffer, out var frame, addCrc: true)) 257 { 258 FrameReady?.Invoke(frame); 259 } 260 } 261 } 262 263 private const int DeviceNameBufferSize = 8192; 264 private const int MTU = 1522; 265 private const int ReadTimeout = 100; // in milliseconds 266 267 [Transient] 268 private bool active; 269 [Transient] 270 private MACAddress mac; 271 private MACAddress backupMAC; 272 [Transient] 273 private CancellationTokenSource cts; 274 private readonly string deviceName; 275 private readonly object lockObject = new object(); 276 private readonly bool persistent; 277 [Transient] 278 private UnixStream stream; 279 [Transient] 280 private int tapFileDescriptor; 281 [Transient] 282 private Thread thread; 283 } 284 } 285 #endif 286