Skip to content

Comments

Read and write pcap files without libpcap#2034

Merged
seladb merged 20 commits intodevfrom
read-write-pcap
Jan 16, 2026
Merged

Read and write pcap files without libpcap#2034
seladb merged 20 commits intodevfrom
read-write-pcap

Conversation

@seladb
Copy link
Owner

@seladb seladb commented Dec 30, 2025

This PR implements parsing and writing pcap files without using libpcap. It's a rewrite of PcapFileReaderDevice and PcapFileWriterDevice classes.

This change preserves the interface of these classes and deprecates PcapFileReaderDevice::isNanoSecondPrecisionSupported() and PcapFileWriterDevice::isNanoSecondPrecisionSupported() which always return true.

The tests, specifically in FileTests.cpp, were heavily refactored, mostly added more tests, and changed tests that are less relevant.

After this change, the only part that will still use libpcap is setting and matching BPF filters. I'm thinking how to remove libpcap dependency here as well, but will be done in a separate PR.

@codecov
Copy link

codecov bot commented Dec 31, 2025

Codecov Report

❌ Patch coverage is 97.45011% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.68%. Comparing base (50c8430) to head (59d557f).
⚠️ Report is 1 commits behind head on dev.

Files with missing lines Patch % Lines
Pcap++/src/PcapFileDevice.cpp 95.20% 6 Missing and 7 partials ⚠️
Tests/Pcap++Test/Tests/FileTests.cpp 98.74% 1 Missing and 6 partials ⚠️
Tests/Pcap++Test/Common/TestUtils.cpp 89.47% 1 Missing and 1 partial ⚠️
Tests/Pcap++Test/Tests/FilterTests.cpp 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #2034      +/-   ##
==========================================
+ Coverage   83.46%   83.68%   +0.22%     
==========================================
  Files         312      312              
  Lines       54858    55467     +609     
  Branches    11819    11996     +177     
==========================================
+ Hits        45787    46418     +631     
+ Misses       7864     7861       -3     
+ Partials     1207     1188      -19     
Flag Coverage Δ
alpine320 76.21% <93.43%> (+0.24%) ⬆️
fedora42 75.98% <93.41%> (+0.48%) ⬆️
macos-14 81.76% <94.68%> (+0.27%) ⬆️
macos-15 81.75% <94.76%> (+0.27%) ⬆️
mingw32 70.00% <85.43%> (-0.13%) ⬇️
mingw64 69.91% <85.43%> (-0.08%) ⬇️
npcap 85.42% <94.96%> (+0.17%) ⬆️
rhel94 75.56% <93.45%> (+0.14%) ⬆️
ubuntu2004 59.56% <64.91%> (+0.13%) ⬆️
ubuntu2004-zstd 59.66% <64.91%> (+0.12%) ⬆️
ubuntu2204 75.50% <93.42%> (+0.14%) ⬆️
ubuntu2204-icpx 58.43% <69.07%> (+0.35%) ⬆️
ubuntu2404 75.88% <93.61%> (+0.31%) ⬆️
ubuntu2404-arm64 75.88% <93.45%> (+0.24%) ⬆️
unittest 83.68% <97.45%> (+0.22%) ⬆️
windows-2022 85.42% <94.96%> (+0.17%) ⬆️
windows-2025 85.44% <94.96%> (+0.12%) ⬆️
winpcap 85.65% <94.96%> (+0.12%) ⬆️
xdp 52.14% <4.13%> (-0.86%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this change is that the link type of RAW_IP_PCAP_PATH is LINKTYPE_DLT_RAW1 (value of 12), which in libpcap is translated to LINKTYPE_RAW (value of 101): https://github.com/the-tcpdump-group/libpcap/blob/master/pcap-common.c#L1399-L1400

LINKTYPE_RAW doesn't have good support in BPF filters, so I replaced it with LINKTYPE_NULL

@Dimi1010
Copy link
Collaborator

Can we deprecate the Nano precision supported methods instead of outright removing them (we can make them always return true)? I think they were part of the previous releases?

@seladb
Copy link
Owner Author

seladb commented Dec 31, 2025

Can we deprecate the Nano precision supported methods instead of outright removing them (we can make them always return true)? I think they were part of the previous releases?

You're right, it's better to deprecate than to remove them altogether.

Added back in d16a361

@seladb seladb changed the title [DRAFT] Read and write pcap files without libpcap Read and write pcap files without libpcap Dec 31, 2025
Copy link
Collaborator

@Dimi1010 Dimi1010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through the reader device. Will look through the writer later.

@seladb seladb requested a review from Dimi1010 January 2, 2026 10:57
};

