diff --git a/.appveyor.yml b/.appveyor.yml index 2825ec90..fad792bc 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,9 +9,12 @@ environment: # Disable automatic AppVeyor build logic build: off skip_branch_with_pr: true +branches: + only: + - master image: - - Visual Studio 2019 + - Visual Studio 2022 - Ubuntu1804 - macos @@ -19,7 +22,7 @@ for: - matrix: only: - - image: Visual Studio 2019 + - image: Visual Studio 2022 install: - ps: .\scripts\install-windows.ps1 - ps: .\scripts\Install-npcap.ps1 diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f1c5d5a..33e695c1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ parameters: default: "Lansweeper_sharppcap" nuget-output-file: type: string - default: "SharpPcap/bin/Release/Lansweeper.SharpPcap.9.0.6.nupkg" + default: "SharpPcap/bin/Release/Lansweeper.SharpPcap.9.0.7.nupkg" executors: node: @@ -20,6 +20,14 @@ orbs: win: circleci/windows@2.2.0 sonar-check: lansweeper/sonar@0.0.6 +commands: + report: + steps: + - store_test_results: + path: Test/TestResults + - store_artifacts: + path: Test/TestResults + jobs: build: executor: win/default @@ -77,7 +85,7 @@ jobs: - run: name: Set correct version in csproj file, build and pack it command: | - $env:package_version = "9.0.6" + $env:package_version = "9.0.7" $file = Get-Item << pipeline.parameters.csproj-file >> [xml]$cn = Get-Content $file diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index a7fcb3ce..00000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "codecov.tool": { - "version": "1.13.0", - "commands": [ - "codecov" - ] - } - } -} \ No newline at end of file diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 86b3bfd9..066eb441 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Install .net dependencies run: dotnet restore - name: Install libpcap @@ -27,9 +27,23 @@ jobs: run: dotnet build SharpPcap/SharpPcap.csproj - name: Test run: sudo -E bash scripts/test.sh + + - run: sudo chmod -R +r Test/TestResults + if: always() + - name: Publish Test Report + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: Test/TestResults/TestResults.xml + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: artifacts + path: Test/TestResults/ + - name: publish on version change id: publish_nuget - uses: alirezanet/publish-nuget@v3.0.4 + uses: alirezanet/publish-nuget@v3.1.0 with: # Filepath of the project to be packaged, relative to root of repository PROJECT_FILE_PATH: SharpPcap/SharpPcap.csproj diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index a35a7f6d..d0c6ace6 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -21,7 +21,7 @@ blocks: - checkout - sudo -E bash scripts/install-libpcap.sh - sudo -E bash scripts/install-tap.sh - - sudo -E bash scripts/install-dotnet.sh + - sudo -E bash scripts/install-dotnet.sh --install-dir /usr/local/bin - sudo -E bash scripts/test.sh --filter "TestCategory!=Performance" name: Test agent: @@ -39,6 +39,6 @@ blocks: - commands: - checkout - sudo -E bash scripts/install-libpcap.sh - - brew install --cask dotnet-sdk + - sudo -E bash scripts/install-dotnet.sh --install-dir /usr/local/bin - sudo -E bash scripts/test.sh --filter "TestCategory!=Performance" name: Test diff --git a/Examples/CreatingCaptureFile/CreatingCaptureFile.csproj b/Examples/CreatingCaptureFile/CreatingCaptureFile.csproj index 4a28e291..4f50a170 100644 --- a/Examples/CreatingCaptureFile/CreatingCaptureFile.csproj +++ b/Examples/CreatingCaptureFile/CreatingCaptureFile.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example1.IfList/Example01.IfList.csproj b/Examples/Example1.IfList/Example01.IfList.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example1.IfList/Example01.IfList.csproj +++ b/Examples/Example1.IfList/Example01.IfList.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example10.SendQueue/Example10.SendQueue.csproj b/Examples/Example10.SendQueue/Example10.SendQueue.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example10.SendQueue/Example10.SendQueue.csproj +++ b/Examples/Example10.SendQueue/Example10.SendQueue.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example11.Statistics/Example11.Statistics.csproj b/Examples/Example11.Statistics/Example11.Statistics.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example11.Statistics/Example11.Statistics.csproj +++ b/Examples/Example11.Statistics/Example11.Statistics.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example12.PacketManipulation/Example12.PacketManipulation.csproj b/Examples/Example12.PacketManipulation/Example12.PacketManipulation.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example12.PacketManipulation/Example12.PacketManipulation.csproj +++ b/Examples/Example12.PacketManipulation/Example12.PacketManipulation.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example2.ArpResolve/Example02.ArpResolve.csproj b/Examples/Example2.ArpResolve/Example02.ArpResolve.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example2.ArpResolve/Example02.ArpResolve.csproj +++ b/Examples/Example2.ArpResolve/Example02.ArpResolve.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example3.BasicCap/Example03.BasicCap.csproj b/Examples/Example3.BasicCap/Example03.BasicCap.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example3.BasicCap/Example03.BasicCap.csproj +++ b/Examples/Example3.BasicCap/Example03.BasicCap.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example4.BasicCapNoCallback/Example04.BasicCapNoCallback.csproj b/Examples/Example4.BasicCapNoCallback/Example04.BasicCapNoCallback.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example4.BasicCapNoCallback/Example04.BasicCapNoCallback.csproj +++ b/Examples/Example4.BasicCapNoCallback/Example04.BasicCapNoCallback.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example5.PcapFilter/Example05.PcapFilter.csproj b/Examples/Example5.PcapFilter/Example05.PcapFilter.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example5.PcapFilter/Example05.PcapFilter.csproj +++ b/Examples/Example5.PcapFilter/Example05.PcapFilter.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example6.DumpTCP/Example06.DumpTCP.csproj b/Examples/Example6.DumpTCP/Example06.DumpTCP.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example6.DumpTCP/Example06.DumpTCP.csproj +++ b/Examples/Example6.DumpTCP/Example06.DumpTCP.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/Example9.SendPacket/Example09.SendPacket.csproj b/Examples/Example9.SendPacket/Example09.SendPacket.csproj index 4a28e291..4f50a170 100644 --- a/Examples/Example9.SendPacket/Example09.SendPacket.csproj +++ b/Examples/Example9.SendPacket/Example09.SendPacket.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/MultipleFiltersOnDevice/MultipleFiltersOnDevice.csproj b/Examples/MultipleFiltersOnDevice/MultipleFiltersOnDevice.csproj index 4a28e291..4f50a170 100644 --- a/Examples/MultipleFiltersOnDevice/MultipleFiltersOnDevice.csproj +++ b/Examples/MultipleFiltersOnDevice/MultipleFiltersOnDevice.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/NpcapRemoteCapture/NpcapRemoteCapture.csproj b/Examples/NpcapRemoteCapture/NpcapRemoteCapture.csproj index 11ceca9a..50e21498 100644 --- a/Examples/NpcapRemoteCapture/NpcapRemoteCapture.csproj +++ b/Examples/NpcapRemoteCapture/NpcapRemoteCapture.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/QueuingPacketsForBackgroundProcessing/QueuingPacketsForBackgroundProcessing.csproj b/Examples/QueuingPacketsForBackgroundProcessing/QueuingPacketsForBackgroundProcessing.csproj index 4a28e291..4f50a170 100644 --- a/Examples/QueuingPacketsForBackgroundProcessing/QueuingPacketsForBackgroundProcessing.csproj +++ b/Examples/QueuingPacketsForBackgroundProcessing/QueuingPacketsForBackgroundProcessing.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/ReadingCaptureFile/ReadingCaptureFile.csproj b/Examples/ReadingCaptureFile/ReadingCaptureFile.csproj index 4a28e291..4f50a170 100644 --- a/Examples/ReadingCaptureFile/ReadingCaptureFile.csproj +++ b/Examples/ReadingCaptureFile/ReadingCaptureFile.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/WakeOnLan/WakeOnLan.csproj b/Examples/WakeOnLan/WakeOnLan.csproj index 4a28e291..4f50a170 100644 --- a/Examples/WakeOnLan/WakeOnLan.csproj +++ b/Examples/WakeOnLan/WakeOnLan.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/Examples/WinpkFilterExample/WinpkFilterExample.csproj b/Examples/WinpkFilterExample/WinpkFilterExample.csproj index 11ceca9a..50e21498 100644 --- a/Examples/WinpkFilterExample/WinpkFilterExample.csproj +++ b/Examples/WinpkFilterExample/WinpkFilterExample.csproj @@ -1,7 +1,7 @@  Exe - net5.0 + net6.0 diff --git a/README.md b/README.md index 0628e90d..d344994a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,22 @@ Fully managed, cross platform (Windows, Mac, Linux) .NET library for capturing p The official SharpPcap repository. +# Table of Contents +1. [Features](#features) +2. [Examples](#examples) + - [Listing Devices](#listing-devices) + - [Capturing Packets](#capturing-packets) + - [Reading from a Capture File](#reading-from-a-capture-file) + - [Writing to a Capture File](#writing-to-a-capture-file) +3. [CI Support](#ci-support) +4. [Releases](#releases) +5. [Platform Specific Notes](#platform-specific-notes) +6. [Migration from 5.x to 6.0](#migration-from-5x-to-60) + + + # Features -For packet dissection and creation see [Packet.Net](https://github.com/chmorgan/packetnet). +For packet dissection and creation, see [Packet.Net](https://github.com/chmorgan/packetnet). * On Linux, support for [libpcap](http://www.tcpdump.org/manpages/pcap.3pcap.html) @@ -24,14 +38,14 @@ For packet dissection and creation see [Packet.Net](https://github.com/chmorgan/ * Live device lists * Statistics * Reading packets from Live Devices (actual network devices) and Offline Devices (Capture files) - * Support for [Berkley Packet Filters](https://www.tcpdump.org/manpages/pcap-filter.7.html) + * Support for [Berkeley Packet Filters](https://www.tcpdump.org/manpages/pcap-filter.7.html) * Dumping packets to Pcap files. * Pcap and pcap-ng format (when using libpcap >=1.1.0 or npcap) * ReadOnlySpan<> is used to avoid memory allocation and copying inside of SharpPcap and provide the best performance. * Helper methods are provided to convert to object instances if it is desired to persist captured packets in memory. * NativeLibrary support - * Capture library resolution works cleanly across Linux, OSX, and Windows + * Capture library resolution operates smoothly across Linux, OSX, and Windows * Cleanly loads libpcap on Linux whether the distro has a symlink to libpcap.so or not. * .NET Core 3 and .NET Framework support @@ -50,6 +64,8 @@ And here => See the [Examples](https://github.com/chmorgan/sharppcap/tree/master/Examples) folder for a range of full example projects using SharpPcap + + ## Listing devices ```cs var devices = CaptureDeviceList.Instance; @@ -57,6 +73,8 @@ See the [Examples](https://github.com/chmorgan/sharppcap/tree/master/Examples) f Console.WriteLine("{0}\n", dev.ToString()); ``` + + ## Capturing packets ```cs void Device_OnPacketArrival(object s, PacketCapture e) @@ -70,6 +88,8 @@ See the [Examples](https://github.com/chmorgan/sharppcap/tree/master/Examples) f device.StartCapture(); ``` + + ## Reading from a capture file ```cs void Device_OnPacketArrival(object s, PacketCapture e) @@ -80,9 +100,11 @@ See the [Examples](https://github.com/chmorgan/sharppcap/tree/master/Examples) f using var device = new CaptureFileReaderDevice("filename.pcap"); device.Open(); device.OnPacketArrival += Device_OnPacketArrival; - device.StartCapture(); + device.Capture(); ``` + + ## Writing to a capture file ```cs using var device = new CaptureFileWriterDevice("somefilename.pcap", System.IO.FileMode.Open); @@ -90,15 +112,21 @@ See the [Examples](https://github.com/chmorgan/sharppcap/tree/master/Examples) f device.Write(bytes); ``` + + # CI support We have support for a number of CI systems for a few reasons: * Diversity of CI systems in case one of them shuts down * Examples in case you'd like to customize SharpPcap and make use of one of these CI systems for internal builds. Note that we assume you are following the license for the library. + + # Releases SharpPcap is released via nuget + + # Platform specific notes * OSX (at least as of 11.1) lacks libpcap with pcap_open @@ -112,6 +140,8 @@ We are especially appreciative of a number of projects we build upon (as SharpPc * libpcap - thank you so much for releasing 1.10 * npcap - for continuing packet capture support on Windows + + # Migration from 5.x to 6.0 We hope that you'll find the 6.x api to be cleaner and easier to use. @@ -121,7 +151,7 @@ We hope that you'll find the 6.x api to be cleaner and easier to use. To aid with the migration from 5.x to 6.0 here is a list of some of the changes you'll have to make to your SharpPcap usage. -The examples are also a great resource a they show working examples using the latest API. +The examples are also a great resource as they show working examples using the latest API. * Packet data is returned via PacketCapture which makes use of ReadOnlySpan<>. * Conversion from ReadOnlySpan<> to RawCapture is performed by PacketCapture.GetPacket(). @@ -129,7 +159,7 @@ The examples are also a great resource a they show working examples using the la * By avoiding memory allocation and memory copying, raw capture performance may be up to 30% faster. * Span's are ideal for use cases where packets are being dumped to disk for later processing. * NativeLibrary is used for improved capture library resolution - * Improves library reosolution situation on Linux distros where there is a libpcap.so.X.Y symlink but no libpcap.so symlink + * Improves library resolution situation on Linux distros where there is a libpcap.so.X.Y symlink but no libpcap.so symlink * Support for Mono DllMap has been removed as Mono supports NativeLibrary. See https://www.mono-project.com/news/2020/08/24/native-loader-net5/ * Devices are IDisposable * Remove calls to Close() @@ -138,7 +168,7 @@ The examples are also a great resource a they show working examples using the la * Open() methods have been collapsed into fewer methods with default variables. * DeviceMode has been replaced by DeviceModes as DeviceMode was not able to cover all of the combinations of ways you could open a device. * NpcapDevice -> LibPcapLiveDevice - * If you are using NpcapDevice you should consider using LibPcapLiveDevice. The latest versions of Npcap come with + * If you are using NpcapDevice, you should consider using LibPcapLiveDevice. The latest versions of Npcap come with newer versions of libpcap that provide almost all of the functionality of Npcap native APIs. * The current gap here is statistics mode, currently only supported by Npcap. * There has been talk of a statistics mode wrapper that would provide similar functionality, albeit without diff --git a/SharpPcap/LibPcap/CaptureFileReaderDevice.cs b/SharpPcap/LibPcap/CaptureFileReaderDevice.cs index 88eca784..97439198 100644 --- a/SharpPcap/LibPcap/CaptureFileReaderDevice.cs +++ b/SharpPcap/LibPcap/CaptureFileReaderDevice.cs @@ -27,7 +27,7 @@ namespace SharpPcap.LibPcap /// /// Read a pcap capture file /// - public class CaptureFileReaderDevice : PcapDevice + public class CaptureFileReaderDevice : CaptureReaderDevice { private readonly string m_pcapFile; @@ -128,17 +128,6 @@ public override void Open(DeviceConfiguration configuration) base.Open(configuration); } - - /// - /// Retrieves pcap statistics - /// - /// Not currently supported for this device - /// - /// - /// A - /// - public override ICaptureStatistics Statistics => null; - } } diff --git a/SharpPcap/LibPcap/CaptureFileWriterDevice.cs b/SharpPcap/LibPcap/CaptureFileWriterDevice.cs index eaec4560..0b204453 100644 --- a/SharpPcap/LibPcap/CaptureFileWriterDevice.cs +++ b/SharpPcap/LibPcap/CaptureFileWriterDevice.cs @@ -32,6 +32,7 @@ namespace SharpPcap.LibPcap public class CaptureFileWriterDevice : PcapDevice, IInjectionDevice { private readonly string m_pcapFile; + private readonly FileMode fileMode; /// /// Handle to an open dump file, not equal to IntPtr.Zero if a dump file is open @@ -80,11 +81,11 @@ public override string Description public CaptureFileWriterDevice(string captureFilename, System.IO.FileMode mode = FileMode.OpenOrCreate) { m_pcapFile = captureFilename; + fileMode = mode; - // append isn't possible without some difficulty and not implemented yet - if (mode == FileMode.Append) + if (mode == FileMode.Append && Pcap.LibpcapVersion < new Version(1, 7, 2)) { - throw new InvalidOperationException("FileMode.Append is not supported, please contact the developers if you are interested in helping to implementing it"); + throw new PlatformNotSupportedException("FileMode.Append is not supported"); } } @@ -134,7 +135,15 @@ public override void Open(DeviceConfiguration configuration) Handle = LibPcapSafeNativeMethods.pcap_open_dead((int)configuration.LinkLayerType, configuration.Snaplen); } - m_pcapDumpHandle = LibPcapSafeNativeMethods.pcap_dump_open(Handle, m_pcapFile); + if (fileMode == FileMode.Append) + { + m_pcapDumpHandle = LibPcapSafeNativeMethods.pcap_dump_open_append(Handle, m_pcapFile); + } + else + { + m_pcapDumpHandle = LibPcapSafeNativeMethods.pcap_dump_open(Handle, m_pcapFile); + } + if (m_pcapDumpHandle == IntPtr.Zero) throw new PcapException("Error opening dump file '" + LastError + "'"); diff --git a/SharpPcap/LibPcap/CaptureHandleReaderDevice.cs b/SharpPcap/LibPcap/CaptureHandleReaderDevice.cs new file mode 100644 index 00000000..55962167 --- /dev/null +++ b/SharpPcap/LibPcap/CaptureHandleReaderDevice.cs @@ -0,0 +1,93 @@ +/* +This file is part of SharpPcap. + +SharpPcap is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SharpPcap is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with SharpPcap. If not, see . +*/ + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace SharpPcap.LibPcap +{ + /// + /// Read a pcap capture from a file handle (e.g., a pipe). + /// + /// NOTE: libpcap will take ownership of the handle. The handle will be closed when this device is closed. + /// On non-Windows systems, the handle passed to this class MUST be opened by fopen or similar functions + /// that return a FILE* (e.g., via Mono.Posix.NETStandard). + /// + public class CaptureHandleReaderDevice : CaptureReaderDevice + { + /// + /// File handle the object was created with. + /// + public SafeHandle FileHandle { get; } + + /// + /// The name of the capture file. + /// + public override string Name => ""; + + /// + /// Description of the device. + /// + public override string Description => "Capture handle reader device"; + + /// + /// Creates a new CaptureHandleReaderDevice. + /// + /// + /// On Windows, a native Windows handle. On other systems, a pointer to a C runtime FILE object. + /// + public CaptureHandleReaderDevice(SafeHandle handle) + { + FileHandle = handle; + } + + /// + /// Opens the device. + /// + public override void Open(DeviceConfiguration configuration) + { + // holds errors + StringBuilder errbuf = new StringBuilder(Pcap.PCAP_ERRBUF_SIZE); + + var resolution = configuration.TimestampResolution ?? TimestampResolution.Microsecond; + PcapHandle adapterHandle; + try + { + adapterHandle = LibPcapSafeNativeMethods.pcap_open_handle_offline_with_tstamp_precision( + FileHandle, (uint)resolution, errbuf); + } + catch (TypeLoadException ex) + { + throw new NotSupportedException("libpcap 1.5.0 or higher is required for opening captures by handle", ex); + } + + // handle error + if (adapterHandle.IsInvalid) + { + string err = "Unable to open offline adapter: " + errbuf; + throw new PcapException(err); + } + + // set the device handle + Handle = adapterHandle; + + base.Open(configuration); + } + } +} + diff --git a/SharpPcap/LibPcap/CaptureReaderDevice.cs b/SharpPcap/LibPcap/CaptureReaderDevice.cs new file mode 100644 index 00000000..f6940fb2 --- /dev/null +++ b/SharpPcap/LibPcap/CaptureReaderDevice.cs @@ -0,0 +1,18 @@ +namespace SharpPcap.LibPcap +{ + /// + /// Base class for 'offline' devices. + /// + public abstract class CaptureReaderDevice : PcapDevice + { + /// + /// Retrieves pcap statistics. + /// + /// Not supported for this device. + /// + /// + /// A + /// + public override ICaptureStatistics Statistics => null; + } +} diff --git a/SharpPcap/LibPcap/LibPcapLiveDevice.cs b/SharpPcap/LibPcap/LibPcapLiveDevice.cs index 69383162..31861e1b 100644 --- a/SharpPcap/LibPcap/LibPcapLiveDevice.cs +++ b/SharpPcap/LibPcap/LibPcapLiveDevice.cs @@ -21,9 +21,9 @@ You should have received a copy of the GNU Lesser General Public License */ using System; -using System.Text; using System.Collections.ObjectModel; using System.Runtime.InteropServices; +using System.Text; namespace SharpPcap.LibPcap { @@ -134,6 +134,11 @@ public override void Open(DeviceConfiguration configuration) Name, // name of the device errbuf); // error buffer + if (Handle.IsInvalid) + { + var err = $"Unable to open the adapter '{Name}'. {errbuf}"; + throw new PcapException(err); + } // Those are configurations that pcap_open can handle differently Configure( configuration, nameof(configuration.Snaplen), diff --git a/SharpPcap/LibPcap/LibPcapSafeNativeMethods.Interop.cs b/SharpPcap/LibPcap/LibPcapSafeNativeMethods.Interop.cs index 78ed8e15..7e8308b7 100644 --- a/SharpPcap/LibPcap/LibPcapSafeNativeMethods.Interop.cs +++ b/SharpPcap/LibPcap/LibPcapSafeNativeMethods.Interop.cs @@ -104,6 +104,13 @@ internal extern static int pcap_findalldevs_ex( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PcapStringMarshaler))] string /*const char * */ fname ); + /// Append a file to write packets. + [DllImport(PCAP_DLL, CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr /* pcap_dumper_t * */ pcap_dump_open_append( + PcapHandle /*pcap_t * */adaptHandle, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PcapStringMarshaler))] string /*const char * */ fname + ); + /// /// Save a packet to disk. /// @@ -485,5 +492,34 @@ internal extern static int pcap_tstamp_type_name_to_val( [DllImport(PCAP_DLL, CallingConvention = CallingConvention.Cdecl)] internal extern static int pcap_setmode(PcapHandle /* pcap_t * */ p, int mode); + /// + /// Windows Only + /// Wraps a Pcap handle around an existing OS handle, e.g., a pipe. + /// + /// Native Windows handle. + /// Desired timestamp precision (micro/nano). + /// Buffer that will receive an error description if an error occurs. + /// + [DllImport(PCAP_DLL, EntryPoint = "pcap_hopen_offline_with_tstamp_precision", CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr /* pcap_t* */ _pcap_hopen_offline_with_tstamp_precision( + SafeHandle handle, + uint precision, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PcapStringMarshaler))] StringBuilder /* char* */ errbuf + ); + + /// + /// Non-Windows Only + /// Wraps a Pcap handle around a C runtime FILE object. + /// + /// Pointer to FILE as returned by fopen, etc. + /// Desired timestamp precision (micro/nano). + /// Buffer that will receive an error description if an error occurs. + /// + [DllImport(PCAP_DLL, EntryPoint = "pcap_fopen_offline_with_tstamp_precision", CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr /* pcap_t* */ _pcap_fopen_offline_with_tstamp_precision( + SafeHandle fileObject, + uint precision, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PcapStringMarshaler))] StringBuilder /* char* */ errbuf + ); } } diff --git a/SharpPcap/LibPcap/LibPcapSafeNativeMethods.cs b/SharpPcap/LibPcap/LibPcapSafeNativeMethods.cs index a2c9fdda..add75571 100644 --- a/SharpPcap/LibPcap/LibPcapSafeNativeMethods.cs +++ b/SharpPcap/LibPcap/LibPcapSafeNativeMethods.cs @@ -99,5 +99,26 @@ internal static int pcap_get_tstamp_precision(PcapHandle /* pcap_t* p */ adapter } #endregion + + /// + /// Wraps a Pcap handle around an existing file handle. + /// + /// Native file handle. On non-Windows systems, this must be a pointer + /// to a C runtime FILE object. + /// Desired timestamp precision (micro/nano). + /// Buffer that will receive an error description if an error occurs. + /// + internal static PcapHandle pcap_open_handle_offline_with_tstamp_precision( + SafeHandle handle, uint precision, StringBuilder errbuf) + { + var pointer = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? _pcap_hopen_offline_with_tstamp_precision(handle, precision, errbuf) + : _pcap_fopen_offline_with_tstamp_precision(handle, precision, errbuf); + if (pointer == IntPtr.Zero) + { + return PcapHandle.Invalid; + } + return new PcapFileHandle(pointer, handle); + } } } diff --git a/SharpPcap/LibPcap/PcapDeviceCaptureLoop.cs b/SharpPcap/LibPcap/PcapDeviceCaptureLoop.cs index d0d5d505..445b3f40 100644 --- a/SharpPcap/LibPcap/PcapDeviceCaptureLoop.cs +++ b/SharpPcap/LibPcap/PcapDeviceCaptureLoop.cs @@ -155,74 +155,94 @@ protected virtual void CaptureThread(CancellationToken cancellationToken) throw new DeviceNotReadyException("Capture called before PcapDevice.Open()"); var Callback = new LibPcapSafeNativeMethods.pcap_handler(PacketHandler); - - while (!cancellationToken.IsCancellationRequested) + var handle = Handle; + var gotRef = false; + try { - // TODO: This check can be removed once libpcap versions >= 1.10 has become in widespread use. - // libpcap 1.10 improves pcap_dispatch() to break out when pcap_breakloop() across threads - if (!PollFileDescriptor()) + // Make sure that handle does not get closed until this function is done + handle.DangerousAddRef(ref gotRef); + if (!gotRef) { - // We don't have data to read, don't call pcap_dispatch() yet - continue; + return; } + while (!cancellationToken.IsCancellationRequested) + { + // TODO: This check can be removed once libpcap versions >= 1.10 has become in widespread use. + // libpcap 1.10 improves pcap_dispatch() to break out when pcap_breakloop() across threads + if (!PollFileDescriptor()) + { + // We don't have data to read, don't call pcap_dispatch() yet + continue; + } - int res = LibPcapSafeNativeMethods.pcap_dispatch(Handle, m_pcapPacketCount, Callback, Handle.DangerousGetHandle()); + #pragma warning disable S3869 // "SafeHandle.DangerousGetHandle" should not be called + int res = LibPcapSafeNativeMethods.pcap_dispatch(handle, m_pcapPacketCount, Callback, handle.DangerousGetHandle()); + #pragma warning restore S3869 // "SafeHandle.DangerousGetHandle" should not be called - // pcap_dispatch() returns the number of packets read or, a status value if the value - // is negative - if (res <= 0) - { - switch (res) // Check pcap loop status results and notify upstream. + // pcap_dispatch() returns the number of packets read or, a status value if the value + // is negative + if (res <= 0) { - case Pcap.LOOP_USER_TERMINATED: // User requsted loop termination with StopCapture() - SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); - return; - case Pcap.LOOP_COUNT_EXHAUSTED: // m_pcapPacketCount exceeded (successful exit) - { - // NOTE: pcap_dispatch() returns 0 when a timeout occurrs so to prevent timeouts - // from causing premature exiting from the capture loop we only consider - // exhausted events to cause an escape from the loop when they are from - // offline devices, ie. files read from disk - if (this is CaptureFileReaderDevice) + switch (res) // Check pcap loop status results and notify upstream. + { + case Pcap.LOOP_USER_TERMINATED: // User requsted loop termination with StopCapture() + SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); + return; + case Pcap.LOOP_COUNT_EXHAUSTED: // m_pcapPacketCount exceeded (successful exit) { - SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); - return; + // NOTE: pcap_dispatch() returns 0 when a timeout occurrs so to prevent timeouts + // from causing premature exiting from the capture loop we only consider + // exhausted events to cause an escape from the loop when they are from + // offline devices, ie. files read from disk + #pragma warning disable S3060 + if (this is CaptureReaderDevice) + #pragma warning restore S3060 + { + SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); + return; + } + break; } - break; - } - case Pcap.LOOP_EXIT_WITH_ERROR: // An error occurred whilst capturing. - SendCaptureStoppedEvent(CaptureStoppedEventStatus.ErrorWhileCapturing); - return; - default: - // This can only be triggered by a bug in libpcap. - // We can't throw here, sicne that would crash the application - Trace.TraceError($"SharpPcap: Unknown pcap_loop exit status: {res}"); - SendCaptureStoppedEvent(CaptureStoppedEventStatus.ErrorWhileCapturing); - return; + case Pcap.LOOP_EXIT_WITH_ERROR: // An error occurred whilst capturing. + SendCaptureStoppedEvent(CaptureStoppedEventStatus.ErrorWhileCapturing); + return; + default: + // This can only be triggered by a bug in libpcap. + // We can't throw here, sicne that would crash the application + Trace.TraceError($"SharpPcap: Unknown pcap_loop exit status: {res}"); + SendCaptureStoppedEvent(CaptureStoppedEventStatus.ErrorWhileCapturing); + return; + } } - } - else // res > 0 - { - // if we aren't capturing infinitely we need to account for - // the packets that we read - if (m_pcapPacketCount != Pcap.InfinitePacketCount) + else // res > 0 { - // take away for the packets read - if (m_pcapPacketCount >= res) - m_pcapPacketCount -= res; - else - m_pcapPacketCount = 0; - - // no more packets to capture, we are finished capturing - if (m_pcapPacketCount == 0) + // if we aren't capturing infinitely we need to account for + // the packets that we read + if (m_pcapPacketCount != Pcap.InfinitePacketCount) { - SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); - return; + // take away for the packets read + if (m_pcapPacketCount >= res) + m_pcapPacketCount -= res; + else + m_pcapPacketCount = 0; + + // no more packets to capture, we are finished capturing + if (m_pcapPacketCount == 0) + { + SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); + return; + } } } } } - + finally + { + if (gotRef) + { + handle.DangerousRelease(); + } + } SendCaptureStoppedEvent(CaptureStoppedEventStatus.CompletedWithoutError); } } diff --git a/SharpPcap/LibPcap/PcapHandle.cs b/SharpPcap/LibPcap/PcapHandle.cs index 7216f00a..dff52313 100644 --- a/SharpPcap/LibPcap/PcapHandle.cs +++ b/SharpPcap/LibPcap/PcapHandle.cs @@ -21,6 +21,7 @@ You should have received a copy of the GNU Lesser General Public License using Microsoft.Win32.SafeHandles; using System; +using System.Runtime.InteropServices; namespace SharpPcap.LibPcap { @@ -45,4 +46,33 @@ protected override bool ReleaseHandle() internal static readonly PcapHandle Invalid = new PcapHandle(IntPtr.Zero); } + + /// + /// Wrapper class that maintains reference to both pcap handle and file handle + /// + internal class PcapFileHandle : PcapHandle + { + private readonly SafeHandle FileHandle; + + public PcapFileHandle(IntPtr pcapHandle, SafeHandle fileHandle) + { + bool gotRef = false; + // The file handle must not be closed by the runtime until the pcap handle is also closed + // Incrementing the ref count ensure this + fileHandle.DangerousAddRef(ref gotRef); + FileHandle = gotRef ? fileHandle : new SafeFileHandle(IntPtr.Zero, false); + SetHandle(pcapHandle); + } + + // If somehow the file handle became invalid, the pcap handle will also be invalid + public override bool IsInvalid => base.IsInvalid || FileHandle.IsInvalid; + + protected override bool ReleaseHandle() + { + // Closing the pcap handle will also close the file handle + FileHandle.SetHandleAsInvalid(); + return base.ReleaseHandle(); + } + } + } diff --git a/SharpPcap/SharpPcap.csproj b/SharpPcap/SharpPcap.csproj index 6db4a251..faf780aa 100644 --- a/SharpPcap/SharpPcap.csproj +++ b/SharpPcap/SharpPcap.csproj @@ -6,6 +6,7 @@ Lansweeper Lansweeper SharpPcap Lansweeper SharpPcap + 6.2.5 A packet capture framework for .NET Tamir Gal, Chris Morgan and others https://github.com/chmorgan/sharppcap @@ -24,10 +25,10 @@ true - - + + - + diff --git a/Test/CaptureFileWriterDeviceTest.cs b/Test/CaptureFileWriterDeviceTest.cs index 2de36727..58f1013b 100644 --- a/Test/CaptureFileWriterDeviceTest.cs +++ b/Test/CaptureFileWriterDeviceTest.cs @@ -36,6 +36,7 @@ public void TestFileCreationAndDeletion() } [Test] + [LibpcapVersion("<1.7.2")] public void TestCreationOptions() { // valid arguments results in the object being created @@ -43,13 +44,29 @@ public void TestCreationOptions() valid.Open(linkLayerType: PacketDotNet.LinkLayers.Ethernet); // file mode of append should throw - Assert.Throws(() => + Assert.Throws(() => { using var wd = new CaptureFileWriterDevice("somefilename.pcap", System.IO.FileMode.Append); wd.Open(linkLayerType: PacketDotNet.LinkLayers.Ethernet); }); } + [Test] + [LibpcapVersion(">=1.7.2")] + public void TestCreationOption2() + { + // valid arguments results in the object being created + using (var valid = new CaptureFileWriterDevice("somefilename.pcap", System.IO.FileMode.Open)) + { + valid.Open(linkLayerType: PacketDotNet.LinkLayers.Ethernet); + } + + using (var validAppend = new CaptureFileWriterDevice("somefilename.pcap", System.IO.FileMode.Append)) + { + validAppend.Open(linkLayerType: PacketDotNet.LinkLayers.Ethernet); + } + } + /// /// Test opening the writer device using another interface's linklayer type /// diff --git a/Test/CaptureHandleReaderDeviceTest.cs b/Test/CaptureHandleReaderDeviceTest.cs new file mode 100644 index 00000000..d548835c --- /dev/null +++ b/Test/CaptureHandleReaderDeviceTest.cs @@ -0,0 +1,185 @@ +using System; +using NUnit.Framework; +using SharpPcap; +using SharpPcap.LibPcap; +using PacketDotNet; +using System.IO; +using System.Runtime.InteropServices; +using Mono.Unix.Native; +using Microsoft.Win32.SafeHandles; + +namespace Test +{ + [TestFixture] + [LibpcapVersion(">=1.5.0")] + public class CaptureHandleReaderDeviceTest + { + private static int capturedPackets; + + private SafeHandle GetTestFileHandle(string filename) + { + filename = TestHelper.GetFile(filename); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On Windows, we need an actual OS handle. + // The FileStream is not disposed, which is alright because we take care of its handle. + // Once .NET Framework is no longer supported, this can be simplified to File.OpenHandle(). + return File.Open(filename, FileMode.Open).SafeFileHandle; + } + // On other platforms, libpcap is not very interop-friendly and expects a FILE*. + return new StdlibFileHandle(Stdlib.fopen(filename, "rb"), true); + } + + [Category("Timestamp")] + [TestCase(TimestampResolution.Nanosecond, "1186341404.189852000s")] + [TestCase(TimestampResolution.Microsecond, "1186341404.189852s")] + public void CaptureTimestampResolution(TimestampResolution resolution, string timeval) + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + var configuration = new DeviceConfiguration + { + TimestampResolution = resolution + }; + device.Open(configuration); + Assert.AreEqual(resolution, device.TimestampResolution); + device.GetNextPacket(out var packet); + Assert.AreEqual(timeval, packet.Header.Timeval.ToString()); + } + + [Test] + public void FileHandleInvalidUponClose() + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + Assert.IsFalse(handle.IsInvalid); + Assert.IsFalse(handle.IsClosed); + { + using var device = new CaptureHandleReaderDevice(handle); + device.Open(); + } + Assert.IsTrue(handle.IsClosed); + } + + [Test] + public void CaptureProperties() + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + device.Open(); + Assert.IsNotEmpty(device.Description); + Assert.AreEqual(handle, device.FileHandle); + } + + /// + /// Test that we can retrieve packets from a pcap file just as we would from + /// a live capture device and that all packets are captured + /// + [Test] + public void CaptureInfinite() + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + device.OnPacketArrival += HandleDeviceOnPacketArrival; + device.Open(); + + var expectedPackets = 10; + capturedPackets = 0; + device.Capture(); + + Assert.AreEqual(expectedPackets, capturedPackets); + } + + /// + /// Test that if we ask to capture a finite number of packets that + /// only this number of packets will be captured + /// + [Test] + public void CaptureFinite() + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + device.OnPacketArrival += HandleDeviceOnPacketArrival; + device.Open(); + + var expectedPackets = 3; + capturedPackets = 0; + device.Capture(expectedPackets); + + Assert.AreEqual(expectedPackets, capturedPackets); + } + + void HandleDeviceOnPacketArrival(object sender, PacketCapture e) + { + Console.WriteLine("got packet " + e.GetPacket().ToString()); + capturedPackets++; + } + + /// + /// Test that we get expected unsupport indication when attempting to retrieve + /// Statistics from this device + /// + [Test] + public void StatisticsUnsupported() + { + const string filename = "ipv6_http.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + device.Open(); + Assert.IsNull(device.Statistics); + } + + [Test] + public void SetFilter() + { + const string filename = "test_stream.pcap"; + using var handle = GetTestFileHandle(filename); + using var device = new CaptureHandleReaderDevice(handle); + + device.Open(); + device.Filter = "port 53"; + + RawCapture rawPacket; + int count = 0; + PacketCapture e; + GetPacketStatus retval; + do + { + retval = device.GetNextPacket(out e); + if (retval == GetPacketStatus.PacketRead) + { + rawPacket = e.GetPacket(); + Packet p = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data); + var udpPacket = p.Extract(); + Assert.IsNotNull(udpPacket); + int dnsPort = 53; + Assert.AreEqual(dnsPort, udpPacket.DestinationPort); + count++; + } + } while (retval == GetPacketStatus.PacketRead); + + Assert.AreEqual(1, count); + } + } + + + internal class StdlibFileHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public StdlibFileHandle(IntPtr preexistingHandle, bool ownsHandle) + : base(ownsHandle) + { + SetHandle(preexistingHandle); + } + + protected override bool ReleaseHandle() + { + return Stdlib.fclose(handle) == 0; + } + + } + +} diff --git a/Test/DeviceFixture.cs b/Test/DeviceFixture.cs index 84a4d5c8..159f07d0 100644 --- a/Test/DeviceFixture.cs +++ b/Test/DeviceFixture.cs @@ -64,6 +64,8 @@ public static IEnumerable GetDevices() .Where(d => d.Name != "virbr0-nic") // TAP interfaces, usually down until being used .Where(d => !d.Name.StartsWith("tap")) + // From AppVeyor + .Where(d => !d.Name.StartsWith("dbus")) .Distinct(); } } diff --git a/Test/SendQueueTest.cs b/Test/SendQueueTest.cs index 5a657bef..364bbf51 100644 --- a/Test/SendQueueTest.cs +++ b/Test/SendQueueTest.cs @@ -16,7 +16,7 @@ namespace Test public class SendQueueTest { private const string Filter = "ether proto 0x1234"; - private const int PacketCount = 100; + private const int PacketCount = 500; // Windows is usually able to simulate inter packet gaps down to 20µs // We test with 100µs to avoid flaky tests internal static readonly decimal DeltaTime = 100E-6M; @@ -24,6 +24,9 @@ public class SendQueueTest /// /// Transmit with normal works correctly /// + /// + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestTransmitNormalSyncFalse() { @@ -44,6 +47,8 @@ public void TestTransmitNormalSyncFalse() /// /// Transmit with Normal works as expected /// + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestTransmitNormal() { @@ -101,6 +106,8 @@ public void TestAddRawCapture() Assert.AreEqual(PcapHeader.MemorySize + rawCapture.PacketLength, queue.CurrentLength); } + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestNativeTransmitNormal() { @@ -118,6 +125,8 @@ public void TestNativeTransmitNormal() } } + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestNativeTransmitSync() { @@ -135,6 +144,8 @@ public void TestNativeTransmitSync() } } + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestManagedTransmitNormal() { @@ -145,6 +156,8 @@ public void TestManagedTransmitNormal() AssertGoodTransmitNormal(received); } + // This test gets affected by host performance, so retry it up to 3 times + [Retry(3)] [Test] public void TestManagedTransmitSync() { diff --git a/Test/Test.csproj b/Test/Test.csproj index 4934f32a..c64a0ca5 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -1,20 +1,21 @@  true - net5.0 + net6.0 $(TargetFrameworks);net48 opencover - + all runtime; build; native; contentfiles; analyzers - - - + + + + - + diff --git a/Test/ThreadSafeTests.cs b/Test/ThreadSafeTests.cs new file mode 100644 index 00000000..cd0fc2ba --- /dev/null +++ b/Test/ThreadSafeTests.cs @@ -0,0 +1,89 @@ +using NUnit.Framework; +using SharpPcap; +using SharpPcap.LibPcap; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + /// + /// This is not about making PcapDevice 100% thread safe + /// this is about prevent memory access violation + /// + [TestFixture] + public class ThreadSafeTests + { + + [Test] + public void TestThreadSafety() + { + var pcapDevice = TestHelper.GetPcapDevice(); + var devices = Enumerable.Range(0, Environment.ProcessorCount) + .Select(_ => new LibPcapLiveDevice(pcapDevice.Interface)) + .ToArray(); + + foreach (var device in devices) + { + Initialize(device); + } + Thread.Sleep(TimeSpan.FromMinutes(1)); + foreach (var device in devices) + { + device.Dispose(); + } + } + + private static void Initialize(LibPcapLiveDevice device) + { + var retry = 0; + while (retry <= 7) + { + try + { + device.Open(DeviceModes.Promiscuous); + device.OnPacketArrival += Device_OnPacketArrival; + device.StartCapture(); + return; + } + catch (PcapException) + { + // Race condition while closing/opening device, retry + retry++; + } + } + } + + private static void Device_OnPacketArrival(object sender, PacketCapture e) + { + try + { + var device = (LibPcapLiveDevice)sender; + Task.Run(() => + { + try + { + device.Dispose(); + Thread.Sleep(1); + Initialize(device); + } + catch (Exception ex) + { + Console.WriteLine($"Exception happened in thread:{ex}"); + } + }); + // Trigger the data to be read from the pcap_t memory + e.GetPacket(); + } + catch (DeviceNotReadyException) + { + // Pass, normal + } + catch (ObjectDisposedException) + { + // Pass, normal + } + } + } +} diff --git a/Tutorial/README.md b/Tutorial/README.md index 39aa1ec6..30687f1e 100644 --- a/Tutorial/README.md +++ b/Tutorial/README.md @@ -448,9 +448,9 @@ SharpPcap represents a send queue using the `SendQueue` class which is construct Once the send queue is created, `SendQueue.Add()` can be called to add a packet to the send queue. This function takes a `PcapHeader` with the packet's timestamp and length and a buffer or a `Packet` object holding the data of the packet. These parameters are the same as those received by the `OnPacketArrival` event, therefore queuing a packet that was just captured or a read from a file is a matter of passing these parameters to `SendQueue.Add()`. -To transmit a send queue, SharpPcap provides the `WinPcapDevice.SendQueue(SendQueue q, SendQueueTransmitModes transmitMode)` function. Note the second parameter: if `SendQueueTransmitModes.Synchronized`, the send will be _synchronized_, i.e. the relative timestamps of the packets will be respected. This operation requires a remarkable amount of CPU, because the synchronization takes place in the kernel driver using "busy wait" loops. Although this operation is quite CPU intensive, it often results in very high precision packet transmissions (often around few microseconds or less). +To transmit a send queue, SharpPcap provides the `SendQueue.Transmit(PcapDevice device, SendQueueTransmitModes transmitMode)` function. Note the second parameter: if `SendQueueTransmitModes.Synchronized`, the send will be _synchronized_, i.e. the relative timestamps of the packets will be respected. This operation requires a remarkable amount of CPU, because the synchronization takes place in the kernel driver using "busy wait" loops. Although this operation is quite CPU intensive, it often results in very high precision packet transmissions (often around few microseconds or less). -Note that transmitting a send queue with `WinPcapDevice.SendQueue()` is more efficient than performing a series of `ICaptureDevice.SendPacket()`, since the send queue buffered at kernel level drastically decreases the number of context switches. +Note that transmitting a send queue with `SendQueue.Transmit()` is more efficient than performing a series of `ICaptureDevice.SendPacket()`, since the send queue buffered at kernel level drastically decreases the number of context switches. When a queue is no longer needed, it can be deleted with `SendQueue.Dispose()` that frees all the buffers associated with the send queue. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b37c4f4b..3e7e7836 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,7 +15,7 @@ pr: jobs: - job: linux pool: - vmImage: 'ubuntu-18.04' + vmImage: ubuntu-20.04 steps: - script: sudo -E bash scripts/install-libpcap.sh - script: sudo -E bash scripts/install-tap.sh @@ -25,32 +25,19 @@ jobs: - job: macos pool: - vmImage: 'macOS-10.15' + vmImage: macOS-11 steps: + - script: sudo -E bash scripts/install-dotnet.sh - script: sudo -E bash scripts/install-libpcap.sh - script: sudo sysctl -w net.inet.udp.maxdgram=65535 - script: sudo -E bash scripts/test.sh env: CODECOV_TOKEN: $(CODECOV_TOKEN) -# NOTE: Remove when npcap has rpcapd support -- job: windows_2019_winpcap + # This CI job is for testing of x86 architecture compatibility +- job: windows_2022_x86 pool: - vmImage: 'windows-2019' - steps: - # tap windows have to be installed before winpcap, - # otherwise winpcap will only pick it up after reboot - - pwsh: .\scripts\install-windows.ps1 - - script: choco install -y winpcap - - pwsh: .\scripts\install-winpkfilter.ps1 - - script: dotnet restore -s https://api.nuget.org/v3/index.json - - script: bash scripts/test.sh --filter "TestCategory!=Timestamp" - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) - -- job: windows_2019_npcap - pool: - vmImage: windows-2019 + vmImage: windows-2022 steps: - pwsh: ./scripts/Install-windows.ps1 - pwsh: ./scripts/Install-npcap.ps1 @@ -58,8 +45,7 @@ jobs: npcap_oem_key: $(npcap_oem_key) - pwsh: .\scripts\install-winpkfilter.ps1 - script: dotnet restore -s https://api.nuget.org/v3/index.json - # This CI job doubles as test of backward compatibility with windows 2019 - # and a cross architecture verification for x86 + # NOTE: Remove filter when npcap has rpcapd support - script: bash scripts/test.sh --filter "TestCategory!=RemotePcap" -r win-x86 env: CODECOV_TOKEN: $(CODECOV_TOKEN) @@ -74,6 +60,7 @@ jobs: npcap_oem_key: $(npcap_oem_key) - pwsh: .\scripts\install-winpkfilter.ps1 - script: dotnet restore -s https://api.nuget.org/v3/index.json + # NOTE: Remove filter when npcap has rpcapd support - script: bash scripts/test.sh --filter "TestCategory!=RemotePcap" env: CODECOV_TOKEN: $(CODECOV_TOKEN) diff --git a/scripts/codecov.sh b/scripts/codecov.sh new file mode 100644 index 00000000..6b9fbe3b --- /dev/null +++ b/scripts/codecov.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Logic for OS detection from https://circleci.com/developer/orbs/orb/codecov/codecov + +family=$(uname -s | tr '[:upper:]' '[:lower:]') +os="windows" + +[[ $family == "darwin" ]] && os="macos" + +[[ $family == "linux" ]] && os="linux" +[[ $os == "linux" ]] && osID=$(grep -e "^ID=" /etc/os-release | cut -c4-) +[[ $osID == "alpine" ]] && os="alpine" + +filename="codecov" +[[ $os == "windows" ]] && filename+=".exe" + +arch=$(uname -m) +if [[ $arch == arm64 ]] || [ $arch == aarch64 ] +then + # Skip until Codecov fix ARM support + # We won't lose coverage since we have no ARM specific code + # See https://github.com/codecov/uploader/issues/523 + exit +fi + +curl -Os "https://uploader.codecov.io/latest/${os}/${filename}" +chmod +x $filename +./$filename "$@" diff --git a/scripts/install-dotnet.sh b/scripts/install-dotnet.sh index 2dc69eda..75ae70bb 100644 --- a/scripts/install-dotnet.sh +++ b/scripts/install-dotnet.sh @@ -1,4 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash + set -e -curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 5.0 --install-dir /usr/local/bin +curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 6.0 "$@" + +dotnet --list-sdks diff --git a/scripts/install-windows.ps1 b/scripts/install-windows.ps1 index a4bb01cd..24edb86b 100644 --- a/scripts/install-windows.ps1 +++ b/scripts/install-windows.ps1 @@ -1,4 +1,4 @@ -choco install -y dotnet-sdk --version=5.0.403 +choco install -y dotnet-sdk --version=6.0.300 choco install -y tapwindows choco install -y procdump diff --git a/scripts/install-winpkfilter.ps1 b/scripts/install-winpkfilter.ps1 index d5195328..ffefdb14 100644 --- a/scripts/install-winpkfilter.ps1 +++ b/scripts/install-winpkfilter.ps1 @@ -2,9 +2,14 @@ Push-Location $env:TEMP $arch = If ([Environment]::Is64BitOperatingSystem) {'x64'} Else {'x86'} +$version = "3.2.32.1" +$url = "https://github.com/wiresock/ndisapi/releases/download/v$version/Windows.Packet.Filter.$version.$arch.msi" -Invoke-WebRequest "https://www.ntkernel.com/downloads/Windows Packet Filter 3.2.29.1%20$arch.msi" -OutFile "WinpkFilter-$arch.msi" -Start-Process "WinpkFilter-$arch.msi" -ArgumentList "/norestart /quiet /l WinpkFilter-$arch.log" -wait -type "WinpkFilter-$arch.log" +echo "Downloading $url" +Invoke-WebRequest $url -OutFile "WinpkFilter-$arch.msi" +$process = Start-Process "WinpkFilter-$arch.msi" -ArgumentList "/norestart /quiet /l WinpkFilter-$arch.log" -PassThru -Wait +Get-Content "WinpkFilter-$arch.log" Pop-Location + +exit $process.ExitCode diff --git a/scripts/test.sh b/scripts/test.sh index 93bf6e50..0703f1a7 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -23,25 +23,4 @@ dotnet test "${TEST_ARGS[@]}" # coverage -CODECOV_ARGS=( -f '**/*.opencover.xml' ) -if [ -n "$SYSTEM_JOBDISPLAYNAME" ] -then - CODECOV_ARGS+=( --flag "$SYSTEM_JOBDISPLAYNAME" ) -fi - -if [ -n "$SYSTEM_PULLREQUEST_SOURCECOMMITID" ] # Azure Pipelines -then - CODECOV_ARGS+=( --sha "$SYSTEM_PULLREQUEST_SOURCECOMMITID" ) - CODECOV_ARGS+=( --branch "$SYSTEM_PULLREQUEST_SOURCEBRANCH" ) -fi - -# Depending on CI, dotnet tool or bash should be used -# This is temporary until codecov fixes CI detection in the dotnet tool - -if [ -n "$SYSTEM_TEAMFOUNDATIONCOLLECTIONURI" ] -then - dotnet tool restore - dotnet codecov ${CODECOV_ARGS[@]} -else - bash <(curl -s https://codecov.io/bash) "${CODECOV_ARGS[@]}" -fi +bash $(dirname $0)/codecov.sh -f '**/*.opencover.xml'