1 /**************************************************************************
2 *                           MIT License
3 *
4 * Copyright (C) 2015 Frederic Chaxel <fchaxel@free.fr>
5 *
6 * Copyright (c) Antmicro
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 *********************************************************************/
28 using System;
29 using System.Runtime.InteropServices;
30 using System.IO;
31 using System.Diagnostics;
32 using System.IO.Pipes;
33 using Antmicro.Renode.Utilities;
34 
35 namespace Antmicro.Renode.Plugins.WiresharkPlugin
36 {
37     public class WiresharkSender
38     {
WiresharkSender(string pipeName, uint pcapNetId, string wiresharkPath)39         public WiresharkSender(string pipeName, uint pcapNetId, string wiresharkPath)
40         {
41             this.pipeName = pipeName;
42             this.pcapNetId = pcapNetId;
43             this.wiresharkPath = wiresharkPath;
44         }
45 
ClearPipe()46         public void ClearPipe()
47         {
48             if(wiresharkPipe != null)
49             {
50                 wiresharkPipe.Close();
51 #if !PLATFORM_WINDOWS
52                 //As named pipes on Linux have their entries in the filesystem, we remove it as a cleanup.
53                 File.Delete(pipeName);
54 #endif
55             }
56         }
57 
TryOpenWireshark()58         public bool TryOpenWireshark()
59         {
60             if(isConnected)
61             {
62                 return false;
63             }
64             lastReportedFrame = null;
65 
66 #if !PLATFORM_WINDOWS
67             // Mono is using the "/var/tmp" prefix for pipes by default.
68             // Because of problems with setting GID bit on OSX in that directory, we combine this default path with an absolute EmulatorTemporaryPath value, which effectively overwrites the default - Path.Combine of two absolute paths drops the first one.
69             wiresharkPipe = new NamedPipeServerStream(GetPrefixedPipeName(), PipeDirection.Out, 1, PipeTransmissionMode.Byte, NamedPipeOptions);
70 #else
71             // Windows does not let you override the prefix with the trick above, and as such it requires a prefixless
72             // name for the named pipe to work, while Wireshark needs the prefixed name as the argument.
73             wiresharkPipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, NamedPipeOptions);
74 #endif
75             wiresharkProces = new Process();
76             wiresharkProces.EnableRaisingEvents = true;
77 
78             wiresharkProces.StartInfo = new ProcessStartInfo(wiresharkPath, $"-ni {GetPrefixedPipeName()} -k")
79             {
80                 UseShellExecute = false,
81                 RedirectStandardError = true,
82                 RedirectStandardOutput = true,
83                 RedirectStandardInput = true
84             };
85             wiresharkProces.Exited += (sender, e) =>
86             {
87                 isConnected = false;
88                 ClearPipe();
89             };
90 
91             wiresharkProces.Start();
92             wiresharkPipe.WaitForConnection();
93             isConnected = true;
94             SendWiresharkGlobalHeader();
95 
96             return true;
97         }
98 
CloseWireshark()99         public void CloseWireshark()
100         {
101             if(wiresharkProces == null)
102             {
103                 return;
104             }
105 
106             try
107             {
108                 if(!wiresharkProces.HasExited)
109                 {
110                     wiresharkProces.CloseMainWindow();
111                 }
112             }
113             catch(InvalidOperationException e)
114             {
115                 // do not report an exception if the program has already exited
116                 if(!e.Message.Contains("finished"))
117                 {
118                     throw;
119                 }
120             }
121             wiresharkProces = null;
122         }
123 
SendReportedFrames(byte[] buffer)124         public void SendReportedFrames(byte[] buffer)
125         {
126             if(lastReportedFrame != buffer)
127             {
128                 SendToWireshark(buffer, 0, buffer.Length);
129                 lastReportedFrame = buffer;
130             }
131         }
132 
SendProcessedFrames(byte[] buffer)133         public void SendProcessedFrames(byte[] buffer)
134         {
135             if(lastProcessedFrame != buffer)
136             {
137                 SendToWireshark(buffer, 0, buffer.Length);
138                 lastProcessedFrame = buffer;
139             }
140         }
141 
GetPrefixedPipeName()142         private string GetPrefixedPipeName()
143         {
144             // Mono is using the "/var/tmp" prefix for pipes by default.
145             // Because of problems with setting GID bit on OSX in that directory, we combine this default path with an absolute EmulatorTemporaryPath value, which effectively overwrites the default - Path.Combine of two absolute paths drops the first one.
146             return $"{namedPipePrefix}{pipeName}";
147         }
148 
SendWiresharkGlobalHeader()149         private void SendWiresharkGlobalHeader()
150         {
151             var p = new PcapGlobalHeader(pcapNetId);
152             var bh = p.ToByteArray();
153             wiresharkPipe.Write(bh, 0, bh.Length);
154         }
155 
SendToWireshark(byte[] buffer, int offset, int lenght)156         private bool SendToWireshark(byte[] buffer, int offset, int lenght)
157         {
158             return SendToWireshark(buffer, offset, lenght, CustomDateTime.Now);
159         }
160 
SendToWireshark(byte[] buffer, int offset, int lenght, DateTime date)161         private bool SendToWireshark(byte[] buffer, int offset, int lenght, DateTime date)
162         {
163             // Suppress all values for ms, us and ns
164             var roundedDate = new DateTime((date.Ticks / (long)10000000) * (long)10000000);
165 
166             var seconds = DateTimeToUnixTimestamp(date);
167             var microseconds = (UInt32)((date.Ticks - roundedDate.Ticks) / 10);
168 
169             return SendToWireshark(buffer, offset, lenght, seconds, microseconds);
170         }
171 
SendToWireshark(byte[] buffer, int offset, int lenght, uint seconds, uint microseconds)172         private bool SendToWireshark(byte[] buffer, int offset, int lenght, uint seconds, uint microseconds)
173         {
174             if(!isConnected)
175             {
176                 return false;
177             }
178 
179             var header = new PcapPacketHeader((uint)lenght, seconds, microseconds);
180             var headerBytes = header.ToByteArray();
181 
182             try
183             {
184                 // Wireshark Header
185                 wiresharkPipe.Write(headerBytes, 0, headerBytes.Length);
186 
187                 // Bacnet packet
188                 wiresharkPipe.Write(buffer, offset, lenght);
189 
190             }
191             catch(Exception)
192             {
193                 // We should probably handle IOException to somehow restart the pipe.
194                 // It is difficult to test, though, and Wireshark may not survive it.
195                 return false;
196             }
197 
198             return true;
199         }
200 
DateTimeToUnixTimestamp(DateTime dateTime)201         private static uint DateTimeToUnixTimestamp(DateTime dateTime)
202         {
203             return (uint)(dateTime - localEpoch).TotalSeconds;
204         }
205 
206         private NamedPipeServerStream wiresharkPipe;
207         private Process wiresharkProces;
208         private string pipeName;
209         private uint pcapNetId;
210         private string wiresharkPath;
211         private bool isConnected;
212         private byte[] lastReportedFrame;
213         private byte[] lastProcessedFrame;
214 
215         private static readonly DateTime localEpoch = Misc.UnixEpoch.ToLocalTime();
216 
217 #if !PLATFORM_WINDOWS
218         private string namedPipePrefix = Utilities.TemporaryFilesManager.Instance.EmulatorTemporaryPath;
219         private const PipeOptions NamedPipeOptions = PipeOptions.None;
220 #else
221         private string namedPipePrefix = @"\\.\pipe\";
222         private const PipeOptions NamedPipeOptions = PipeOptions.Asynchronous;
223 #endif
224 
225         [StructLayout(LayoutKind.Sequential, Pack = 1)]
226         struct PcapPacketHeader
227         {
PcapPacketHeaderAntmicro.Renode.Plugins.WiresharkPlugin.WiresharkSender.PcapPacketHeader228             public PcapPacketHeader(uint lenght, uint seconds, uint microseconds)
229             {
230                 savedBytesLength = packetLength = lenght;
231                 timestampSeconds = seconds;
232                 timestampMicroseconds = microseconds;
233             }
234 
ToByteArrayAntmicro.Renode.Plugins.WiresharkPlugin.WiresharkSender.PcapPacketHeader235             public byte[] ToByteArray()
236             {
237                 var rawsize = Marshal.SizeOf(this);
238                 var rawdatas = new byte[rawsize];
239                 var handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
240                 var buffer = handle.AddrOfPinnedObject();
241                 Marshal.StructureToPtr(this, buffer, false);
242                 handle.Free();
243                 return rawdatas;
244             }
245 
246             /* timestamp seconds */
247             private uint timestampSeconds;
248             /* timestamp microseconds */
249             private uint timestampMicroseconds;
250             /* number of octets of packet saved in file */
251             private uint savedBytesLength;
252             /* actual length of packet */
253             private uint packetLength;
254         }
255 
256         [StructLayout(LayoutKind.Sequential, Pack = 1)]
257         struct PcapGlobalHeader
258         {
PcapGlobalHeaderAntmicro.Renode.Plugins.WiresharkPlugin.WiresharkSender.PcapGlobalHeader259             public PcapGlobalHeader(uint network)
260             {
261                 magicNumber = 0xa1b2c3d4;
262                 majorVersion = 2;
263                 minorVersion = 4;
264                 timezoneCorrection = 0;
265                 sigfigs = 0;
266                 maximumPacketLength = 65535;
267                 networkType = network;
268             }
269 
ToByteArrayAntmicro.Renode.Plugins.WiresharkPlugin.WiresharkSender.PcapGlobalHeader270             public byte[] ToByteArray()
271             {
272                 var rawsize = Marshal.SizeOf(this);
273                 var rawdata = new byte[rawsize];
274                 var handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
275                 var buffer = handle.AddrOfPinnedObject();
276                 Marshal.StructureToPtr(this, buffer, false);
277                 handle.Free();
278                 return rawdata;
279             }
280 
281             /* magic number */
282             private uint magicNumber;
283             /* major version number */
284             private ushort majorVersion;
285             /* minor version number */
286             private ushort minorVersion;
287             /* GMT to local correction */
288             private int timezoneCorrection;
289             /* accuracy of timestamps */
290             private uint sigfigs;
291             /* max length of captured packets, in octets */
292             private uint maximumPacketLength;
293             /* data link type */
294             private uint networkType;
295         }
296     }
297 }
298