Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement poll() for startCaptureBlockingMode #1245

Merged
merged 69 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
e383a0d
wip
tigercosmos Nov 20, 2023
2d51095
fix tidy
tigercosmos Nov 21, 2023
bf88d15
CI
tigercosmos Nov 21, 2023
207f362
fix build
tigercosmos Nov 21, 2023
1ab0abd
doc
tigercosmos Nov 21, 2023
1568c86
improve poll timeout
tigercosmos Nov 22, 2023
b5e5f60
test timeout
tigercosmos Nov 22, 2023
094479d
try to fix test
tigercosmos Nov 22, 2023
06136dc
fix the behavior
tigercosmos Nov 22, 2023
d7e4630
save some lines
tigercosmos Nov 22, 2023
1c98e4d
fix tidy
tigercosmos Nov 22, 2023
358b568
should fix the behavior
tigercosmos Nov 22, 2023
6480fac
pcap with poll
tigercosmos Nov 24, 2023
7c08824
Merge branch 'dev' into blockingCall
seladb Nov 25, 2023
1d6f804
fix cross platform build
tigercosmos Nov 21, 2023
f76d432
Merge branch 'dev' into blockingCall
tigercosmos Nov 27, 2023
367ba95
try to fix error
tigercosmos Nov 27, 2023
6feecda
fix
tigercosmos Nov 27, 2023
c219834
fix ci
tigercosmos Nov 27, 2023
62ee7e3
review
tigercosmos Nov 28, 2023
017d0e0
fix
tigercosmos Nov 28, 2023
9527b25
Merge branch 'dev' into blockingCall
tigercosmos Nov 30, 2023
b51e39b
use IP to find interface name
tigercosmos Nov 29, 2023
cfcba7a
fix
tigercosmos Nov 29, 2023
4975409
fix
tigercosmos Nov 29, 2023
253e2d2
fix
tigercosmos Nov 29, 2023
8415039
fix
tigercosmos Nov 29, 2023
3631e5a
fix
tigercosmos Nov 29, 2023
bec74df
Trigger CI
tigercosmos Nov 29, 2023
5119be8
remove necessary function.
tigercosmos Dec 3, 2023
0fa40e0
check system return value
tigercosmos Dec 3, 2023
1ea4902
change the boundary
tigercosmos Dec 3, 2023
be2a6dc
// cppcheck-suppress exceptThrowInDestructor
tigercosmos Dec 3, 2023
b189b7e
fix build
tigercosmos Dec 3, 2023
214fe06
check pcap_dispatch return value
tigercosmos Dec 3, 2023
8125da5
do not change the behaviour
tigercosmos Dec 3, 2023
db98aaa
install iptables
tigercosmos Dec 4, 2023
531c67a
try to fix apt
tigercosmos Dec 4, 2023
c9011a3
sudo not found
tigercosmos Dec 4, 2023
f56f762
test ubuntu first
tigercosmos Dec 4, 2023
9a27df0
add sudo back
tigercosmos Dec 4, 2023
618794c
try
tigercosmos Dec 4, 2023
81b396c
no sudo found
tigercosmos Dec 4, 2023
e94e0ea
add sudo permission
tigercosmos Dec 4, 2023
adc8b9f
use PTF_ASSERT_GREATER_OR_EQUAL_THAN
tigercosmos Dec 5, 2023
b51d4b5
add TestPcapLiveDeviceBlockingModePollNotTimeout
tigercosmos Dec 5, 2023
f082b5e
rename test name
tigercosmos Dec 5, 2023
1a50a03
fix the logic of joining thread
tigercosmos Dec 5, 2023
8622d8d
Merge remote-tracking branch 'upstream/dev' into blockingCall
tigercosmos Dec 5, 2023
2cb4dcf
add some comments
tigercosmos Dec 6, 2023
25c150d
add some comments
tigercosmos Dec 6, 2023
c8c12d1
fix tab
tigercosmos Dec 6, 2023
0992e5c
use std::system directly
tigercosmos Dec 6, 2023
5ca1cb9
fix comment
tigercosmos Dec 6, 2023
844ec8b
update
tigercosmos Dec 6, 2023
cdcc9c2
Merge remote-tracking branch 'upstream/dev' into blockingCall
tigercosmos Dec 6, 2023
0b68421
add iptables
tigercosmos Dec 6, 2023
7120404
add missing PTF_RUN_TEST
tigercosmos Dec 6, 2023
f80953a
no need sudo
tigercosmos Dec 4, 2023
a280f25
Merge branch 'dev' into blockingCall
seladb Dec 7, 2023
430cd6f
revert
tigercosmos Dec 8, 2023
7671f77
add MANUAL_TEST
tigercosmos Dec 8, 2023
856f784
revert
tigercosmos Dec 8, 2023
5f4b9dc
fix
tigercosmos Dec 8, 2023
cd4f6e6
fix
tigercosmos Dec 8, 2023
5938c68
fix
tigercosmos Dec 8, 2023
620d015
review.
tigercosmos Dec 13, 2023
0a074d1
remove unused include
tigercosmos Dec 13, 2023
497888a
update
tigercosmos Dec 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions Pcap++/header/PcapLiveDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ namespace pcpp
// that occurs in libpcap on Linux (on Windows using WinPcap/Npcap it works well):
// It's impossible to capture packets sent by the same descriptor
pcap_t* m_PcapSendDescriptor;
int m_PcapSelectableFd;
std::string m_Name;
std::string m_Description;
bool m_IsLoopback;
Expand All @@ -109,6 +110,7 @@ namespace pcpp
RawPacketVector* m_CapturedPackets;
bool m_CaptureCallbackMode;
LinkLayerType m_LinkType;
bool m_UsePoll;