static bool checkNanoSupport()
LinkLayerType toLinkLayerType(uint32_t value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can check if it is invalid at the beginning and simply call static_cast<LinkLayerType>(value) to avoid the long switch.

Copy link
Collaborator

@Dimi1010 Dimi1010 Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LinkLayerType is not a packed enum, tho. There are gaps, e.g. there are no enum names for values between 14(iirc) and 50.

Copy link
Owner Author

@seladb seladb Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are more gaps, for example: between 51 - 100, 101 - 104, 114 - 117, and more. There's is no easy way to do this conversion

Comment on lines 536 to 539
if (m_DeviceOpened || m_PcapFile.is_open())
{
// TODO: Ambiguity in API
// If appendMode is required but the file is already opened in write mode.
PCPP_LOG_DEBUG("Pcap descriptor already opened. Nothing to do");
return true;
PCPP_LOG_ERROR("File already opened");
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe seperate the checks to make the error message correct.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need to store m_DeviceOpened? We could make it to instead infer if the device is opened purely, though m_PcapFile.is_open()?

Copy link
Owner Author

@seladb seladb Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we rely just on m_PcapFile.is_open() we need to always close the file if something goes wrong in open() after we open it. We can use a temp file for this and transfer the ownership to m_PcapFile in the end of open(), like this:

std::fstream pcapFile;
pcapFile.open(m_FileName, flags);

// The rest of the method

m_PcapFile = std::move(pcapFile);
return true;

We can do the same in PcapFileReaderDevice. Please let me know what you think.

EDIT: if we do that we also need to override isOpened() and ignore m_DeviceOpened that is defined in IFileDevice...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in this commit: 8174ba1

If you think we shouldn't I can revert this commit.

Copy link
Collaborator

@Dimi1010 Dimi1010 Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I like the construct as local -> move, as we don't maintain any state if open fails midway, for example if header is partial, broken or something.

EDIT: if we do that we also need to override isOpened() and ignore m_DeviceOpened that is defined in IFileDevice...

Honestly, forgot about that detail. I think it would be better to keep the m_DeviceOpened as it was for now to keep the PR simple.

When this PR is merged I think we can simplify some of the upper interfaces. Some examples:

  • IFileDevice inherits IPcapDevice - this is only because m_PcapDescriptor was used in pcap parsing.
    This leads to all file devices having a pcap handle member. I think we can drop that and inherit directly from IFilterableDevice?
  • Drop the close override on IFileDevice (and the destructor body) as there wouldn't be a pcap descriptor to close.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this PR is merged I think we can simplify some of the upper interfaces. Some examples:

* `IFileDevice` inherits `IPcapDevice` - this is only because `m_PcapDescriptor` was used in pcap parsing.
  This leads to all file devices having a pcap handle member. I think we can drop that and inherit directly from `IFilterableDevice`?

* Drop the `close` override on `IFileDevice` (and the destructor body) as there wouldn't be a pcap descriptor to close.

Yes, I was planning to do it in follow up PRs. The ultimate goal is to not require libpcap at all, like the other capture engines, but it will take time because we need to figure out the BPF filtering...

Honestly, forgot about that detail. I think it would be better to keep the m_DeviceOpened as it was for now to keep the PR simple.

So do you suggest I revert the override of isOpened() and continue using m_DeviceOpened for now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ultimate goal is to not require libpcap at all, like the other capture engines, but it will take time because we need to figure out the BPF filtering...

Hmm. Curious as to why tho. I see the benefit for standalone read / write, but I think keeping the libpcap dependency for filtering support seems ok? The only issue would be modularising the code so that it can be compiled without filter support.

So do you suggest I revert the override of isOpened() and continue using m_DeviceOpened for now?

Yeah, we can leave it to use the logic from IFileDevice for now.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can leave it to use the logic from IFileDevice for now.

Sure, done in this commit: f6b92ea

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Curious as to why tho. I see the benefit for standalone read / write, but I think keeping the libpcap dependency for filtering support seems ok? The only issue would be modularising the code so that it can be compiled without filter support.

Today libpcap / Npcap / WinPcap are different from the other capture engines we support because they are always linked, even if an app doesn't need them. For example: if someone wants to write an application that uses PcapPlusPlus with DPDK, they still have to link libpcap.

The long term goal is to remove this dependency:

  • People can build PcapPlusPlus with any capture engine, and libpcap is just one of the options
  • libpcap is just used as a capture engine, same as DPDK, WinDivert, AF_XDP, etc. That's why handling files shouldn't depend on libpcap

@seladb seladb requested a review from Dimi1010 January 12, 2026 03:47
@seladb seladb mentioned this pull request Jan 14, 2026
Copy link
Collaborator

@Dimi1010 Dimi1010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@seladb seladb merged commit 44e9085 into dev Jan 16, 2026
41 checks passed
@seladb seladb deleted the read-write-pcap branch January 16, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants