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