// c'tor is not public, there should be only one for every interface (created by PcapLiveDeviceList)
PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway);
Expand Down Expand Up @@ -217,6 +219,11 @@ namespace pcpp
*/
unsigned int nflogGroup;


/// In Unix-like system, use poll() for blocking mode.
bool usePoll;


/**
* A c'tor for this struct
* @param[in] mode The mode to open the device: promiscuous or non-promiscuous. Default value is promiscuous
Expand All @@ -231,16 +238,18 @@ namespace pcpp
* captured with USBPcap (> 131072, < 262144). A snapshot length of 65535 should be sufficient, on most if not all networks,
* to capture all the data available from the packet.
* @param[in] nflogGroup NFLOG group for NFLOG devices. Default value is 0.
* @param[in] usePoll use `poll()` when capturing packets in blocking more (`startCaptureBlockingMode()`) on Unix-like system. Default value is false.
*/
explicit DeviceConfiguration(DeviceMode mode = Promiscuous, int packetBufferTimeoutMs = 0, int packetBufferSize = 0,
PcapDirection direction = PCPP_INOUT, int snapshotLength = 0, unsigned int nflogGroup = 0)
PcapDirection direction = PCPP_INOUT, int snapshotLength = 0, unsigned int nflogGroup = 0, bool usePoll = false)
{
this->mode = mode;
this->packetBufferTimeoutMs = packetBufferTimeoutMs;
this->packetBufferSize = packetBufferSize;
this->direction = direction;
this->snapshotLength = snapshotLength;
this->nflogGroup = nflogGroup;
this->usePoll = usePoll;
}
};

Expand Down Expand Up @@ -402,13 +411,14 @@ namespace pcpp
* @param[in] userCookie A pointer to a user provided object. This object will be transferred to the onPacketArrives callback
* each time it is called. This cookie is very useful for transferring objects that give context to the capture callback, for example:
* objects that counts packets, manages flow state or manages the application state according to the packet that was captured
* @param[in] timeout A timeout in seconds for the blocking to stop even if the user didn't return "true" in the onPacketArrives callback
* If this timeout is set to 0 or less the timeout will be ignored, meaning the method will keep blocking until the user frees it via
* the onPacketArrives callback
* @param[in] timeout A timeout in seconds for the blocking to stop even if the user didn't return "true" in the onPacketArrives callback.
* The precision of `timeout` is millisecond, e.g. 2.345 seconds means 2345 milliseconds.
tigercosmos marked this conversation as resolved.
Show resolved Hide resolved
* If this timeout is set to 0 or less the timeout will be ignored, meaning the method will keep handling packets until the `onPacketArrives`
* callback returns `true`.
* @return -1 if timeout expired, 1 if blocking was stopped via onPacketArrives callback or 0 if an error occurred (such as device
* not open etc.). When returning 0 an appropriate error message is printed to log
*/
virtual int startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, int timeout);
virtual int startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, const double timeout);

