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