From 89caf70fca3e642ec253f66e0323164fab92c260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Engelthaler?= Date: Fri, 27 Sep 2024 11:29:56 +0200 Subject: [PATCH] Add support for Linux "cooked" capture encapsulation v2 (SLL2) layer type (#208) * Add support for Linux "cooked" capture encapsulation v2 (SLL2) layer type * Update LinuxSll2Packet.cs --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> --- PacketDotNet/LinkLayers.cs | 7 +- PacketDotNet/LinuxSll2Fields.cs | 102 ++++++++++ PacketDotNet/LinuxSll2Packet.cs | 207 ++++++++++++++++++++ PacketDotNet/LinuxSll2Type.cs | 41 ++++ PacketDotNet/Packet.cs | 5 + Test/CaptureFiles/LinuxCookedCapturev2.pcap | Bin 0 -> 447 bytes Test/PacketType/LinuxCookedCapturev2Test.cs | 135 +++++++++++++ 7 files changed, 495 insertions(+), 2 deletions(-) create mode 100644 PacketDotNet/LinuxSll2Fields.cs create mode 100644 PacketDotNet/LinuxSll2Packet.cs create mode 100644 PacketDotNet/LinuxSll2Type.cs create mode 100644 Test/CaptureFiles/LinuxCookedCapturev2.pcap create mode 100644 Test/PacketType/LinuxCookedCapturev2Test.cs diff --git a/PacketDotNet/LinkLayers.cs b/PacketDotNet/LinkLayers.cs index 54cb8d80..9524c933 100644 --- a/PacketDotNet/LinkLayers.cs +++ b/PacketDotNet/LinkLayers.cs @@ -162,5 +162,8 @@ public enum LinkLayers : ushort IPv6 = 229, /// Protocol for communication between host and guest machines in VMware and KVM hypervisors. - VSock = 271 - } + VSock = 271, + + /// Linux "cooked" capture encapsulation v2. + LinuxSll2 = 276, +} diff --git a/PacketDotNet/LinuxSll2Fields.cs b/PacketDotNet/LinuxSll2Fields.cs new file mode 100644 index 00000000..6c77c087 --- /dev/null +++ b/PacketDotNet/LinuxSll2Fields.cs @@ -0,0 +1,102 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +namespace PacketDotNet; + + /// + /// Lengths and offsets to the fields in the LinuxSll2 packet + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public struct LinuxSll2Fields + { + /// + /// Length of the ethernet protocol field + /// + public static readonly int EthernetProtocolTypeLength = 2; + + /// + /// Position of the ethernet protocol type field + /// + public static readonly int EthernetProtocolTypePosition = 0; + + /// + /// Link layer address length + /// + public static readonly int LinkLayerAddressLengthLength = 1; + + /// + /// Positino of the link layer address length field + /// + public static readonly int LinkLayerAddressLengthPosition; + + /// + /// The link layer address field length + /// NOTE: the actual link layer address MAY be shorter than this + /// + public static readonly int LinkLayerAddressMaximumLength = 8; + + /// + /// Position of the link layer address field + /// + public static readonly int LinkLayerAddressPosition; + + /// + /// Link layer address type + /// + public static readonly int LinkLayerAddressTypeLength = 2; + + /// + /// Position of the link layer address type field + /// + public static readonly int LinkLayerAddressTypePosition; + + /// + /// Length of the packet type field + /// + public static readonly int PacketTypeLength = 1; + + /// + /// Position of the packet type field + /// + public static readonly int PacketTypePosition; + + /// + /// Reserved (MBZ) + /// + public static readonly int ReservedMBZLength = 2; + + /// + /// Position of the Reserved (MBZ) field + /// + public static readonly int ReservedMBZPosition = 0; + + /// + /// Length of the interface index field + /// + public static readonly int InterfaceIndexLength = 4; + + /// + /// Position of the interface index field + /// + public static readonly int InterfaceIndexPosition = 0; + + /// + /// Number of bytes in a SLL2 header + /// + public static readonly int SLL2HeaderLength = 20; + + static LinuxSll2Fields() + { + ReservedMBZPosition = EthernetProtocolTypePosition + EthernetProtocolTypeLength; + InterfaceIndexPosition = ReservedMBZPosition + ReservedMBZLength; + LinkLayerAddressTypePosition = InterfaceIndexPosition + InterfaceIndexLength; + PacketTypePosition = LinkLayerAddressTypePosition + LinkLayerAddressTypeLength; + LinkLayerAddressLengthPosition = PacketTypePosition + PacketTypeLength; + LinkLayerAddressPosition = LinkLayerAddressLengthPosition + LinkLayerAddressLengthLength; + } + } \ No newline at end of file diff --git a/PacketDotNet/LinuxSll2Packet.cs b/PacketDotNet/LinuxSll2Packet.cs new file mode 100644 index 00000000..6fbdd57d --- /dev/null +++ b/PacketDotNet/LinuxSll2Packet.cs @@ -0,0 +1,207 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using PacketDotNet.Utils; +using PacketDotNet.Utils.Converters; + +namespace PacketDotNet; + + /// + /// Represents a Linux cooked capture packet, the kinds of packets + /// received when capturing on an 'any' device + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public class LinuxSll2Packet : InternetLinkLayerPacket + { + /// + /// Constructor + /// + /// + /// A + /// + public LinuxSll2Packet(ByteArraySegment byteArraySegment) + { + Header = new ByteArraySegment(byteArraySegment) { Length = LinuxSll2Fields.SLL2HeaderLength }; + + // parse the payload via an EthernetPacket method + PayloadPacketOrData = new LazySlim(() => EthernetPacket.ParseNextSegment(Header, EthernetProtocolType)); + } + + /// + /// The encapsulated protocol type + /// + public EthernetType EthernetProtocolType + { + get => (EthernetType) EndianBitConverter.Big.ToInt16(Header.Bytes, + Header.Offset + LinuxSll2Fields.EthernetProtocolTypePosition); + set + { + var v = (short) value; + EndianBitConverter.Big.CopyBytes(v, + Header.Bytes, + Header.Offset + LinuxSll2Fields.EthernetProtocolTypePosition); + } + } + + /// + /// Link layer header bytes, maximum of 8 bytes + /// + public byte[] LinkLayerAddress + { + get + { + var headerLength = LinkLayerAddressLength; + var theHeader = new byte[headerLength]; + + Array.Copy(Header.Bytes, + Header.Offset + LinuxSll2Fields.LinkLayerAddressPosition, + theHeader, + 0, + headerLength); + + return theHeader; + } + set + { + // update the link layer length + LinkLayerAddressLength = value.Length; + + // copy in the new link layer header bytes + Array.Copy(value, + 0, + Header.Bytes, + Header.Offset + LinuxSll2Fields.LinkLayerAddressPosition, + value.Length); + } + } + + /// + /// Number of bytes in the link layer address of the sender of the packet + /// + public int LinkLayerAddressLength + { + get => Header.Bytes[Header.Offset + LinuxSll2Fields.LinkLayerAddressLengthPosition]; + set + { + // range check + if (value is < 0 or > 8) + { + throw new InvalidOperationException("value of " + value + " out of range of 0 to 8"); + } + + Header.Bytes[Header.Offset + LinuxSll2Fields.LinkLayerAddressLengthPosition] = (byte) value; + } + } + + /// + /// The + /// + public int LinkLayerAddressType + { + get => EndianBitConverter.Big.ToInt16(Header.Bytes, + Header.Offset + LinuxSll2Fields.LinkLayerAddressTypePosition); + set + { + var v = (short) value; + EndianBitConverter.Big.CopyBytes(v, + Header.Bytes, + Header.Offset + LinuxSll2Fields.LinkLayerAddressTypePosition); + } + } + + /// + /// Information about the packet direction + /// + public LinuxSll2Type Type + { + get => (LinuxSll2Type)Header.Bytes[Header.Offset + LinuxSll2Fields.PacketTypePosition]; + set + { + Header.Bytes[Header.Offset + LinuxSll2Fields.PacketTypePosition] = (byte) value; + } + } + + /// + /// Information about the interface index + /// + public int InterfaceIndex + { + get => EndianBitConverter.Big.ToInt32(Header.Bytes, + Header.Offset + LinuxSll2Fields.InterfaceIndexPosition); + set + { + var v = (int)value; + EndianBitConverter.Big.CopyBytes(v, + Header.Bytes, + Header.Offset + LinuxSll2Fields.InterfaceIndexPosition); + } + } + + /// + public override string ToString(StringOutputType outputFormat) + { + var buffer = new StringBuilder(); + var color = ""; + var colorEscape = ""; + + if (outputFormat is StringOutputType.Colored or StringOutputType.VerboseColored) + { + color = Color; + colorEscape = AnsiEscapeSequences.Reset; + } + + if (outputFormat is StringOutputType.Normal or StringOutputType.Colored) + { + // build the output string + buffer.AppendFormat("[{0}LinuxSll2Packet{1}: ProtocolType={2}, InterfaceIndex={3}, LinkLayerAddressType={4}, Type={5}, LinkLayerAddressLength={6}, Source={7}]", + color, + colorEscape, + EthernetProtocolType, + InterfaceIndex, + LinkLayerAddressType, + Type, + LinkLayerAddressLength, + BitConverter.ToString(LinkLayerAddress, 0)); + } + + if (outputFormat is StringOutputType.Verbose or StringOutputType.VerboseColored) + { + // collect the properties and their value + var properties = new Dictionary + { + { "protocol", EthernetProtocolType + " (0x" + EthernetProtocolType.ToString("x") + ")" }, + { "interface index", InterfaceIndex.ToString() }, + { "link layer address type", LinkLayerAddressType.ToString() }, + { "type", Type + " (" + (int) Type + ")" }, + { "link layer address length", LinkLayerAddressLength.ToString() }, + { "source", BitConverter.ToString(LinkLayerAddress) }, + }; + + // calculate the padding needed to right-justify the property names + var padLength = RandomUtils.LongestStringLength(new List(properties.Keys)); + + // build the output string + buffer.AppendLine("LCC: ******* LinuxSll2 - \"Linux Cooked Capture v2\" - offset=? length=" + TotalPacketLength); + buffer.AppendLine("LCC:"); + foreach (var property in properties) + { + buffer.AppendLine("LCC: " + property.Key.PadLeft(padLength) + " = " + property.Value); + } + + buffer.AppendLine("LCC:"); + } + + // append the base output + buffer.Append(base.ToString(outputFormat)); + + return buffer.ToString(); + } + } diff --git a/PacketDotNet/LinuxSll2Type.cs b/PacketDotNet/LinuxSll2Type.cs new file mode 100644 index 00000000..9d22d908 --- /dev/null +++ b/PacketDotNet/LinuxSll2Type.cs @@ -0,0 +1,41 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +namespace PacketDotNet; + + /// + /// The types of cooked packets v2 + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public enum LinuxSll2Type + { + /// + /// Packet was sent to us by somebody else + /// + PacketSentToUs = 0x0, + + /// + /// Packet was broadcast by somebody else + /// + PacketBroadCast = 0x1, + + /// + /// Packet was multicast, but not broadcast + /// + PacketMulticast = 0x2, + + /// + /// Packet was sent by somebody else to somebody else + /// + PacketSentToSomeoneElse = 0x3, + + /// + /// Packet was sent by us + /// + PacketSentByUs = 0x4 + } \ No newline at end of file diff --git a/PacketDotNet/Packet.cs b/PacketDotNet/Packet.cs index 07434638..63f885d9 100644 --- a/PacketDotNet/Packet.cs +++ b/PacketDotNet/Packet.cs @@ -339,6 +339,11 @@ public static Packet ParsePacket(LinkLayers linkLayers, byte[] packetData) p = new LinuxSllPacket(byteArraySegment); break; } + case LinkLayers.LinuxSll2: + { + p = new LinuxSll2Packet(byteArraySegment); + break; + } case LinkLayers.Null: { p = new NullPacket(byteArraySegment); diff --git a/Test/CaptureFiles/LinuxCookedCapturev2.pcap b/Test/CaptureFiles/LinuxCookedCapturev2.pcap new file mode 100644 index 0000000000000000000000000000000000000000..5f80f65467cd8d4066132bf59e1127a227763800 GIT binary patch literal 447 zcmca|c+)~A1{MYcU||qpWMIg&+>;_J&A~7gC;`G85Cu#Oj4W&nJeq7jTNxN!85kOW z^g1v&u*GB?Sg}bDh#7lX7y@pKF>cDaG;L9X1jEy(Ul6a&l?`P*_V}ifQ|!Ugd>^QF3r_r70UuUvL`&1f#JWPfGyC0 zQV$pyev7;mdC5@mrWdG%fq@xJGV;eK=jRqA=4IyR>E>kSWwXSWBp0yeTQFfi~i zFml9a&L@+&G?;!9Hs4k&`e85lHK7cejgPGJxNhAJP! z$#38H9`h@JxF!qe-rZZ+8JvJtf-nagkYHc{1}Y<4#oj$n))@kMj2sMXEDVegKG4N- NU^NV&z=g1oNdV7(), "expected a tcp packet"); + } + + private void VerifyPacket1(Packet p) + { + // expect a udp packet + Assert.IsNotNull(p.Extract(), "expected a udp packet"); + } + + private void VerifyPacket2(Packet p) + { + // expect an arp packet + var arpPacket = p.Extract(); + Assert.IsNotNull(arpPacket, "Expected arpPacket to not be null"); + + // validate some of the LinuxSSLPacket fields + var l = (LinuxSll2Packet) p; + Assert.AreEqual(2, l.InterfaceIndex, "Interface index"); + Assert.AreEqual(6, l.LinkLayerAddressLength, "Address length"); + Assert.AreEqual(1, l.LinkLayerAddressType); + Assert.AreEqual(LinuxSll2Type.PacketBroadCast, l.Type); + + // validate some of the arp fields + Assert.AreEqual("192.168.178.30", + arpPacket.SenderProtocolAddress.ToString(), + "Arp SenderProtocolAddress"); + + Assert.AreEqual("192.168.178.1", + arpPacket.TargetProtocolAddress.ToString(), + "Arp TargetProtocolAddress"); + } + + [Test] + public void CookedCaptureTest() + { + var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "LinuxCookedCapturev2.pcap"); + dev.Open(); + + PacketCapture e; + GetPacketStatus status; + var packetIndex = 0; + while ((status = dev.GetNextPacket(out e)) == GetPacketStatus.PacketRead) + { + var rawCapture = e.GetPacket(); + var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data); + switch (packetIndex) + { + case 0: + { + VerifyPacket0(p); + break; + } + case 1: + { + VerifyPacket1(p); + break; + } + case 2: + { + VerifyPacket2(p); + break; + } + default: + { + Assert.Fail("didn't expect to get to packetIndex " + packetIndex); + break; + } + } + + packetIndex++; + } + + dev.Close(); + } + + [Test] + public void PrintString() + { + Console.WriteLine("Loading the sample capture file"); + var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "LinuxCookedCapture.pcap"); + dev.Open(); + Console.WriteLine("Reading packet data"); + PacketCapture c; + dev.GetNextPacket(out c); + var rawCapture = c.GetPacket(); + var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data); + + Console.WriteLine("Parsing"); + var l = (LinuxSllPacket) p; + + Console.WriteLine("Printing human readable string"); + Console.WriteLine(l.ToString()); + } + + [Test] + public void PrintVerboseString() + { + Console.WriteLine("Loading the sample capture file"); + var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "LinuxCookedCapture.pcap"); + dev.Open(); + Console.WriteLine("Reading packet data"); + PacketCapture c; + dev.GetNextPacket(out c); + var rawCapture = c.GetPacket(); + var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data); + + Console.WriteLine("Parsing"); + var l = (LinuxSllPacket) p; + + Console.WriteLine("Printing human readable string"); + Console.WriteLine(l.ToString(StringOutputType.Verbose)); + } + } \ No newline at end of file