/**
* Stop a currently running packet capture. This method terminates gracefully both packet capture thread and periodic stats collection
Expand Down
98 changes: 77 additions & 21 deletions Pcap++/src/PcapLiveDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <string.h>
#include <iostream>
#include <fstream>
#include <chrono>
#include <sstream>
#if defined(_WIN32)
// The definition of BPF_MAJOR_VERSION is required to support Npcap. In Npcap there are
Expand All @@ -30,6 +31,8 @@
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <poll.h>
#include <pcap/pcap.h>
#endif // if defined(_WIN32)
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <net/if_dl.h>
Expand Down Expand Up @@ -64,8 +67,8 @@ static pcap_direction_t directionTypeMap(PcapLiveDevice::PcapDirection direction



PcapLiveDevice::PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway) : IPcapDevice(),
m_MacAddress(""), m_DefaultGateway(IPv4Address::Zero)
PcapLiveDevice::PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway) :
IPcapDevice(), m_PcapSelectableFd(-1), m_MacAddress(""), m_DefaultGateway(IPv4Address::Zero), m_UsePoll(false)
{
m_DeviceMtu = 0;
m_LinkType = LINKTYPE_ETHERNET;
Expand Down Expand Up @@ -346,6 +349,21 @@ bool PcapLiveDevice::open(const DeviceConfiguration& config)

m_DeviceOpened = true;

if(!config.usePoll)
{
m_UsePoll = false;
m_PcapSelectableFd = -1;
}
else
{
#if !defined(_WIN32)
m_UsePoll = true;
m_PcapSelectableFd = pcap_get_selectable_fd(m_PcapSendDescriptor);
#else
PCPP_LOG_ERROR("Windows doesn't support poll(), ignoring the `usePoll` parameter");
#endif
}

return true;
}

Expand Down Expand Up @@ -478,7 +496,7 @@ bool PcapLiveDevice::startCapture(RawPacketVector& capturedPacketsVector)
}


int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, int timeout)
int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, const double timeout)
{
if (!m_DeviceOpened || m_PcapDescriptor == nullptr)
{
Expand All @@ -500,48 +518,84 @@ int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacke
m_cbOnPacketArrivesBlockingMode = onPacketArrives;
m_cbOnPacketArrivesBlockingModeUserCookie = userCookie;

long startTimeSec = 0, startTimeNSec = 0;
clockGetTime(startTimeSec, startTimeNSec);

long curTimeSec = 0;

m_CaptureThreadStarted = true;
m_StopThread = false;

const int64_t timeoutMs = timeout * 1000; // timeout unit is seconds, let's change it to milliseconds
auto startTime = std::chrono::steady_clock::now();
auto currentTime = startTime;

#if !defined(_WIN32)
struct pollfd pcapPollFd;
memset(&pcapPollFd, 0, sizeof(pcapPollFd));
pcapPollFd.fd = m_PcapSelectableFd;
pcapPollFd.events = POLLIN;
#endif

bool pcapDispatchError = false;

if (timeout <= 0)
seladb marked this conversation as resolved.
Show resolved Hide resolved
if(timeoutMs <= 0)
{
while (!m_StopThread)
{
if (pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast<uint8_t*>(this)) == -1)
if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, (uint8_t*)this) < 0)
Copy link
Owner

Choose a reason for hiding this comment

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

An error in pcap_dispatch is -1 and not other values:

https://linux.die.net/man/3/pcap_dispatch

It returns -1 if an error occurs or -2 if the loop terminated due to a call to pcap_breakloop() before any packets were processed. If your application uses pcap_breakloop(), make sure that you explicitly check for -1 and -2, rather than just checking for a return value < 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sorry, fixed.

{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
pcapDispatchError = true;
m_StopThread = true;
}
}
curTimeSec = startTimeSec + timeout;
}
else
{
while (!m_StopThread && curTimeSec <= (startTimeSec + timeout))
while (!m_StopThread && std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count() < timeoutMs )
{
long curTimeNSec = 0;
if (pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast<uint8_t*>(this)) == -1)
if(m_UsePoll)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
pcapDispatchError = true;
m_StopThread = true;
#if !defined(_WIN32)
// Be careful when you modify the code related to `poll` in this block
// Some tests are not supported to run on CIs: `TestPcapLiveDeviceBlockingModePollTimeout`, `TestPcapLiveDeviceBlockingModeNotTimeoutWithoutPoll`
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@seladb I added some comments here to mention that manual tests are required here

// Please turn on `MANUAL_TEST` flag and at least ensure that it works on your machine

int64_t pollTimeoutMs = timeoutMs - std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
pollTimeoutMs = std::max(pollTimeoutMs, (int64_t)0); // poll will be in blocking mode if negative value

int ready = poll(&pcapPollFd, 1, pollTimeoutMs); // wait the packets until timeout
seladb marked this conversation as resolved.
Show resolved Hide resolved

if(ready > 0)
{
if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, (uint8_t*)this) < 0)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
pcapDispatchError = true;
m_StopThread = true;
}
}
else if(ready < 0)
{
PCPP_LOG_ERROR("poll() got error '" << strerror(errno) << "'");
return -1;
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think we can return 0 directly because in lines 597-600 we reset a few private members.
Instead we should set m_StopThread = true and maybe add a new local variable pollError and I think we can return 0 in this case (poll error)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

}
#else
PCPP_LOG_ERROR("Windows doesn't support poll()");
return 0;
Copy link
Owner

Choose a reason for hiding this comment

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

ditto: we cannot return 0 here. We should probably add a new local variable pollError and set it to true

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

#endif
tigercosmos marked this conversation as resolved.
Show resolved Hide resolved
}
clockGetTime(curTimeSec, curTimeNSec);
else
{
if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, (uint8_t*)this) < 0)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
pcapDispatchError = true;
m_StopThread = true;
}
}
currentTime = std::chrono::steady_clock::now();
}
}

m_CaptureThreadStarted = false;

m_StopThread = false;

m_cbOnPacketArrivesBlockingMode = nullptr;
m_cbOnPacketArrivesBlockingModeUserCookie = nullptr;

Expand All @@ -550,8 +604,10 @@ int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacke
return 0;
}

if (curTimeSec > (startTimeSec + timeout))
if (std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count() >= timeoutMs )
{
return -1;
}
return 1;
}

Expand Down
28 changes: 28 additions & 0 deletions Tests/Pcap++Test/Common/TestUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ class DeviceTeardown
}
};

class SystemCommandTeardown
{
private:
std::string m_command;
bool m_CancelTeardown;

public:

explicit SystemCommandTeardown(const std::string& command) : m_command(command) , m_CancelTeardown(false) {}

~SystemCommandTeardown() noexcept(false)
{
if (!m_CancelTeardown)
{
if(std::system(m_command.c_str()) < 0)
{
// cppcheck-suppress exceptThrowInDestructor
throw std::runtime_error("failed to run: " + m_command);
}
}
}

void cancelTeardown()
{
m_CancelTeardown = true;
}
};

bool sendURLRequest(const std::string &url);

bool readPcapIntoPacketVec(const std::string& pcapFileName, std::vector<pcpp::RawPacket>& packetStream, std::string& errMsg);
Expand Down
2 changes: 2 additions & 0 deletions Tests/Pcap++Test/TestDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ PTF_TEST_CASE(TestPcapLiveDeviceClone);
PTF_TEST_CASE(TestPcapLiveDeviceNoNetworking);
PTF_TEST_CASE(TestPcapLiveDeviceStatsMode);
PTF_TEST_CASE(TestPcapLiveDeviceBlockingMode);
PTF_TEST_CASE(TestPcapLiveDeviceBlockingModePollTimeout);
seladb marked this conversation as resolved.
Show resolved Hide resolved
PTF_TEST_CASE(TestPcapLiveDeviceBlockingModeNotTimeoutWithoutPoll);
PTF_TEST_CASE(TestPcapLiveDeviceSpecialCfg);
PTF_TEST_CASE(TestWinPcapLiveDevice);
PTF_TEST_CASE(TestSendPacket);
Expand Down
Loading
Loading