blob: 9825ca63ab8fa02cbf096398d8dc10be58fc568b [file] [log] [blame]
/**************************************************************************
* 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;
}
}
}