diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 81f4603f48..91c26ee0e0 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -532,3 +532,39 @@ jobs: sed -i.bak "s|abiFilters.*$|abiFilters '${{ matrix.target }}'|g" app/build.gradle chmod +x gradlew ./gradlew assembleDebug + + xdp: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt update && sudo apt -y install libpcap-dev libbpf-dev tcpreplay + + - name: Configure PcapPlusPlus + run: cmake -DPCAPPP_USE_XDP=ON -S . -B $BUILD_DIR + + - name: Build PcapPlusPlus + run: cmake --build $BUILD_DIR -j + + - name: Test PcapPlusPlus + run: | + python -m pip install -U pip + python -m pip install -r ci/run_tests/requirements.txt + python ci/run_tests/run_tests.py --interface eth0 --use-sudo --pcap-test-args="-t xdp" + + - name: Create Cobertura Report + run: | + python -m pip install gcovr + gcovr -v -r . $GCOVR_FLAGS -o coverage.xml + + - name: Upload Coverage Results + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + flags: xdp,unittest + fail_ci_if_error: false + verbose: true diff --git a/CMakeLists.txt b/CMakeLists.txt index b989c33a06..d2251c622c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ cmake_dependent_option( "PCAPPP_USE_DPDK" OFF) option(PCAPPP_USE_PF_RING "Setup PcapPlusPlus with PF_RING. In this case you must also set PF_RING_ROOT") +option(PCAPPP_USE_XDP "Setup PcapPlusPlus with XDP") option(PCAPPP_INSTALL "Install Pcap++" ${PCAPPP_MAIN_PROJECT}) option(PCAPPP_PACKAGE "Package Pcap++ could require a recent version of CMake" OFF) @@ -213,6 +214,14 @@ if(PCAPPP_USE_PF_RING) add_definitions(-DUSE_PF_RING) endif() +if(PCAPPP_USE_XDP) + find_package(BPF) + if(NOT BPF_FOUND) + message(FATAL_ERROR "libbpf not found!") + endif() + add_definitions(-DUSE_XDP) +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" diff --git a/Common++/header/Logger.h b/Common++/header/Logger.h index 425a55d092..ced696a42d 100644 --- a/Common++/header/Logger.h +++ b/Common++/header/Logger.h @@ -81,6 +81,7 @@ namespace pcpp PcapLogModuleMBufRawPacket, ///< MBufRawPacket module (Pcap++) PcapLogModuleDpdkDevice, ///< DpdkDevice module (Pcap++) PcapLogModuleKniDevice, ///< KniDevice module (Pcap++) + PcapLogModuleXdpDevice, ///< XdpDevice module (Pcap++) NetworkUtils, ///< NetworkUtils module (Pcap++) NumOfLogModules }; diff --git a/Packet++/header/RawPacket.h b/Packet++/header/RawPacket.h index 4e273b8c66..7df25657e2 100644 --- a/Packet++/header/RawPacket.h +++ b/Packet++/header/RawPacket.h @@ -348,6 +348,18 @@ namespace pcpp */ virtual bool setRawData(const uint8_t* pRawData, int rawDataLen, timespec timestamp, LinkLayerType layerType = LINKTYPE_ETHERNET, int frameLength = -1); + /** + * Initialize a raw packet with data. The main difference between this method and setRawData() is that setRawData() + * is meant for replacing the data in an existing raw packet, whereas this method is meant to be used right after + * constructing a raw packet using the default c'tor, before setting any data + * @param pRawData A pointer to the new raw data + * @param rawDataLen The new raw data length in bytes + * @param timestamp The timestamp packet was received by the NIC (in nsec precision) + * @param layerType The link layer type for this raw data + * @return True if raw data was set successfully, false otherwise + */ + bool initWithRawData(const uint8_t* pRawData, int rawDataLen, timespec timestamp, LinkLayerType layerType = LINKTYPE_ETHERNET); + /** * Get raw data pointer * @return A read-only pointer to the raw data diff --git a/Packet++/src/RawPacket.cpp b/Packet++/src/RawPacket.cpp index 7e086a4a48..8ad52d520f 100644 --- a/Packet++/src/RawPacket.cpp +++ b/Packet++/src/RawPacket.cpp @@ -112,6 +112,12 @@ bool RawPacket::setRawData(const uint8_t* pRawData, int rawDataLen, timespec tim return true; } +bool RawPacket::initWithRawData(const uint8_t* pRawData, int rawDataLen, timespec timestamp, LinkLayerType layerType) +{ + init(false); + return setRawData(pRawData, rawDataLen, timestamp, layerType); +} + void RawPacket::clear() { if (m_RawData != nullptr) diff --git a/Pcap++/CMakeLists.txt b/Pcap++/CMakeLists.txt index 34c4d93254..a23efa7e17 100644 --- a/Pcap++/CMakeLists.txt +++ b/Pcap++/CMakeLists.txt @@ -16,6 +16,7 @@ add_library( $<$:src/PcapRemoteDeviceList.cpp> $<$:src/PfRingDevice.cpp> $<$:src/PfRingDeviceList.cpp> + $<$:src/XdpDevice.cpp> src/RawSocketDevice.cpp $<$:src/WinPcapLiveDevice.cpp> # Force light pcapng to be link fully static @@ -56,6 +57,10 @@ if(PCAPPP_USE_PF_RING) header/PfRingDeviceList.h) endif() +if(PCAPPP_USE_XDP) + list(APPEND public_headers header/XdpDevice.h) +endif() + if(LINUX) list(APPEND public_headers header/LinuxNicInformationSocket.h) endif() @@ -93,6 +98,7 @@ target_link_libraries( Packet++ $<$:PF_RING::PF_RING> $<$:DPDK::DPDK> + $<$:BPF::BPF> PCAP::PCAP Threads::Threads) diff --git a/Pcap++/header/XdpDevice.h b/Pcap++/header/XdpDevice.h new file mode 100644 index 0000000000..f3ab70c51e --- /dev/null +++ b/Pcap++/header/XdpDevice.h @@ -0,0 +1,341 @@ +#ifndef PCAPPP_XDP_DEVICE +#define PCAPPP_XDP_DEVICE + +/// @file + +#include "Device.h" +#include +#include + +/** +* \namespace pcpp +* \brief The main namespace for the PcapPlusPlus lib + */ +namespace pcpp +{ + /** + * @class XdpDevice + * A class wrapping the main functionality of using AF_XDP (XSK) sockets + * which are optimized for high performance packet processing. + * + * It provides methods for configuring and initializing an AF_XDP socket, and then send and receive packets through it. + * It also provides a method for gathering statistics from the socket. + */ + class XdpDevice : public IDevice + { + public: + + /** + * @typedef OnPacketsArrive + * The callback that is called whenever packets are received on the socket + * @param[in] packets An array of the raw packets received + * @param[in] packetCount The number of packets received + * @param[in] device The XdpDevice packets are received from (represents the AF_XDP socket) + * @param[in] userCookie A pointer to an object set by the user when receivePackets() started + */ + typedef void (*OnPacketsArrive)(RawPacket packets[], uint32_t packetCount, XdpDevice* device, void* userCookie); + + /** + * @struct XdpDeviceConfiguration + * A struct containing the configuration parameters available for opening an XDP device + */ + struct XdpDeviceConfiguration + { + /** + * @enum AttachMode + * AF_XDP operation mode + */ + enum AttachMode + { + /** A fallback mode that works for any network device. Use it if the network driver doesn't have support for XDP */ + SkbMode = 1, + /** Use this mode if the network driver has support for XDP */ + DriverMode = 2, + /** Automatically detect whether driver mode is supported, otherwise fallback to SKB mode */ + AutoMode = 3 + }; + + /** AF_XDP operation mode */ + AttachMode attachMode; + + /** + * UMEM is a region of virtual contiguous memory, divided into equal-sized frames. + * This parameter determines the number of frames that will be allocated as pert of the UMEM. + **/ + uint16_t umemNumFrames; + + /** + * UMEM is a region of virtual contiguous memory, divided into equal-sized frames. + * This parameter determines the frame size that will be allocated. + * NOTE: the frame size should be equal to the memory page size (use getpagesize() to determine this size) + **/ + uint16_t umemFrameSize; + + /** + * The size of the fill ring used by the AF_XDP socket. This size should be a power of two + * and less or equal to the total number of UMEM frames + */ + uint32_t fillRingSize; + + /** + * The size of the completion ring used by the AF_XDP socket. This size should be a power of two + * and less or equal to the total number of UMEM frames + */ + uint32_t completionRingSize; + + /** + * The size of the RX ring used by the AF_XDP socket. This size should be a power of two + * and less or equal to the total number of UMEM frames + */ + uint32_t rxSize; + + /** + * The size of the TX ring used by the AF_XDP socket. This size should be a power of two + * and less or equal to the total number of UMEM frames + */ + uint32_t txSize; + + /** + * The max number of packets to be received or sent in one batch + */ + uint16_t rxTxBatchSize; + + /** + * A c'tor for this struct. Each parameter has a default value described below. + * @param[in] attachMode AF_XDP operation mode. The default value is auto mode + * @param[in] umemNumFrames Number of UMEM frames to allocate. The default value is 4096 + * @param[in] umemFrameSize The size of each UMEM frame. The default value is equal to getpagesize() + * @param[in] fillRingSize The size of the fill ring used by the AF_XDP socket. The default value is 4096 + * @param[in] completionRingSize The size of the completion ring used by the AF_XDP socket. The default value is 2048 + * @param[in] rxSize The size of the RX ring used by the AF_XDP socket. The default value is 2048 + * @param[in] txSize The size of the TX ring used by the AF_XDP socket. The default value is 2048 + * @param[in] rxTxBatchSize The max number of packets to be received or sent in one batch. The default value is 64 + */ + explicit XdpDeviceConfiguration(AttachMode attachMode = AutoMode, + uint16_t umemNumFrames = 0, + uint16_t umemFrameSize = 0, + uint32_t fillRingSize = 0, + uint32_t completionRingSize = 0, + uint32_t rxSize = 0, + uint32_t txSize = 0, + uint16_t rxTxBatchSize = 0) + { + this->attachMode = attachMode; + this->umemNumFrames = umemNumFrames; + this->umemFrameSize = umemFrameSize; + this->fillRingSize = fillRingSize; + this->completionRingSize = completionRingSize; + this->rxSize = rxSize; + this->txSize = txSize; + this->rxTxBatchSize = rxTxBatchSize; + } + }; + + /** + * @struct XdpDeviceStats + * A container for XDP device statistics + */ + struct XdpDeviceStats + { + /** The timestamp when the stats were collected */ + timespec timestamp; + /** Number of packets received */ + uint64_t rxPackets; + /** Packets received per second. Measured from to the previous time stats were collected */ + uint64_t rxPacketsPerSec; + /** Number of bytes received */ + uint64_t rxBytes; + /** Bytes per second received. Measured from to the previous time stats were collected */ + uint64_t rxBytesPerSec; + /** Total number of dropped RX packets */ + uint64_t rxDroppedTotalPackets; + /** RX packets dropped due to invalid descriptor */ + uint64_t rxDroppedInvalidPackets; + /** RX packets dropped due to RX ring being full */ + uint64_t rxDroppedRxRingFullPackets; + /** Failed RX packets to retrieve item from fill ring */ + uint64_t rxDroppedFillRingPackets; + /** Number of poll() timeouts */ + uint64_t rxPollTimeout; + /** Number of packets sent from the application */ + uint64_t txSentPackets; + /** Packets sent from the app per second. Measured from to the previous time stats were collected */ + uint64_t txSentPacketsPerSec; + /** Number of bytes sent from the application */ + uint64_t txSentBytes; + /** Bytes per second sent from the app. Measured from to the previous time stats were collected */ + uint64_t txSentBytesPerSec; + /** Number of completed sent packets, meaning packets that were confirmed as sent by the kernel */ + uint64_t txCompletedPackets; + /** Completed sent packets per second. Measured from to the previous time stats were collected */ + uint64_t txCompletedPacketsPerSec; + /** TX packets dropped due to invalid descriptor */ + uint64_t txDroppedInvalidPackets; + /** Current RX ring ID */ + uint64_t rxRingId; + /** Current TX ring ID */ + uint64_t txRingId; + /** Current fill ring ID */ + uint64_t fqRingId; + /** Current completion ring ID */ + uint64_t cqRingId; + /** Number of UMEM frames that are currently in-use (allocated) */ + uint64_t umemAllocatedFrames; + /** Number of UMEM frames that are currently free (not allocated) */ + uint64_t umemFreeFrames; + }; + + /** + * A c'tor for this class. Please note that calling this c'tor doesn't initialize the AF_XDP socket. In order to + * set up the socket call open(). + * @param[in] interfaceName The interface name to open the AF_XDP socket on + */ + explicit XdpDevice(std::string interfaceName); + + /** + * A d'tor for this class. It closes the device if it's open. + */ + ~XdpDevice() override; + + /** + * Open the device with default configuration. Call getConfig() after opening the device to get the + * current configuration. + * This method initializes the UMEM, and then creates and configures the AF_XDP socket. If it succeeds the + * socket is ready to receive and send packets. + * @return True if device was opened successfully, false otherwise + */ + bool open() override; + + /** + * Open the device with custom configuration set by the user. + * This method initializes the UMEM, and then creates and configures the AF_XDP socket. If it succeeds the + * socket is ready to receive and send packets. + * @param[in] config The configuration to use for opening the device + * @return True if device was opened successfully, false otherwise + */ + bool open(const XdpDeviceConfiguration& config); + + /** + * Close the device. This method closes the AF_XDP socket and frees the UMEM that was allocated for it. + */ + void close() override; + + /** + * Start receiving packets. In order to use this method the device should be open. Note that this method is + * blocking and will return if: + * - stopReceivePackets() was called from within the user callback + * - timeoutMS passed without receiving any packets + * - Some error occurred (an error log will be printed) + * @param[in] onPacketsArrive A callback to be called when packets are received + * @param[in] onPacketsArriveUserCookie The callback is invoked with this cookie as a parameter. It can be used + * to pass information from the user application to the callback + * @param[in] timeoutMS Timeout in milliseconds to stop if no packets are received. The default value is 5000 ms + * @return True if stopped receiving packets because stopReceivePackets() was called or because timeoutMS passed, + * or false if an error occurred. + */ + bool receivePackets(OnPacketsArrive onPacketsArrive, void* onPacketsArriveUserCookie, int timeoutMS = 5000); + + /** + * Stop receiving packets. Call this method from within the callback passed to receivePackets() whenever you + * want to stop receiving packets. + */ + void stopReceivePackets(); + + /** + * Send a vector of packet pointers. + * @param[in] packets A vector of packet pointers to send + * @param[in] waitForTxCompletion Wait for confirmation from the kernel that packets were sent. If set to true + * this method will wait until the number of packets in the completion ring is equal or greater to the number + * of packets that were sent. The default value is false + * @param[in] waitForTxCompletionTimeoutMS If waitForTxCompletion is set to true, poll the completion ring with + * this timeout. The default value is 5000 ms + * @return True if all packets were sent, or if waitForTxCompletion is true - all sent packets were confirmed. + * Returns false if an error occurred or if poll timed out. + */ + bool sendPackets(const RawPacketVector& packets, bool waitForTxCompletion = false, int waitForTxCompletionTimeoutMS = 5000); + + /** + * Send an array of packets. + * @param[in] packets An array of raw packets to send + * @param[in] packetCount The length of the packet array + * @param[in] waitForTxCompletion Wait for confirmation from the kernel that packets were sent. If set to true + * this method will wait until the number of packets in the completion ring is equal or greater to the number + * of packets sent. The default value is false + * @param[in] waitForTxCompletionTimeoutMS If waitForTxCompletion is set to true, poll the completion ring with + * this timeout. The default value is 5000 ms + * @return True if all packets were sent, or if waitForTxCompletion is true - all sent packets were confirmed. + * Returns false if an error occurred or if poll timed out. + */ + bool sendPackets(RawPacket packets[], size_t packetCount, bool waitForTxCompletion = false, int waitForTxCompletionTimeoutMS = 5000); + + /** + * @return A pointer to the current device configuration. If the device is not open this method returns nullptr + */ + XdpDeviceConfiguration* getConfig() const { return m_Config; } + + /** + * @return Current device statistics + */ + XdpDeviceStats getStatistics(); + + private: + class XdpUmem + { + public: + explicit XdpUmem(uint16_t numFrames, uint16_t frameSize, uint32_t fillRingSize, uint32_t completionRingSize); + + virtual ~XdpUmem(); + + inline uint16_t getFrameSize() const { return m_FrameSize; } + inline uint16_t getFrameCount() const { return m_FrameCount; } + + std::pair> allocateFrames(uint32_t count); + + void freeFrame(uint64_t addr); + + const uint8_t* getDataPtr(uint64_t addr) const; + + void setData(uint64_t addr, const uint8_t* data, size_t dataLen); + + inline size_t getFreeFrameCount() { return m_FreeFrames.size(); } + + inline void* getInfo() { return m_UmemInfo; } + + private: + void* m_UmemInfo; + void* m_Buffer; + uint16_t m_FrameSize; + uint16_t m_FrameCount; + std::vector m_FreeFrames; + }; + + struct XdpPrevDeviceStats + { + timespec timestamp; + uint64_t rxPackets; + uint64_t rxBytes; + uint64_t txSentPackets; + uint64_t txSentBytes; + uint64_t txCompletedPackets; + }; + + std::string m_InterfaceName; + XdpDeviceConfiguration* m_Config; + bool m_ReceivingPackets; + XdpUmem* m_Umem; + void* m_SocketInfo; + XdpDeviceStats m_Stats; + XdpPrevDeviceStats m_PrevStats; + + bool sendPackets(const std::function& getPacketAt, const std::function& getPacketCount, bool waitForTxCompletion = false, int waitForTxCompletionTimeoutMS = 5000); + bool populateFillRing(uint32_t count, uint32_t rxId = 0); + bool populateFillRing(const std::vector& addresses, uint32_t rxId); + uint32_t checkCompletionRing(); + bool configureSocket(); + bool initUmem(); + bool initConfig(); + bool getSocketStats(); + }; +} + +#endif // PCAPPP_XDP_DEVICE diff --git a/Pcap++/src/XdpDevice.cpp b/Pcap++/src/XdpDevice.cpp new file mode 100644 index 0000000000..b585da3b31 --- /dev/null +++ b/Pcap++/src/XdpDevice.cpp @@ -0,0 +1,614 @@ +#define LOG_MODULE PcapLogModuleXdpDevice +#include "XdpDevice.h" +#include "GeneralUtils.h" +#include "Logger.h" +#include "Packet.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pcpp +{ + +struct xsk_umem_info { + struct xsk_ring_prod fq; + struct xsk_ring_cons cq; + struct xsk_umem *umem; +}; + +struct xsk_socket_info +{ + struct xsk_ring_cons rx; + struct xsk_ring_prod tx; + struct xsk_socket *xsk; +}; + +#define DEFAULT_UMEM_NUM_FRAMES (XSK_RING_PROD__DEFAULT_NUM_DESCS * 2) +#define DEFAULT_FILL_RING_SIZE (XSK_RING_PROD__DEFAULT_NUM_DESCS * 2) +#define DEFAULT_COMPLETION_RING_SIZE XSK_RING_PROD__DEFAULT_NUM_DESCS +#define DEFAULT_BATCH_SIZE 64 +#define IS_POWER_OF_TWO(num) (num && ((num & (num - 1)) == 0)) + + +XdpDevice::XdpUmem::XdpUmem(uint16_t numFrames, uint16_t frameSize, uint32_t fillRingSize, uint32_t completionRingSize) +{ + size_t bufferSize = numFrames * frameSize; + + if (posix_memalign(&m_Buffer, getpagesize(), bufferSize)) + { + throw std::runtime_error("Could not allocate buffer memory for UMEM"); + } + + struct xsk_umem_config cfg = { + .fill_size = fillRingSize, + .comp_size = completionRingSize, + .frame_size = frameSize, + .frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM, + .flags = 0 + }; + + struct xsk_umem_info* umem = new xsk_umem_info; + memset(umem, 0, sizeof(xsk_umem_info)); + + int ret = xsk_umem__create(&umem->umem, m_Buffer, bufferSize, &umem->fq, &umem->cq, &cfg); + if (ret) + { + throw std::runtime_error("Could not allocate UMEM - xsk_umem__create() returned " + std::to_string(ret)); + } + + m_UmemInfo = umem; + + for (uint16_t i = 0; i < numFrames; i++) + { + m_FreeFrames.push_back(i * frameSize); + } + + m_FrameSize = frameSize; + m_FrameCount = numFrames; +} + +XdpDevice::XdpUmem::~XdpUmem() +{ + xsk_umem__delete(static_cast(m_UmemInfo)->umem); + free(m_Buffer); +} + +const uint8_t* XdpDevice::XdpUmem::getDataPtr(uint64_t addr) const +{ + return static_cast(xsk_umem__get_data(m_Buffer, addr)); +} + +void XdpDevice::XdpUmem::setData(uint64_t addr, const uint8_t* data, size_t dataLen) +{ + auto dataPtr = static_cast(xsk_umem__get_data(m_Buffer, addr)); + memcpy(dataPtr, data, dataLen); +} + +std::pair> XdpDevice::XdpUmem::allocateFrames(uint32_t count) +{ + if (m_FreeFrames.size() < count) + { + PCPP_LOG_ERROR("Not enough frames to allocate. Requested: " << count << ", available: " << m_FreeFrames.size()); + return {false, {} }; + } + + std::vector result; + for (uint32_t i = 0; i < count; i++) + { + result.push_back(m_FreeFrames.back()); + m_FreeFrames.pop_back(); + } + + return {true, result}; +} + +void XdpDevice::XdpUmem::freeFrame(uint64_t addr) +{ + auto frame = (uint64_t )((addr / m_FrameSize) * m_FrameSize); + m_FreeFrames.push_back(frame); +} + +XdpDevice::XdpDevice(std::string interfaceName) : + m_InterfaceName(std::move(interfaceName)), m_Config(nullptr), m_ReceivingPackets(false), m_Umem(nullptr), m_SocketInfo(nullptr) +{ + memset(&m_Stats, 0, sizeof(m_Stats)); + memset(&m_PrevStats, 0, sizeof(m_PrevStats)); +} + +XdpDevice::~XdpDevice() +{ + close(); +} + +bool XdpDevice::receivePackets(OnPacketsArrive onPacketsArrive, void* onPacketsArriveUserCookie, int timeoutMS) +{ + if (!m_DeviceOpened) + { + PCPP_LOG_ERROR("Device is not open"); + return false; + } + + auto socketInfo = static_cast(m_SocketInfo); + + m_ReceivingPackets = true; + uint32_t rxId = 0; + + pollfd pollFds[1]; + pollFds[0] = { + .fd = xsk_socket__fd(socketInfo->xsk), + .events = POLLIN + }; + + while (m_ReceivingPackets) + { + checkCompletionRing(); + + auto pollResult = poll(pollFds, 1, timeoutMS); + if (pollResult == 0 && timeoutMS != 0) + { + m_Stats.rxPollTimeout++; + m_ReceivingPackets = false; + return true; + } + if (pollResult < 0) + { + PCPP_LOG_ERROR("poll() returned an error: " << errno); + m_ReceivingPackets = false; + return false; + } + + uint32_t receivedPacketsCount = xsk_ring_cons__peek(&socketInfo->rx, m_Config->rxTxBatchSize, &rxId); + + if (!receivedPacketsCount) + { + continue; + } + + m_Stats.rxPackets += receivedPacketsCount; + + RawPacket rawPacketsArr[receivedPacketsCount]; + + for (uint32_t i = 0; i < receivedPacketsCount; i++) + { + uint64_t addr = xsk_ring_cons__rx_desc(&socketInfo->rx, rxId + i)->addr; + uint32_t len = xsk_ring_cons__rx_desc(&socketInfo->rx, rxId + i)->len; + + auto data = m_Umem->getDataPtr(addr); + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + rawPacketsArr[i].initWithRawData(data, static_cast(len), ts); + + m_Stats.rxBytes += len; + + m_Umem->freeFrame(addr); + } + + onPacketsArrive(rawPacketsArr, receivedPacketsCount, this, onPacketsArriveUserCookie); + + xsk_ring_cons__release(&socketInfo->rx, receivedPacketsCount); + m_Stats.rxRingId = rxId + receivedPacketsCount; + + if (!populateFillRing(receivedPacketsCount, rxId)) + { + m_ReceivingPackets = false; + } + } + + return true; +} + +void XdpDevice::stopReceivePackets() +{ + m_ReceivingPackets = false; +} + +bool XdpDevice::sendPackets(const std::function& getPacketAt, const std::function& getPacketCount, bool waitForTxCompletion, int waitForTxCompletionTimeoutMS) +{ + if (!m_DeviceOpened) + { + PCPP_LOG_ERROR("Device is not open"); + return false; + } + + auto socketInfo = static_cast(m_SocketInfo); + + checkCompletionRing(); + + uint32_t txId = 0; + uint32_t packetCount = getPacketCount(); + + auto frameResponse = m_Umem->allocateFrames(packetCount); + if (!frameResponse.first) + { + return false; + } + + if (xsk_ring_prod__reserve(&socketInfo->tx, packetCount, &txId) < packetCount) + { + for (auto frame : frameResponse.second) + { + m_Umem->freeFrame(frame); + } + PCPP_LOG_ERROR("Cannot reserve " << packetCount << " tx slots"); + return false; + } + + for (uint32_t i = 0; i < packetCount; i++) + { + if (getPacketAt(i).getRawDataLen() > m_Umem->getFrameSize()) + { + PCPP_LOG_ERROR("Cannot send packets with data length (" << getPacketAt(i).getRawDataLen() << ") greater than UMEM frame size (" << m_Umem->getFrameSize() << ")"); + return false; + } + } + + uint64_t sentBytes = 0; + for (uint32_t i = 0; i < packetCount; i++) + { + uint64_t frame = frameResponse.second[i]; + m_Umem->setData(frame, getPacketAt(i).getRawData(), getPacketAt(i).getRawDataLen()); + + struct xdp_desc* txDesc = xsk_ring_prod__tx_desc(&socketInfo->tx, txId + i); + txDesc->addr = frame; + txDesc->len = getPacketAt(i).getRawDataLen(); + + sentBytes += txDesc->len; + + } + + xsk_ring_prod__submit(&socketInfo->tx, packetCount); + m_Stats.txSentPackets += packetCount; + m_Stats.txSentBytes += sentBytes; + m_Stats.txRingId = txId + packetCount; + + if (waitForTxCompletion) + { + uint32_t completedPackets = checkCompletionRing(); + + pollfd pollFds[1]; + pollFds[0] = { + .fd = xsk_socket__fd(socketInfo->xsk), + .events = POLLOUT + }; + + while (completedPackets < packetCount) + { + auto pollResult = poll(pollFds, 1, waitForTxCompletionTimeoutMS); + if (pollResult == 0 && waitForTxCompletionTimeoutMS != 0) + { + PCPP_LOG_ERROR("Wait for TX completion timed out"); + return false; + } + if (pollResult < 0) + { + PCPP_LOG_ERROR("poll() returned an error: " << errno); + return false; + } + + completedPackets += checkCompletionRing(); + } + } + + return true; +} + +bool XdpDevice::sendPackets(const RawPacketVector& packets, bool waitForTxCompletion, int waitForTxCompletionTimeoutMS) +{ + return sendPackets([&](uint32_t i) { return *packets.at(static_cast(i)); }, [&]() { return packets.size(); }, waitForTxCompletion, waitForTxCompletionTimeoutMS); +} + +bool XdpDevice::sendPackets(RawPacket packets[], size_t packetCount, bool waitForTxCompletion, int waitForTxCompletionTimeoutMS) +{ + return sendPackets([&](uint32_t i) { return packets[i]; }, [&]() { return static_cast(packetCount); }, waitForTxCompletion, waitForTxCompletionTimeoutMS); +} + +bool XdpDevice::populateFillRing(uint32_t count, uint32_t rxId) +{ + auto frameResponse = m_Umem->allocateFrames(count); + if (!frameResponse.first) + { + return false; + } + + bool result = populateFillRing(frameResponse.second, rxId); + if (!result) + { + for (auto frame : frameResponse.second) + { + m_Umem->freeFrame(frame); + } + } + + return result; +} + +bool XdpDevice::populateFillRing(const std::vector& addresses, uint32_t rxId) +{ + auto umem = static_cast(m_Umem->getInfo()); + auto count = static_cast(addresses.size()); + + uint32_t ret = xsk_ring_prod__reserve(&umem->fq,count, &rxId); + if (ret != count) + { + PCPP_LOG_ERROR("xsk_ring_prod__reserve returned: " << ret << "; expected: " << count); + return false; + } + + for (uint32_t i = 0; i < count; i++) + { + *xsk_ring_prod__fill_addr(&umem->fq, rxId + i) = addresses[i]; + } + + xsk_ring_prod__submit(&umem->fq, count); + m_Stats.fqRingId = rxId + count; + + return true; +} + +uint32_t XdpDevice::checkCompletionRing() +{ + uint32_t cqId = 0; + auto umemInfo = static_cast(m_Umem->getInfo()); + + auto socketInfo = static_cast(m_SocketInfo); + if (xsk_ring_prod__needs_wakeup(&socketInfo->tx)) + { + sendto(xsk_socket__fd(socketInfo->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); + } + + uint32_t completedCount = xsk_ring_cons__peek(&umemInfo->cq, m_Config->rxTxBatchSize, &cqId); + + if (completedCount) + { + for (uint32_t i = 0; i < completedCount; i++) + { + uint64_t addr = *xsk_ring_cons__comp_addr(&umemInfo->cq, cqId + i); + m_Umem->freeFrame(addr); + } + + xsk_ring_cons__release(&umemInfo->cq, completedCount); + m_Stats.cqRingId = cqId + completedCount; + } + + m_Stats.txCompletedPackets += completedCount; + return completedCount; +} + +bool XdpDevice::configureSocket() +{ + auto socketInfo = new xsk_socket_info(); + + auto umemInfo = static_cast(m_Umem->getInfo()); + + struct xsk_socket_config xskConfig; + xskConfig.rx_size = m_Config->txSize; + xskConfig.tx_size = m_Config->rxSize; + xskConfig.libbpf_flags = 0; + xskConfig.xdp_flags = 0; + xskConfig.bind_flags = 0; + if (m_Config->attachMode == XdpDeviceConfiguration::SkbMode) + { + xskConfig.xdp_flags = XDP_FLAGS_SKB_MODE; + xskConfig.bind_flags &= ~XDP_ZEROCOPY; + xskConfig.bind_flags |= XDP_COPY; + } + else if (m_Config->attachMode == XdpDeviceConfiguration::DriverMode) + { + xskConfig.xdp_flags = XDP_FLAGS_DRV_MODE; + } + + int ret = xsk_socket__create(&socketInfo->xsk, m_InterfaceName.c_str(), 0, umemInfo->umem, + &socketInfo->rx, &socketInfo->tx, &xskConfig); + if (ret) + { + PCPP_LOG_ERROR("xsk_socket__create returned an error: " << ret); + delete socketInfo; + return false; + } + + m_SocketInfo = socketInfo; + return true; +} + +bool XdpDevice::initUmem() +{ + m_Umem = new XdpUmem(m_Config->umemNumFrames, m_Config->umemFrameSize, m_Config->fillRingSize, m_Config->completionRingSize); + return true; +} + +bool XdpDevice::initConfig() +{ + if (!m_Config) + { + m_Config = new XdpDeviceConfiguration(); + } + + uint16_t numFrames = m_Config->umemNumFrames ? m_Config->umemNumFrames : DEFAULT_UMEM_NUM_FRAMES; + uint16_t frameSize = m_Config->umemFrameSize ? m_Config->umemFrameSize : getpagesize(); + uint32_t fillRingSize = m_Config->fillRingSize ? m_Config->fillRingSize : DEFAULT_FILL_RING_SIZE; + uint32_t completionRingSize = m_Config->completionRingSize ? m_Config->completionRingSize : DEFAULT_COMPLETION_RING_SIZE; + uint32_t rxSize = m_Config->rxSize ? m_Config->rxSize : XSK_RING_CONS__DEFAULT_NUM_DESCS; + uint32_t txSize = m_Config->txSize ? m_Config->txSize : XSK_RING_PROD__DEFAULT_NUM_DESCS; + uint32_t batchSize = m_Config->rxTxBatchSize ? m_Config->rxTxBatchSize : DEFAULT_BATCH_SIZE; + + if (frameSize != getpagesize()) + { + PCPP_LOG_ERROR("UMEM frame size must match the memory page size (" << getpagesize() << ")"); + return false; + } + + if (!(IS_POWER_OF_TWO(fillRingSize) && IS_POWER_OF_TWO(completionRingSize) && IS_POWER_OF_TWO(rxSize) && IS_POWER_OF_TWO(txSize))) + { + PCPP_LOG_ERROR("All ring sizes (fill ring, completion ring, rx ring, tx ring) should be a power of two"); + return false; + } + + if (fillRingSize > numFrames) + { + PCPP_LOG_ERROR("Fill ring size (" << fillRingSize << ") must be lower or equal to the total number of UMEM frames (" << numFrames << ")"); + return false; + } + + if (completionRingSize > numFrames) + { + PCPP_LOG_ERROR("Completion ring size (" << completionRingSize << ") must be lower or equal to the total number of UMEM frames (" << numFrames << ")"); + return false; + } + + if (rxSize > numFrames) + { + PCPP_LOG_ERROR("RX size (" << rxSize << ") must be lower or equal to the total number of UMEM frames (" << numFrames << ")"); + return false; + } + + if (txSize > numFrames) + { + PCPP_LOG_ERROR("TX size (" << txSize << ") must be lower or equal to the total number of UMEM frames (" << numFrames << ")"); + return false; + } + + if (batchSize > rxSize || batchSize > txSize) + { + PCPP_LOG_ERROR("RX/TX batch size (" << batchSize << ") must be lower or equal to RX/TX ring size"); + return false; + } + + m_Config->umemNumFrames = numFrames; + m_Config->umemFrameSize = frameSize; + m_Config->fillRingSize = fillRingSize; + m_Config->completionRingSize = completionRingSize; + m_Config->rxSize = rxSize; + m_Config->txSize = txSize; + m_Config->rxTxBatchSize = batchSize; + + return true; +} + +bool XdpDevice::open() +{ + if (m_DeviceOpened) + { + PCPP_LOG_ERROR("Device already opened"); + return false; + } + + if (!(initConfig() && + initUmem() && + populateFillRing(std::min(m_Config->fillRingSize, static_cast(m_Config->umemNumFrames / 2))) && + configureSocket())) + { + if (m_Umem) + { + delete m_Umem; + m_Umem = nullptr; + } + if (m_Config) + { + delete m_Config; + m_Config = nullptr; + } + return false; + } + + memset(&m_Stats, 0, sizeof(m_Stats)); + memset(&m_PrevStats, 0 ,sizeof(m_PrevStats)); + + m_DeviceOpened = true; + return m_DeviceOpened; +} + +bool XdpDevice::open(const XdpDeviceConfiguration& config) +{ + m_Config = new XdpDeviceConfiguration(config); + return open(); +} + +void XdpDevice::close() +{ + if (m_DeviceOpened) + { + auto socketInfo = static_cast(m_SocketInfo); + xsk_socket__delete(socketInfo->xsk); + m_DeviceOpened = false; + delete m_Umem; + delete m_Config; + m_Config = nullptr; + m_Umem = nullptr; + } +} + +bool XdpDevice::getSocketStats() +{ + auto socketInfo = static_cast(m_SocketInfo); + int fd = xsk_socket__fd(socketInfo->xsk); + + struct xdp_statistics socketStats; + socklen_t optlen = sizeof(socketStats); + + int err = getsockopt(fd, SOL_XDP, XDP_STATISTICS, &socketStats, &optlen); + if (err) + { + PCPP_LOG_ERROR("Error getting stats from socket, return error: " << err); + return false; + } + + if (optlen != sizeof(struct xdp_statistics)) + { + PCPP_LOG_ERROR("Error getting stats from socket: optlen (" << optlen << ") != expected size (" << sizeof(struct xdp_statistics) << ")"); + return false; + } + + m_Stats.rxDroppedInvalidPackets = socketStats.rx_invalid_descs; + m_Stats.rxDroppedRxRingFullPackets = socketStats.rx_ring_full; + m_Stats.rxDroppedFillRingPackets = socketStats.rx_fill_ring_empty_descs; + m_Stats.rxDroppedTotalPackets = m_Stats.rxDroppedFillRingPackets + m_Stats.rxDroppedRxRingFullPackets + m_Stats.rxDroppedInvalidPackets + socketStats.rx_dropped; + m_Stats.txDroppedInvalidPackets = socketStats.tx_invalid_descs; + + return true; +} + +#define nanosec_gap(begin, end) ((end.tv_sec - begin.tv_sec) * 1000000000.0 + (end.tv_nsec - begin.tv_nsec)) + +XdpDevice::XdpDeviceStats XdpDevice::getStatistics() +{ + timespec timestamp; + clock_gettime(CLOCK_MONOTONIC, ×tamp); + + m_Stats.timestamp = timestamp; + + if (m_DeviceOpened) + { + getSocketStats(); + m_Stats.umemFreeFrames = m_Umem->getFreeFrameCount(); + m_Stats.umemAllocatedFrames = m_Umem->getFrameCount() - m_Stats.umemFreeFrames; + } + else + { + m_Stats.umemFreeFrames = 0; + m_Stats.umemAllocatedFrames = 0; + } + + double secsElapsed = (double)nanosec_gap(m_PrevStats.timestamp, timestamp) / 1000000000.0; + m_Stats.rxPacketsPerSec = static_cast((m_Stats.rxPackets - m_PrevStats.rxPackets) / secsElapsed); + m_Stats.rxBytesPerSec = static_cast((m_Stats.rxBytes - m_PrevStats.rxBytes) / secsElapsed); + m_Stats.txSentPacketsPerSec = static_cast((m_Stats.txSentPackets - m_PrevStats.txSentPackets) / secsElapsed); + m_Stats.txSentBytesPerSec = static_cast((m_Stats.txSentBytes - m_PrevStats.txSentBytes) / secsElapsed); + m_Stats.txCompletedPacketsPerSec = static_cast((m_Stats.txCompletedPackets - m_PrevStats.txCompletedPackets) / secsElapsed); + + m_PrevStats.timestamp = timestamp; + m_PrevStats.rxPackets = m_Stats.rxPackets; + m_PrevStats.rxBytes = m_Stats.rxBytes; + m_PrevStats.txSentPackets = m_Stats.txSentPackets; + m_PrevStats.txSentBytes = m_Stats.txSentBytes; + m_PrevStats.txCompletedPackets = m_Stats.txCompletedPackets; + + return m_Stats; +} + +} diff --git a/Tests/Pcap++Test/CMakeLists.txt b/Tests/Pcap++Test/CMakeLists.txt index e8a47b1ef9..73c6fdcc06 100644 --- a/Tests/Pcap++Test/CMakeLists.txt +++ b/Tests/Pcap++Test/CMakeLists.txt @@ -14,7 +14,8 @@ add_executable( Tests/PfRingTests.cpp Tests/RawSocketTests.cpp Tests/SystemUtilsTests.cpp - Tests/TcpReassemblyTests.cpp) + Tests/TcpReassemblyTests.cpp + Tests/XdpTests.cpp) target_link_libraries( Pcap++Test diff --git a/Tests/Pcap++Test/TestDefinition.h b/Tests/Pcap++Test/TestDefinition.h index bebcd9a917..cc03d8d744 100644 --- a/Tests/Pcap++Test/TestDefinition.h +++ b/Tests/Pcap++Test/TestDefinition.h @@ -108,3 +108,9 @@ PTF_TEST_CASE(TestRawSockets); // Implemented in SystemUtilsTests.cpp PTF_TEST_CASE(TestSystemCoreUtils); + +// Implemented in XdpTest.cpp +PTF_TEST_CASE(TestXdpDeviceReceivePackets); +PTF_TEST_CASE(TestXdpDeviceSendPackets); +PTF_TEST_CASE(TestXdpDeviceNonDefaultConfig); +PTF_TEST_CASE(TestXdpDeviceInvalidConfig); diff --git a/Tests/Pcap++Test/Tests/XdpTests.cpp b/Tests/Pcap++Test/Tests/XdpTests.cpp new file mode 100644 index 0000000000..30c9cf4042 --- /dev/null +++ b/Tests/Pcap++Test/Tests/XdpTests.cpp @@ -0,0 +1,304 @@ +#include "../TestDefinition.h" +#include "../Common/GlobalTestArgs.h" +#include "PcapLiveDeviceList.h" +#include "XdpDevice.h" +#include "PcapFileDevice.h" +#include "Packet.h" +#include "Logger.h" + + +extern PcapTestArgs PcapTestGlobalArgs; + +#ifdef USE_XDP + +struct XdpPacketData +{ + int packetCount; + int byteCount; + uint64_t latestTimestamp; + + XdpPacketData() : packetCount(0), byteCount(0), latestTimestamp(0) {} +}; + +bool assertConfig(const pcpp::XdpDevice::XdpDeviceConfiguration* config, + const pcpp::XdpDevice::XdpDeviceConfiguration::AttachMode expectedAttachMode, + const uint16_t expectedUmemNumFrames, + const uint16_t expectedUmemFrameSize, + const uint32_t expectedFillRingSize, + const uint32_t expectedCompletionRingSize, + const uint32_t expectedRxSize, + const uint32_t expectedTxSize, + const uint16_t expectedRxTxBatchSize) +{ + return ( + config != nullptr && + config->attachMode == expectedAttachMode && + config->umemNumFrames == expectedUmemNumFrames && + config->umemFrameSize == expectedUmemFrameSize && + config->fillRingSize == expectedFillRingSize && + config->completionRingSize == expectedCompletionRingSize && + config->rxSize == expectedRxSize && + config->txSize == expectedTxSize && + config->rxTxBatchSize == expectedRxTxBatchSize); +} + +std::string getDeviceName() +{ + auto pcapLiveDev = pcpp::PcapLiveDeviceList::getInstance().getPcapLiveDeviceByIp(PcapTestGlobalArgs.ipToSendReceivePackets.c_str()); + if (pcapLiveDev) + { + return pcapLiveDev->getName(); + } + + return ""; +} + + +#endif // USE_XDP + +PTF_TEST_CASE(TestXdpDeviceReceivePackets) +{ +#ifdef USE_XDP + std::string devName = getDeviceName(); + PTF_ASSERT_FALSE(devName.empty()); + pcpp::XdpDevice device(devName); + + PTF_ASSERT_NULL(device.getConfig()); + + PTF_ASSERT_TRUE(device.open()); + + PTF_ASSERT_TRUE( + assertConfig(device.getConfig(), + pcpp::XdpDevice::XdpDeviceConfiguration::AutoMode, + 4096, 4096,4096,2048,2048,2048,64)); + + XdpPacketData packetData; + + auto onPacketsArrive = [](pcpp::RawPacket packets[], uint32_t packetCount, pcpp::XdpDevice* device, void* userCookie) -> void { + auto packetData = static_cast(userCookie); + + for (uint32_t i = 0; i < packetCount; i++) + { + if (packets[i].getRawDataLen() > 0) + { + packetData->packetCount++; + packetData->byteCount += packets[i].getRawDataLen(); + packetData->latestTimestamp = 1000*1000*1000*packets[i].getPacketTimeStamp().tv_sec + packets[i].getPacketTimeStamp().tv_nsec; + } + } + + if (packetData->packetCount >= 5) + { + device->stopReceivePackets(); + } + }; + + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + uint64_t curTimestamp = 1000*1000*1000*ts.tv_sec + ts.tv_nsec; + + PTF_ASSERT_TRUE(device.receivePackets(onPacketsArrive, &packetData, 20000)); + + PTF_ASSERT_GREATER_OR_EQUAL_THAN(packetData.packetCount, 5); + PTF_ASSERT_GREATER_THAN(packetData.latestTimestamp, curTimestamp); + + auto stats = device.getStatistics(); + PTF_ASSERT_GREATER_THAN(stats.umemAllocatedFrames, 0); + PTF_ASSERT_GREATER_THAN(stats.umemFreeFrames, 0); + + device.close(); + + stats = device.getStatistics(); + + PTF_ASSERT_EQUAL(stats.rxPackets, packetData.packetCount); + PTF_ASSERT_EQUAL(stats.rxBytes, packetData.byteCount); + PTF_ASSERT_EQUAL(stats.rxDroppedTotalPackets, 0); + PTF_ASSERT_EQUAL(stats.txSentPackets, 0); + PTF_ASSERT_EQUAL(stats.txSentBytes, 0); + PTF_ASSERT_EQUAL(stats.txCompletedPackets, 0); + PTF_ASSERT_EQUAL(stats.txDroppedInvalidPackets, 0); + PTF_ASSERT_EQUAL(stats.txSentBytesPerSec, 0); + PTF_ASSERT_EQUAL(stats.txSentPacketsPerSec, 0); + PTF_ASSERT_EQUAL(stats.txCompletedPacketsPerSec, 0); + PTF_ASSERT_EQUAL(stats.umemAllocatedFrames, 0); + PTF_ASSERT_EQUAL(stats.umemFreeFrames, 0); + PTF_ASSERT_GREATER_THAN(stats.rxRingId, 0); + PTF_ASSERT_GREATER_THAN(stats.fqRingId, 0); + PTF_ASSERT_EQUAL(stats.txRingId, 0); + PTF_ASSERT_EQUAL(stats.cqRingId, 0); + + pcpp::Logger::getInstance().suppressLogs(); + PTF_ASSERT_FALSE(device.receivePackets(onPacketsArrive, nullptr)); + pcpp::Logger::getInstance().enableLogs(); +#else + PTF_SKIP_TEST("XDP not configured"); +#endif +} // TestXdpDeviceReceivePackets + + +PTF_TEST_CASE(TestXdpDeviceSendPackets) +{ +#ifdef USE_XDP + std::string devName = getDeviceName(); + PTF_ASSERT_FALSE(devName.empty()); + pcpp::XdpDevice device(devName); + + pcpp::PcapFileReaderDevice reader("PcapExamples/one_http_stream_fin.pcap"); + PTF_ASSERT_TRUE(reader.open()); + pcpp::RawPacketVector packets; + reader.getNextPackets(packets); + + PTF_ASSERT_TRUE(device.open()); + + PTF_ASSERT_TRUE(device.sendPackets(packets, true)); + + auto stats = device.getStatistics(); + PTF_ASSERT_EQUAL(stats.rxPackets, 0); + PTF_ASSERT_EQUAL(stats.rxBytes, 0); + PTF_ASSERT_EQUAL(stats.rxDroppedTotalPackets, 0); + PTF_ASSERT_EQUAL(stats.txSentPackets, 15); + PTF_ASSERT_EQUAL(stats.txSentBytes, 4808); + PTF_ASSERT_EQUAL(stats.txCompletedPackets, 15); + PTF_ASSERT_EQUAL(stats.txDroppedInvalidPackets, 0); + PTF_ASSERT_GREATER_THAN(stats.umemAllocatedFrames, 0); + PTF_ASSERT_GREATER_THAN(stats.umemFreeFrames, 0); + PTF_ASSERT_EQUAL(stats.rxRingId, 0); + PTF_ASSERT_EQUAL(stats.fqRingId, 0); + PTF_ASSERT_GREATER_THAN(stats.txRingId, 0); + PTF_ASSERT_GREATER_THAN(stats.cqRingId, 0); + + PTF_ASSERT_TRUE(device.sendPackets(packets)); + + stats = device.getStatistics(); + PTF_ASSERT_NOT_EQUAL(stats.txSentPackets, stats.txCompletedPackets); + + device.close(); + + pcpp::Logger::getInstance().suppressLogs(); + PTF_ASSERT_FALSE(device.sendPackets(packets)); + pcpp::Logger::getInstance().enableLogs(); +#else + PTF_SKIP_TEST("XDP not configured"); +#endif +} // TestXdpDeviceSendPackets + + +PTF_TEST_CASE(TestXdpDeviceNonDefaultConfig) +{ +#ifdef USE_XDP + std::string devName = getDeviceName(); + PTF_ASSERT_FALSE(devName.empty()); + pcpp::XdpDevice device(devName); + + auto config = pcpp::XdpDevice::XdpDeviceConfiguration(pcpp::XdpDevice::XdpDeviceConfiguration::SkbMode, + 1000, 4096, 512, 512, 512, 512, 20); + PTF_ASSERT_TRUE(device.open(config)); + + PTF_ASSERT_TRUE( + assertConfig(device.getConfig(), + pcpp::XdpDevice::XdpDeviceConfiguration::SkbMode, + 1000, 4096,512,512,512,512,20)); + + int numPackets = 0; + + auto onPacketsArrive = [](pcpp::RawPacket packets[], uint32_t packetCount, pcpp::XdpDevice* device, void* userCookie) -> void { + int* totalPacketCount = static_cast(userCookie); + + for (uint32_t i = 0; i < packetCount; i++) + { + if (packets[i].getRawDataLen() > 0) + { + (*totalPacketCount)++; + } + } + + if (*totalPacketCount >= 5) + { + device->stopReceivePackets(); + } + }; + + PTF_ASSERT_TRUE(device.receivePackets(onPacketsArrive, &numPackets, 20000)); + + PTF_ASSERT_GREATER_OR_EQUAL_THAN(numPackets, 5); +#else + PTF_SKIP_TEST("XDP not configured"); +#endif +} // TestXdpDeviceNonDefaultConfig + + +PTF_TEST_CASE(TestXdpDeviceInvalidConfig) +{ +#ifdef USE_XDP + std::string devName = getDeviceName(); + PTF_ASSERT_FALSE(devName.empty()); + pcpp::XdpDevice device(devName); + + pcpp::Logger::getInstance().suppressLogs(); + + // Frame size is not a power of 2 + auto config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.umemFrameSize = 1000; + + PTF_ASSERT_FALSE(device.open(config)); + + // Fill ring size is not a power of 2 + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.fillRingSize = 100; + + PTF_ASSERT_FALSE(device.open(config)); + + // Completion ring size is not a power of 2 + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.completionRingSize = 100; + + PTF_ASSERT_FALSE(device.open(config)); + + // RX ring size is not a power of 2 + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.rxSize = 100; + + PTF_ASSERT_FALSE(device.open(config)); + + // TX ring size is not a power of 2 + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.txSize = 100; + + PTF_ASSERT_FALSE(device.open(config)); + + // Fill ring size is larger than total number of frames + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.fillRingSize = 8192; + + PTF_ASSERT_FALSE(device.open(config)); + + // Completion ring size is larger than total number of frames + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.completionRingSize = 8192; + + PTF_ASSERT_FALSE(device.open(config)); + + // RX ring size is larger than total number of frames + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.rxSize = 8192; + + PTF_ASSERT_FALSE(device.open(config)); + + // TX ring size is larger than total number of frames + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.txSize = 8192; + + PTF_ASSERT_FALSE(device.open(config)); + + // Batch ring size is larger than RX/TX size + config = pcpp::XdpDevice::XdpDeviceConfiguration(); + config.rxTxBatchSize = 8192; + + PTF_ASSERT_FALSE(device.open(config)); + + pcpp::Logger::getInstance().enableLogs(); +#else + PTF_SKIP_TEST("XDP not configured"); +#endif +} // TestXdpDeviceInvalidConfig diff --git a/Tests/Pcap++Test/main.cpp b/Tests/Pcap++Test/main.cpp index a62f94fa56..a4af1391c3 100644 --- a/Tests/Pcap++Test/main.cpp +++ b/Tests/Pcap++Test/main.cpp @@ -293,6 +293,11 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(TestSystemCoreUtils, "no_network;system_utils"); + PTF_RUN_TEST(TestXdpDeviceReceivePackets, "xdp"); + PTF_RUN_TEST(TestXdpDeviceSendPackets, "xdp"); + PTF_RUN_TEST(TestXdpDeviceNonDefaultConfig, "xdp"); + PTF_RUN_TEST(TestXdpDeviceInvalidConfig, "xdp"); + PTF_END_RUNNING_TESTS; } diff --git a/cmake/modules/FindBPF.cmake b/cmake/modules/FindBPF.cmake new file mode 100644 index 0000000000..80daedc211 --- /dev/null +++ b/cmake/modules/FindBPF.cmake @@ -0,0 +1,35 @@ +# ~~~ +# - Try to find libbpf +# +# Once done this will define +# +# BPF_FOUND - System has libbpf +# BPF_INCLUDE_DIRS - The libbpf include directories +# BPF_LIBRARIES - The libraries needed to use libbpf +# ~~~ + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBBPF libbpf) + +find_path( + BPF_INCLUDE_DIRS + NAMES bpf/bpf.h + HINTS ${PC_LIBBPF_INCLUDE_DIRS}) + +find_library( + BPF_LIBRARIES + NAMES bpf + HINTS ${PC_LIBBPF_LIBRARY_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + BPF + REQUIRED_VARS BPF_LIBRARIES BPF_INCLUDE_DIRS + VERSION_VAR BPF_VERSION + FAIL_MESSAGE "libbpf not found!") + +if(BPF_FOUND AND NOT TARGET BPF::BPF) + add_library(BPF::BPF INTERFACE IMPORTED) + set_target_properties(BPF::BPF PROPERTIES INTERFACE_LINK_LIBRARIES "${BPF_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES + "${BPF_INCLUDE_DIRS}") +endif()