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