Skip to content

Commit

Permalink
implement poll() for startCaptureBlockingMode (#1245)
Browse files Browse the repository at this point in the history
  • Loading branch information
tigercosmos authored Dec 13, 2023
1 parent 4e848ce commit 660843e
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 97 deletions.
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.
* 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
100 changes: 77 additions & 23 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,58 +518,94 @@ 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;

bool pcapDispatchError = 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 shouldReturnError = false;

if (timeout <= 0)
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, reinterpret_cast<uint8_t*>(this)) == -1)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
pcapDispatchError = true;
shouldReturnError = 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;
#if !defined(_WIN32)
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

if(ready > 0)
{
if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast<uint8_t*>(this)) == -1)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
shouldReturnError = true;
m_StopThread = true;
}
}
else if(ready < 0)
{
PCPP_LOG_ERROR("poll() got error '" << strerror(errno) << "'");
shouldReturnError = true;
m_StopThread = true;
}
#else
PCPP_LOG_ERROR("Windows doesn't support poll()");
shouldReturnError = true;
m_StopThread = true;
#endif
}
clockGetTime(curTimeSec, curTimeNSec);
else
{
if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast<uint8_t*>(this)) == -1)
{
PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor));
shouldReturnError = true;
m_StopThread = true;
}
}
currentTime = std::chrono::steady_clock::now();
}
}

m_CaptureThreadStarted = false;

m_StopThread = false;

m_cbOnPacketArrivesBlockingMode = nullptr;
m_cbOnPacketArrivesBlockingModeUserCookie = nullptr;

if (pcapDispatchError)
if (shouldReturnError)
{
return 0;
}

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

Expand Down
Loading

0 comments on commit 660843e

Please sign in to comment.