Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
72 changes: 57 additions & 15 deletions Pcap++/header/PcapFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,31 +78,35 @@ namespace pcpp
/// A wrapper class for BPF filtering. Enables setting a BPF filter and matching it against a packet
class BpfFilterWrapper
{
private:
std::string m_FilterStr;
LinkLayerType m_LinkType;
std::unique_ptr<bpf_program, internal::BpfProgramDeleter> m_Program;
public:
/// @brief An enum with possible behaviours in case of link type mismatch
enum class LinkMismatchBehaviour
{
/// @brief Attempt to recompile the filter with the new link type
RecompileFilter,
/// @brief Do not attempt to recompile the filter, just return false.
NoMatch
};

void freeProgram();
/// @brief Creates a new instance with no filter.
BpfFilterWrapper() = default;

public:
/// A c'tor for this class
BpfFilterWrapper();
/// @brief Creates a new instance with the given filter string.
/// @param filter A filter in BPF syntax
/// @param linkType An optional parameter to set the filter's link type. The default is LINKTYPE_ETHERNET
/// @throws std::runtime_error if the filter could not be compiled
explicit BpfFilterWrapper(std::string filter, LinkLayerType linkType = LINKTYPE_ETHERNET);

/// A copy constructor for this class.
/// @param[in] other The instance to copy from
BpfFilterWrapper(const BpfFilterWrapper& other);

/// A copy assignment operator for this class.
/// @param[in] other An instance of IPNetwork to assign
/// @return A reference to the assignee
BpfFilterWrapper(BpfFilterWrapper&&) noexcept = default;
BpfFilterWrapper& operator=(const BpfFilterWrapper& other);
BpfFilterWrapper& operator=(BpfFilterWrapper&&) noexcept = default;

/// Set a filter. This method receives a filter in BPF syntax (https://biot.com/capstats/bpf.html) and an
/// optional link type, compiles them, and if compilation is successful it stores the filter.
/// @param[in] filter A filter in BPF syntax
/// @param[in] linkType An optional parameter to set the filter's link type. The default is LINKTYPE_ETHERNET
/// @return True if compilation is successful and filter is stored in side this object, false otherwise
/// @return True if compilation is successful and filter is stored inside this object, false otherwise
bool setFilter(const std::string& filter, LinkLayerType linkType = LINKTYPE_ETHERNET);

/// Match a packet with the filter stored in this object. If the filter is empty the method returns "true".
Expand All @@ -124,6 +128,44 @@ namespace pcpp
/// could not be compiled
bool matchPacketWithFilter(const uint8_t* packetData, uint32_t packetDataLength, timespec packetTimestamp,
uint16_t linkType);

/// @brief Match a packet with the filter stored in this object.
///
/// If the filter is empty the method returns "true".
/// If the link type of the raw packet is different than the one set in setFilter():
/// - If onLinkmismatch is set to RecompileFilter, the filter will be re-compiled and stored in the object.
/// - If onLinkmismatch is set to NoMatch, the method will return "false"
///
/// @param[in] rawPacket The raw packet to match the filter against
/// @param[in] onLinkmismatch The behaviour in case of link type mismatch
/// @return True if the filter matches (or if it's empty). False otherwise
bool matches(const RawPacket& rawPacket,
LinkMismatchBehaviour onLinkmismatch = LinkMismatchBehaviour::RecompileFilter) const;

/// @brief Match a raw buffer of packet data against the filter stored in this object.
///
/// If the filter is empty the method returns "true".
/// If the link type provided is different than the one set in setFilter():
/// - If onLinkmismatch is set to RecompileFilter, the filter will be re-compiled and stored in the object.
/// - If onLinkmismatch is set to NoMatch, the method will return "false"
///
/// @param[in] packetData A pointer to the raw packet data
/// @param[in] packetDataLength The length of the raw packet data in bytes
/// @param[in] timestamp Timestamp to be associated with the packet
/// @param[in] linkType The link type of the packet
/// @param[in] onLinkmismatch The behaviour in case of link type mismatch
/// @return True if the filter matches (or if it's empty). False otherwise
bool matches(const uint8_t* packetData, uint32_t packetDataLength, timespec timestamp, uint16_t linkType,
LinkMismatchBehaviour onLinkmismatch = LinkMismatchBehaviour::RecompileFilter) const;

private:
using BpfProgramUPtr = std::unique_ptr<bpf_program, internal::BpfProgramDeleter>;

std::string m_FilterStr;
mutable LinkLayerType m_CachedProgramLinkType = LinkLayerType::LINKTYPE_ETHERNET;
mutable BpfProgramUPtr m_CachedProgram;

static BpfProgramUPtr compileFilter(std::string const& filter, LinkLayerType linkType);
};

/// @class GeneralFilter
Expand Down
127 changes: 93 additions & 34 deletions Pcap++/src/PcapFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,82 +38,141 @@ namespace pcpp
}
} // namespace internal

BpfFilterWrapper::BpfFilterWrapper() : m_LinkType(LinkLayerType::LINKTYPE_ETHERNET)
{}

BpfFilterWrapper::BpfFilterWrapper(const BpfFilterWrapper& other) : BpfFilterWrapper()
BpfFilterWrapper::BpfFilterWrapper(std::string filter, LinkLayerType linkType)
: m_FilterStr(std::move(filter)), m_CachedProgramLinkType(linkType),
m_CachedProgram(compileFilter(m_FilterStr, linkType))
{
setFilter(other.m_FilterStr, other.m_LinkType);
if (!m_FilterStr.empty() && m_CachedProgram == nullptr)
{
throw std::runtime_error("Couldn't compile BPF filter: '" + m_FilterStr + "'");
}
}

