| /************************************************************************** | |
| * MIT License | |
| * | |
| * Copyright (C) 2015 Frederic Chaxel <fchaxel@free.fr> | |
| * | |
| * Copyright (c) Antmicro | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining | |
| * a copy of this software and associated documentation files (the | |
| * "Software"), to deal in the Software without restriction, including | |
| * without limitation the rights to use, copy, modify, merge, publish, | |
| * distribute, sublicense, and/or sell copies of the Software, and to | |
| * permit persons to whom the Software is furnished to do so, subject to | |
| * the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included | |
| * in all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| *********************************************************************/ | |
| using System; | |
| using System.Runtime.InteropServices; | |
| using System.IO; | |
| using System.Diagnostics; | |
| using System.IO.Pipes; | |
| using Antmicro.Renode.Utilities; | |
| namespace Antmicro.Renode.Plugins.WiresharkPlugin | |
| { | |
| public class WiresharkSender | |
| { | |
| public WiresharkSender(string pipeName, uint pcapNetId, string wiresharkPath) | |
| { | |
| this.pipeName = pipeName; | |
| this.pcapNetId = pcapNetId; | |
| this.wiresharkPath = wiresharkPath; | |
| } | |
| public void ClearPipe() | |
| { | |
| if(wiresharkPipe != null) | |
| { | |
| wiresharkPipe.Close(); | |
| #if !PLATFORM_WINDOWS | |
| //As named pipes on Linux have their entries in the filesystem, we remove it as a cleanup. | |
| File.Delete(pipeName); | |
| #endif | |
| } | |
| } | |
| public bool TryOpenWireshark() | |
| { | |
| if(isConnected) | |
| { | |
| return false; | |
| } | |
| lastReportedFrame = null; | |
| #if !PLATFORM_WINDOWS | |
| // Mono is using the "/var/tmp" prefix for pipes by default. | |
| // 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. | |
| wiresharkPipe = new NamedPipeServerStream(GetPrefixedPipeName(), PipeDirection.Out, 1, PipeTransmissionMode.Byte, NamedPipeOptions); | |
| #else | |
| // Windows does not let you override the prefix with the trick above, and as such it requires a prefixless | |
| // name for the named pipe to work, while Wireshark needs the prefixed name as the argument. | |
| wiresharkPipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, NamedPipeOptions); | |
| #endif | |
| wiresharkProces = new Process(); | |
| wiresharkProces.EnableRaisingEvents = true; | |
| wiresharkProces.StartInfo = new ProcessStartInfo(wiresharkPath, $"-ni {GetPrefixedPipeName()} -k") | |
| { | |
| UseShellExecute = false, | |
| RedirectStandardError = true, | |
| RedirectStandardOutput = true, | |
| RedirectStandardInput = true | |
| }; | |
| wiresharkProces.Exited += (sender, e) => | |
| { | |
| isConnected = false; | |
| ClearPipe(); | |
| }; | |
| wiresharkProces.Start(); | |
| wiresharkPipe.WaitForConnection(); | |
| isConnected = true; | |
| SendWiresharkGlobalHeader(); | |
| return true; | |
| } | |
| public void CloseWireshark() | |
| { | |
| if(wiresharkProces == null) | |
| { | |
| return; | |
| } | |
| try | |
| { | |
| if(!wiresharkProces.HasExited) | |
| { | |
| wiresharkProces.CloseMainWindow(); | |
| } | |
| } | |
| catch(InvalidOperationException e) | |
| { | |
| // do not report an exception if the program has already exited | |
| if(!e.Message.Contains("finished")) | |
| { | |
| throw; | |
| } | |
| } | |
| wiresharkProces = null; | |
| } | |
| public void SendReportedFrames(byte[] buffer) | |
| { | |
| if(lastReportedFrame != buffer) | |
| { | |
| SendToWireshark(buffer, 0, buffer.Length); | |
| lastReportedFrame = buffer; | |
| } | |
| } | |
| public void SendProcessedFrames(byte[] buffer) | |
| { | |
| if(lastProcessedFrame != buffer) | |
| { | |
| SendToWireshark(buffer, 0, buffer.Length); | |
| lastProcessedFrame = buffer; | |
| } | |
| } | |
| private string GetPrefixedPipeName() | |
| { | |
| // Mono is using the "/var/tmp" prefix for pipes by default. | |
| // 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. | |
| return $"{namedPipePrefix}{pipeName}"; | |
| } | |
| private void SendWiresharkGlobalHeader() | |
| { | |
| var p = new PcapGlobalHeader(pcapNetId); | |
| var bh = p.ToByteArray(); | |
| wiresharkPipe.Write(bh, 0, bh.Length); | |
| } | |
| private bool SendToWireshark(byte[] buffer, int offset, int lenght) | |
| { | |
| return SendToWireshark(buffer, offset, lenght, CustomDateTime.Now); | |
| } | |
| private bool SendToWireshark(byte[] buffer, int offset, int lenght, DateTime date) | |
| { | |
| // Suppress all values for ms, us and ns | |
| var roundedDate = new DateTime((date.Ticks / (long)10000000) * (long)10000000); | |
| var seconds = DateTimeToUnixTimestamp(date); | |
| var microseconds = (UInt32)((date.Ticks - roundedDate.Ticks) / 10); | |
| return SendToWireshark(buffer, offset, lenght, seconds, microseconds); | |
| } | |
| private bool SendToWireshark(byte[] buffer, int offset, int lenght, uint seconds, uint microseconds) | |
| { | |
| if(!isConnected) | |
| { | |
| return false; | |
| } | |
| var header = new PcapPacketHeader((uint)lenght, seconds, microseconds); | |
| var headerBytes = header.ToByteArray(); | |
| try | |
| { | |
| // Wireshark Header | |
| wiresharkPipe.Write(headerBytes, 0, headerBytes.Length); | |
| // Bacnet packet | |
| wiresharkPipe.Write(buffer, offset, lenght); | |
| } | |
| catch(Exception) | |
| { | |
| // We should probably handle IOException to somehow restart the pipe. | |
| // It is difficult to test, though, and Wireshark may not survive it. | |
| return false; | |
| } | |
| return true; | |
| } | |
| private static uint DateTimeToUnixTimestamp(DateTime dateTime) | |
| { | |
| return (uint)(dateTime - localEpoch).TotalSeconds; | |
| } | |
| private NamedPipeServerStream wiresharkPipe; | |
| private Process wiresharkProces; | |
| private string pipeName; | |
| private uint pcapNetId; | |
| private string wiresharkPath; | |
| private bool isConnected; | |
| private byte[] lastReportedFrame; | |
| private byte[] lastProcessedFrame; | |
| private static readonly DateTime localEpoch = Misc.UnixEpoch.ToLocalTime(); | |
| #if !PLATFORM_WINDOWS | |
| private string namedPipePrefix = Utilities.TemporaryFilesManager.Instance.EmulatorTemporaryPath; | |
| private const PipeOptions NamedPipeOptions = PipeOptions.None; | |
| #else | |
| private string namedPipePrefix = @"\\.\pipe\"; | |
| private const PipeOptions NamedPipeOptions = PipeOptions.Asynchronous; | |
| #endif | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| struct PcapPacketHeader | |
| { | |
| public PcapPacketHeader(uint lenght, uint seconds, uint microseconds) | |
| { | |
| savedBytesLength = packetLength = lenght; | |
| timestampSeconds = seconds; | |
| timestampMicroseconds = microseconds; | |
| } | |
| public byte[] ToByteArray() | |
| { | |
| var rawsize = Marshal.SizeOf(this); | |
| var rawdatas = new byte[rawsize]; | |
| var handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned); | |
| var buffer = handle.AddrOfPinnedObject(); | |
| Marshal.StructureToPtr(this, buffer, false); | |
| handle.Free(); | |
| return rawdatas; | |
| } | |
| /* timestamp seconds */ | |
| private uint timestampSeconds; | |
| /* timestamp microseconds */ | |
| private uint timestampMicroseconds; | |
| /* number of octets of packet saved in file */ | |
| private uint savedBytesLength; | |
| /* actual length of packet */ | |
| private uint packetLength; | |
| } | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| struct PcapGlobalHeader | |
| { | |
| public PcapGlobalHeader(uint network) | |
| { | |
| magicNumber = 0xa1b2c3d4; | |
| majorVersion = 2; | |
| minorVersion = 4; | |
| timezoneCorrection = 0; | |
| sigfigs = 0; | |
| maximumPacketLength = 65535; | |
| networkType = network; | |
| } | |
| public byte[] ToByteArray() | |
| { | |
| var rawsize = Marshal.SizeOf(this); | |
| var rawdata = new byte[rawsize]; | |
| var handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned); | |
| var buffer = handle.AddrOfPinnedObject(); | |
| Marshal.StructureToPtr(this, buffer, false); | |
| handle.Free(); | |
| return rawdata; | |
| } | |
| /* magic number */ | |
| private uint magicNumber; | |
| /* major version number */ | |
| private ushort majorVersion; | |
| /* minor version number */ | |
| private ushort minorVersion; | |
| /* GMT to local correction */ | |
| private int timezoneCorrection; | |
| /* accuracy of timestamps */ | |
| private uint sigfigs; | |
| /* max length of captured packets, in octets */ | |
| private uint maximumPacketLength; | |
| /* data link type */ | |
| private uint networkType; | |
| } | |
| } | |
| } |