BpfFilterWrapper::BpfFilterWrapper(const BpfFilterWrapper& other)
: BpfFilterWrapper(other.m_FilterStr, other.m_CachedProgramLinkType)
{}

BpfFilterWrapper& BpfFilterWrapper::operator=(const BpfFilterWrapper& other)
{
setFilter(other.m_FilterStr, other.m_LinkType);
setFilter(other.m_FilterStr, other.m_CachedProgramLinkType);
return *this;
}

bool BpfFilterWrapper::setFilter(const std::string& filter, LinkLayerType linkType)
{
if (filter.empty())
{
freeProgram();
m_CachedProgram = nullptr;
m_FilterStr.clear();
return true;
}

if (filter != m_FilterStr || linkType != m_LinkType)
if (filter != m_FilterStr || linkType != m_CachedProgramLinkType)
{
auto pcap = std::unique_ptr<pcap_t, internal::PcapCloseDeleter>(pcap_open_dead(linkType, DEFAULT_SNAPLEN));
if (pcap == nullptr)
auto newProgram = compileFilter(filter, linkType);
if (newProgram == nullptr)
{
PCPP_LOG_ERROR("Couldn't compile BPF filter: '" << filter << "'");
return false;
}

auto newProg = std::make_unique<bpf_program>();
int ret = pcap_compile(pcap.get(), newProg.get(), filter.c_str(), 1, 0);
if (ret < 0)
{
return false;
}

// Reassigns ownership of the bpf program to a new unique_ptr with a custom deleter as it now requires
// specialized cleanup.
m_Program = std::unique_ptr<bpf_program, internal::BpfProgramDeleter>(newProg.release());
m_FilterStr = filter;
m_LinkType = linkType;
m_CachedProgram = std::move(newProgram);
m_CachedProgramLinkType = linkType;
}

return true;
}

void BpfFilterWrapper::freeProgram()
{
m_Program = nullptr;
m_FilterStr.clear();
}

bool BpfFilterWrapper::matchPacketWithFilter(const RawPacket* rawPacket)
{
return matchPacketWithFilter(rawPacket->getRawData(), rawPacket->getRawDataLen(),
rawPacket->getPacketTimeStamp(), rawPacket->getLinkLayerType());
if (rawPacket == nullptr)
{
PCPP_LOG_ERROR("Raw packet pointer is null");
return false;
}

return matches(*rawPacket);
}

bool BpfFilterWrapper::matchPacketWithFilter(const uint8_t* packetData, uint32_t packetDataLength,
timespec packetTimestamp, uint16_t linkType)
{
return matches(packetData, packetDataLength, packetTimestamp, linkType);
}

bool BpfFilterWrapper::matches(const RawPacket& rawPacket, LinkMismatchBehaviour onLinkmismatch) const
{
return matches(rawPacket.getRawData(), rawPacket.getRawDataLen(), rawPacket.getPacketTimeStamp(),
rawPacket.getLinkLayerType(), onLinkmismatch);
}

bool BpfFilterWrapper::matches(const uint8_t* packetData, uint32_t packetDataLength, timespec timestamp,
uint16_t linkType, LinkMismatchBehaviour onLinkmismatch) const
{
if (m_FilterStr.empty())
return true;

if (!setFilter(std::string(m_FilterStr), static_cast<LinkLayerType>(linkType)))
// This should never happen, but just in case
if (m_CachedProgram == nullptr)
{
return false;
throw std::runtime_error("No compiled BPF program available");
}

// Handle link type mismatch
if (linkType != static_cast<uint16_t>(m_CachedProgramLinkType))
{
switch (onLinkmismatch)
{
case LinkMismatchBehaviour::NoMatch:
{
return false; // Do not attempt to recompile, just return false
}
case LinkMismatchBehaviour::RecompileFilter:
{
auto newProgram = compileFilter(m_FilterStr, static_cast<LinkLayerType>(linkType));
if (newProgram == nullptr)
{
PCPP_LOG_ERROR("Couldn't compile BPF filter: '" << m_FilterStr << "' for link type: " << linkType);
return false;
}
m_CachedProgram = std::move(newProgram);
m_CachedProgramLinkType = static_cast<LinkLayerType>(linkType);
break;
}
default:
throw std::logic_error("Unknown LinkmismatchBehaviour");
}
}

struct pcap_pkthdr pktHdr;
// Test the packet against the filter
pcap_pkthdr pktHdr;
pktHdr.caplen = packetDataLength;
pktHdr.len = packetDataLength;
pktHdr.ts = internal::toTimeval(packetTimestamp);
pktHdr.ts = internal::toTimeval(timestamp);
return (pcap_offline_filter(m_CachedProgram.get(), &pktHdr, packetData) != 0);
}

BpfFilterWrapper::BpfProgramUPtr BpfFilterWrapper::compileFilter(std::string const& filter, LinkLayerType linkType)
{
if (filter.empty())
return nullptr;

auto pcap = std::unique_ptr<pcap_t, internal::PcapCloseDeleter>(pcap_open_dead(linkType, DEFAULT_SNAPLEN));
if (pcap == nullptr)
{
return nullptr;
}

auto newProg = std::make_unique<bpf_program>();
int ret = pcap_compile(pcap.get(), newProg.get(), filter.c_str(), 1, 0);
if (ret < 0)
{
return nullptr;
}

return (pcap_offline_filter(m_Program.get(), &pktHdr, packetData) != 0);
// Reassigns ownership to a new unique_ptr with a custom deleter as it now requires specialized cleanup.
return BpfProgramUPtr(newProg.release());
}

void BPFStringFilter::parseToString(std::string& result)
Expand Down
Loading