From 13a3c07032a4ee143407c00dee59716f362c842c Mon Sep 17 00:00:00 2001 From: alacrity-aya Date: Thu, 27 Aug 2026 22:02:05 +0800 Subject: [PATCH 1/4] Add GENEVE protocol layer --- Common++/header/Logger.h | 1 + Packet++/CMakeLists.txt | 2 + Packet++/header/GeneveLayer.h | 346 +++++++++++++++++++ Packet++/header/ProtocolType.h | 3 + Packet++/header/UdpLayer.h | 4 +- Packet++/src/GeneveLayer.cpp | 408 +++++++++++++++++++++++ Packet++/src/UdpLayer.cpp | 5 + Tests/Packet++Test/CMakeLists.txt | 1 + Tests/Packet++Test/TestDefinition.h | 4 + Tests/Packet++Test/Tests/GeneveTests.cpp | 158 +++++++++ Tests/Packet++Test/main.cpp | 2 + 11 files changed, 932 insertions(+), 2 deletions(-) create mode 100644 Packet++/header/GeneveLayer.h create mode 100644 Packet++/src/GeneveLayer.cpp create mode 100644 Tests/Packet++Test/Tests/GeneveTests.cpp diff --git a/Common++/header/Logger.h b/Common++/header/Logger.h index 84638a5115..cc22e355c0 100644 --- a/Common++/header/Logger.h +++ b/Common++/header/Logger.h @@ -102,6 +102,7 @@ namespace pcpp PacketLogModuleSmtpLayer, ///< SmtpLayer module (Packet++) PacketLogModuleWireGuardLayer, ///< WireGuardLayer module (Packet++) PacketLogModuleDoIpLayer, ///< DoipLayer module (Packet++) + PacketLogModuleGeneveLayer, ///< GeneveLayer module (Packet++) PcapLogModuleWinPcapLiveDevice, ///< WinPcapLiveDevice module (Pcap++) PcapLogModuleRemoteDevice, ///< WinPcapRemoteDevice module (Pcap++) PcapLogModuleLiveDevice, ///< PcapLiveDevice module (Pcap++) diff --git a/Packet++/CMakeLists.txt b/Packet++/CMakeLists.txt index 7a6db98422..81d8c3642a 100644 --- a/Packet++/CMakeLists.txt +++ b/Packet++/CMakeLists.txt @@ -16,6 +16,7 @@ add_library( src/EthDot3Layer.cpp src/EthLayer.cpp src/FtpLayer.cpp + src/GeneveLayer.cpp src/GreLayer.cpp src/GtpLayer.cpp src/HttpLayer.cpp @@ -97,6 +98,7 @@ set( header/EthDot3Layer.h header/EthLayer.h header/FtpLayer.h + header/GeneveLayer.h header/GreLayer.h header/GtpLayer.h header/HttpLayer.h diff --git a/Packet++/header/GeneveLayer.h b/Packet++/header/GeneveLayer.h new file mode 100644 index 0000000000..5e32a7a84f --- /dev/null +++ b/Packet++/header/GeneveLayer.h @@ -0,0 +1,346 @@ +#pragma once + +#include "Layer.h" +#include "TLVData.h" + +#include +#include + +/// @file + +/// @namespace pcpp +/// @brief The main namespace for the PcapPlusPlus lib +namespace pcpp +{ + class GeneveLayer; + class GeneveOptionIterator; + + /// @struct geneve_header + /// Represents the fixed part of a GENEVE protocol header +#pragma pack(push, 1) + struct geneve_header + { +#if (BYTE_ORDER == LITTLE_ENDIAN) + /// Options length in 4-byte units + uint8_t optionsLength : 6; + /// Protocol version + uint8_t version : 2; + /// Reserved bits + uint8_t reserved1 : 6; + /// Critical options present flag + uint8_t criticalFlag : 1; + /// Operations, administration, and maintenance packet flag + uint8_t oamFlag : 1; +#else + /// Protocol version + uint8_t version : 2; + /// Options length in 4-byte units + uint8_t optionsLength : 6; + /// Operations, administration, and maintenance packet flag + uint8_t oamFlag : 1; + /// Critical options present flag + uint8_t criticalFlag : 1; + /// Reserved bits + uint8_t reserved1 : 6; +#endif + /// EtherType of the encapsulated protocol + uint16_t protocolType; + /// Virtual network identifier + uint8_t vni[3]; + /// Reserved byte + uint8_t reserved2; + }; +#pragma pack(pop) + static_assert(sizeof(geneve_header) == 8, "geneve_header size is not 8 bytes"); + + /// @struct geneve_option_header + /// Represents a GENEVE option header +#pragma pack(push, 1) + struct geneve_option_header + { + /// Option namespace assigned by IANA + uint16_t optionClass; + /// Option type. The most significant bit is the critical bit + uint8_t type; +#if (BYTE_ORDER == LITTLE_ENDIAN) + /// Option data length in 4-byte units + uint8_t length : 5; + /// Reserved bits + uint8_t reserved : 3; +#else + /// Reserved bits + uint8_t reserved : 3; + /// Option data length in 4-byte units + uint8_t length : 5; +#endif + }; +#pragma pack(pop) + static_assert(sizeof(geneve_option_header) == 4, "geneve_option_header size is not 4 bytes"); + + /// @class GeneveOption + /// A non-owning view of a GENEVE option. The view is invalidated when its underlying data is destroyed or moved, + /// including when options are added to or removed from the containing GeneveLayer + class GeneveOption + { + friend class GeneveLayer; + friend class GeneveOptionIterator; + friend class GeneveOptionRange; + + private: + geneve_option_header* m_Data; + + explicit GeneveOption(geneve_option_header& optionData) : m_Data(&optionData) + {} + + static bool canAssign(const uint8_t* optionRawData, size_t optionDataLen); + + public: + /// @return Option class in host byte order + uint16_t getOptionClass() const; + + /// @return The 7-bit option type without the critical bit + uint8_t getType() const; + + /// @return True if the option is critical + bool isCritical() const; + + /// @return Option data length in bytes + size_t getDataSize() const; + + /// @return Total option size including its 4-byte header + size_t getTotalSize() const; + + /// @return A pointer to the option data + uint8_t* getData() const; + + /// @return A pointer to the option header + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + }; + + /// @class GeneveOptionIterator + /// An input iterator over structurally valid GENEVE options + class GeneveOptionIterator + { + friend class GeneveOptionRange; + + private: + uint8_t* m_Current; + uint8_t* m_End; + + GeneveOptionIterator(uint8_t* current, uint8_t* end) : m_Current(current), m_End(end) + {} + + public: + using iterator_category = std::input_iterator_tag; + using value_type = GeneveOption; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = GeneveOption; + + /// Dereference this iterator + /// @return A valid non-owning option view + /// @pre This iterator must not equal the end iterator + GeneveOption operator*() const; + + /// Advance to the next structurally valid option, or to the end iterator + /// @return This iterator + GeneveOptionIterator& operator++(); + + /// Advance to the next structurally valid option, or to the end iterator + /// @return The iterator value before it was advanced + GeneveOptionIterator operator++(int); + + /// Compare two option iterators + /// @param[in] other The iterator to compare + /// @return True if both iterators refer to the same position in the same range + bool operator==(const GeneveOptionIterator& other) const + { + return m_Current == other.m_Current && m_End == other.m_End; + } + + /// Compare two option iterators + /// @param[in] other The iterator to compare + /// @return True if the iterators refer to different positions or ranges + bool operator!=(const GeneveOptionIterator& other) const + { + return !operator==(other); + } + }; + + /// @class GeneveOptionRange + /// A non-owning range of GENEVE options. The range and all iterators obtained from it are invalidated when the + /// containing GeneveLayer is modified or destroyed + class GeneveOptionRange + { + friend class GeneveLayer; + + private: + uint8_t* m_Begin; + uint8_t* m_End; + + GeneveOptionRange(uint8_t* begin, uint8_t* end); + + public: + /// Construct an empty option range + GeneveOptionRange() : m_Begin(nullptr), m_End(nullptr) + {} + + /// @return An iterator to the first option, or end() if the range is empty + GeneveOptionIterator begin() const + { + return GeneveOptionIterator(m_Begin, m_End); + } + + /// @return The iterator marking the end of this range + GeneveOptionIterator end() const + { + return GeneveOptionIterator(m_End, m_End); + } + + /// Find the first option matching a class and type + /// @param[in] optionClass Option class in host byte order + /// @param[in] optionType The 7-bit option type + /// @return An iterator to the matching option, or end() if no option matches + GeneveOptionIterator find(uint16_t optionClass, uint8_t optionType) const; + + /// @return The number of structurally valid options in this range + size_t size() const; + + /// @return True if this range contains no options + bool empty() const + { + return m_Begin == m_End; + } + }; + + /// @class GeneveOptionBuilder + /// Builds GENEVE options. Option data is padded with zeroes to a 4-byte boundary + class GeneveOptionBuilder : public TLVRecordBuilder + { + private: + uint16_t m_OptionClass; + bool m_Critical; + + public: + /// Construct a GENEVE option builder + /// @param[in] optionClass Option namespace assigned by IANA + /// @param[in] optionType The 7-bit option type + /// @param[in] optionData A read-only buffer containing option data + /// @param[in] optionDataLen Option data length in bytes. The maximum supported length is 124 bytes + /// @param[in] critical Set the option critical bit + GeneveOptionBuilder(uint16_t optionClass, uint8_t optionType, const uint8_t* optionData, uint8_t optionDataLen, + bool critical = false) + : TLVRecordBuilder(optionType, optionData, optionDataLen), m_OptionClass(optionClass), m_Critical(critical) + {} + + /// Build a GENEVE option into an owning byte buffer + /// @return The encoded option, or an empty buffer if the data is too long + std::vector build() const; + }; + + /// @class GeneveLayer + /// Represents a GENEVE (Generic Network Virtualization Encapsulation) protocol layer + class GeneveLayer : public Layer + { + public: + /// The IANA-assigned UDP destination port for GENEVE + static constexpr uint16_t DefaultPort = 6081; + + /// Construct a layer from existing packet data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where the layer is stored + GeneveLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : Layer(data, dataLen, prevLayer, packet, Geneve) + {} + + /// Construct a new GENEVE layer + /// @param[in] vni The 24-bit virtual network identifier + /// @param[in] protocolType EtherType of the encapsulated protocol. Defaults to Transparent Ethernet Bridging + /// @param[in] oamFlag Set the operations, administration, and maintenance flag + explicit GeneveLayer(uint32_t vni = 0, uint16_t protocolType = 0x6558, bool oamFlag = false); + + ~GeneveLayer() override = default; + + /// Validate a GENEVE byte stream, including all declared options + /// @param[in] data The beginning of the GENEVE header + /// @param[in] dataLen Available bytes + /// @return True if the data contains a supported, structurally valid GENEVE header + static bool isDataValid(const uint8_t* data, size_t dataLen); + + /// Check whether a UDP port is the standard GENEVE destination port + /// @param[in] port UDP port in host byte order + /// @return True for port 6081 + static bool isGenevePort(uint16_t port) + { + return port == DefaultPort; + } + + /// @return A pointer to the fixed GENEVE header + geneve_header* getGeneveHeader() const + { + return reinterpret_cast(m_Data); + } + + /// @return The VNI in host byte order + uint32_t getVNI() const; + + /// Set the VNI. Only the least significant 24 bits are used + /// @param[in] vni The VNI to set + void setVNI(uint32_t vni); + + /// @return Encapsulated protocol EtherType in host byte order + uint16_t getProtocolType() const; + + /// Set the encapsulated protocol EtherType + /// @param[in] protocolType EtherType in host byte order + void setProtocolType(uint16_t protocolType); + + /// @return Total options length in bytes + size_t getOptionsLength() const; + + /// @return Number of structurally valid options in this layer + size_t getOptionCount() const; + + /// @return A non-owning range over the options in this layer + GeneveOptionRange getOptions() const; + + /// Add an option after all existing options + /// @param[in] optionBuilder Builder containing the option to add + /// @return True if the option was added successfully + bool addOption(const GeneveOptionBuilder& optionBuilder); + + /// Remove the first option matching a class and type + /// @param[in] optionClass Option class in host byte order + /// @param[in] optionType The 7-bit option type + /// @return True if an option was found and removed + bool removeOption(uint16_t optionClass, uint8_t optionType); + + /// Remove all options + /// @return True if all options were removed + bool removeAllOptions(); + + /// Parse the encapsulated protocol according to the Protocol Type field + void parseNextLayer() override; + + /// @return Fixed header plus the declared options length + size_t getHeaderLen() const override; + + /// Update the Protocol Type and Critical flag from the following layer and options + void computeCalculateFields() override; + + std::string toString() const override; + + OsiModelLayer getOsiModelLayer() const override + { + return OsiModelDataLinkLayer; + } + + private: + void updateCriticalFlag(); + }; +} // namespace pcpp diff --git a/Packet++/header/ProtocolType.h b/Packet++/header/ProtocolType.h index 5b8965b51f..d2906a96a5 100644 --- a/Packet++/header/ProtocolType.h +++ b/Packet++/header/ProtocolType.h @@ -257,6 +257,9 @@ namespace pcpp /// MySQL protocol const ProtocolType MySQL = 63; + /// GENEVE protocol + const ProtocolType Geneve = 65; + /// FTP protocol family (FTPControl and FtpData protocols) const ProtocolTypeFamily FTP = 0x3c29; diff --git a/Packet++/header/UdpLayer.h b/Packet++/header/UdpLayer.h index c78bc2ded9..1963777156 100644 --- a/Packet++/header/UdpLayer.h +++ b/Packet++/header/UdpLayer.h @@ -72,8 +72,8 @@ namespace pcpp // implement abstract methods - /// Currently identifies the following next layers: DnsLayer, DhcpLayer, VxlanLayer, SipRequestLayer, - /// SipResponseLayer, RadiusLayer. Otherwise sets PayloadLayer + /// Currently identifies the following next layers: DnsLayer, DhcpLayer, VxlanLayer, GeneveLayer, + /// SipRequestLayer, SipResponseLayer, RadiusLayer. Otherwise sets PayloadLayer void parseNextLayer() override; /// @return Size of @ref udphdr diff --git a/Packet++/src/GeneveLayer.cpp b/Packet++/src/GeneveLayer.cpp new file mode 100644 index 0000000000..d322f91a36 --- /dev/null +++ b/Packet++/src/GeneveLayer.cpp @@ -0,0 +1,408 @@ +#define LOG_MODULE PacketLogModuleGeneveLayer + +#include "GeneveLayer.h" +#include "EthDot3Layer.h" +#include "EthLayer.h" +#include "IPv4Layer.h" +#include "IPv6Layer.h" +#include "Logger.h" +#include "MplsLayer.h" +#include "PayloadLayer.h" +#include "VlanLayer.h" + +#include +#include +#include + +namespace pcpp +{ + namespace + { + constexpr uint8_t GeneveSupportedVersion = 0; + constexpr size_t GeneveOptionAlignment = 4; + constexpr uint8_t GeneveOptionLengthMask = 0x1f; + constexpr uint8_t GeneveOptionTypeMask = 0x7f; + constexpr uint8_t GeneveOptionCriticalBitMask = 0x80; + constexpr uint8_t GeneveOptionsLengthMask = 0x3f; + constexpr size_t GeneveMaxOptionDataLength = GeneveOptionLengthMask * GeneveOptionAlignment; + constexpr size_t GeneveMaxOptionsLength = GeneveOptionsLengthMask * GeneveOptionAlignment; + constexpr uint8_t BitsPerByte = 8; + constexpr uint32_t ByteMask = 0xff; + + size_t getGeneveOptionTotalSize(const uint8_t* optionData) + { + constexpr size_t OptionLengthFieldOffset = sizeof(geneve_option_header) - 1; + auto optionDataLength = static_cast(optionData[OptionLengthFieldOffset] & GeneveOptionLengthMask) * + GeneveOptionAlignment; + return sizeof(geneve_option_header) + optionDataLength; + } + } // namespace + + bool GeneveOption::canAssign(const uint8_t* optionRawData, size_t optionDataLen) + { + if (optionRawData == nullptr || optionDataLen < sizeof(geneve_option_header)) + return false; + + return getGeneveOptionTotalSize(optionRawData) <= optionDataLen; + } + + uint16_t GeneveOption::getOptionClass() const + { + return be16toh(m_Data->optionClass); + } + + uint8_t GeneveOption::getType() const + { + return static_cast(m_Data->type & GeneveOptionTypeMask); + } + + bool GeneveOption::isCritical() const + { + return (m_Data->type & GeneveOptionCriticalBitMask) != 0; + } + + size_t GeneveOption::getDataSize() const + { + return static_cast(m_Data->length) * GeneveOptionAlignment; + } + + size_t GeneveOption::getTotalSize() const + { + return sizeof(geneve_option_header) + getDataSize(); + } + + uint8_t* GeneveOption::getData() const + { + return reinterpret_cast(m_Data) + sizeof(geneve_option_header); + } + + GeneveOption GeneveOptionIterator::operator*() const + { + return GeneveOption(*reinterpret_cast(m_Current)); + } + + GeneveOptionIterator& GeneveOptionIterator::operator++() + { + if (m_Current == m_End) + return *this; + + size_t remaining = static_cast(m_End - m_Current); + if (!GeneveOption::canAssign(m_Current, remaining)) + { + m_Current = m_End; + return *this; + } + + m_Current += (**this).getTotalSize(); + if (m_Current != m_End && !GeneveOption::canAssign(m_Current, static_cast(m_End - m_Current))) + { + m_Current = m_End; + } + + return *this; + } + + GeneveOptionIterator GeneveOptionIterator::operator++(int) + { + GeneveOptionIterator previous = *this; + ++(*this); + return previous; + } + + GeneveOptionRange::GeneveOptionRange(uint8_t* begin, uint8_t* end) : m_Begin(begin), m_End(end) + { + if (m_Begin == nullptr || m_Begin == m_End || + !GeneveOption::canAssign(m_Begin, static_cast(m_End - m_Begin))) + { + m_Begin = m_End; + } + } + + GeneveOptionIterator GeneveOptionRange::find(uint16_t optionClass, uint8_t optionType) const + { + for (auto iterator = begin(); iterator != end(); ++iterator) + { + GeneveOption option = *iterator; + if (option.getOptionClass() == optionClass && option.getType() == (optionType & GeneveOptionTypeMask)) + return iterator; + } + + return end(); + } + + size_t GeneveOptionRange::size() const + { + size_t count = 0; + for (auto iterator = begin(); iterator != end(); ++iterator) + ++count; + return count; + } + + std::vector GeneveOptionBuilder::build() const + { + if (m_RecValueLen > GeneveMaxOptionDataLength) + return {}; + + constexpr size_t AlignmentMask = GeneveOptionAlignment - 1; + size_t paddedDataLength = (m_RecValueLen + AlignmentMask) & ~AlignmentMask; + + size_t totalLength = sizeof(geneve_option_header) + paddedDataLength; + std::vector optionData(totalLength, 0); + + geneve_option_header header = {}; + header.optionClass = htobe16(m_OptionClass); + header.type = static_cast(m_RecType & GeneveOptionTypeMask); + if (m_Critical) + header.type |= GeneveOptionCriticalBitMask; + header.length = static_cast(paddedDataLength / GeneveOptionAlignment); + memcpy(optionData.data(), &header, sizeof(header)); + if (m_RecValueLen > 0) + memcpy(optionData.data() + sizeof(geneve_option_header), m_RecValue, m_RecValueLen); + + return optionData; + } + + GeneveLayer::GeneveLayer(uint32_t vni, uint16_t protocolType, bool oamFlag) + { + allocData(sizeof(geneve_header)); + m_Protocol = Geneve; + setVNI(vni); + setProtocolType(protocolType); + getGeneveHeader()->oamFlag = oamFlag ? 1 : 0; + } + + bool GeneveLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + if (!canReinterpretAs(data, dataLen)) + return false; + + auto* header = reinterpret_cast(data); + if (header->version != GeneveSupportedVersion) + return false; + + size_t optionsLength = static_cast(header->optionsLength) * GeneveOptionAlignment; + if (optionsLength > dataLen - sizeof(geneve_header)) + return false; + + const uint8_t* option = data + sizeof(geneve_header); + size_t remaining = optionsLength; + while (remaining > 0) + { + if (!GeneveOption::canAssign(option, remaining)) + return false; + + size_t optionLength = getGeneveOptionTotalSize(option); + option += optionLength; + remaining -= optionLength; + } + + return true; + } + + uint32_t GeneveLayer::getVNI() const + { + const uint8_t* vni = getGeneveHeader()->vni; + return (static_cast(vni[0]) << (2 * BitsPerByte)) | (static_cast(vni[1]) << BitsPerByte) | + vni[2]; + } + + void GeneveLayer::setVNI(uint32_t vni) + { + uint8_t* vniData = getGeneveHeader()->vni; + vniData[0] = static_cast((vni >> (2 * BitsPerByte)) & ByteMask); + vniData[1] = static_cast((vni >> BitsPerByte) & ByteMask); + vniData[2] = static_cast(vni & ByteMask); + } + + uint16_t GeneveLayer::getProtocolType() const + { + return be16toh(getGeneveHeader()->protocolType); + } + + void GeneveLayer::setProtocolType(uint16_t protocolType) + { + getGeneveHeader()->protocolType = htobe16(protocolType); + } + + size_t GeneveLayer::getOptionsLength() const + { + if (m_Data == nullptr || m_DataLen < sizeof(geneve_header)) + return 0; + return static_cast(getGeneveHeader()->optionsLength) * GeneveOptionAlignment; + } + + size_t GeneveLayer::getHeaderLen() const + { + if (m_Data == nullptr) + return 0; + if (m_DataLen < sizeof(geneve_header)) + return m_DataLen; + + return (std::min)(m_DataLen, sizeof(geneve_header) + getOptionsLength()); + } + + GeneveOptionRange GeneveLayer::getOptions() const + { + if (m_Data == nullptr || m_DataLen <= sizeof(geneve_header)) + return {}; + + size_t optionsLength = getHeaderLen() - sizeof(geneve_header); + uint8_t* options = m_Data + sizeof(geneve_header); + return GeneveOptionRange(options, options + optionsLength); + } + + size_t GeneveLayer::getOptionCount() const + { + return getOptions().size(); + } + + bool GeneveLayer::addOption(const GeneveOptionBuilder& optionBuilder) + { + std::vector optionData = optionBuilder.build(); + if (optionData.empty()) + { + PCPP_LOG_ERROR("Cannot build GENEVE option"); + return false; + } + + size_t oldOptionsLength = getOptionsLength(); + if (oldOptionsLength + optionData.size() > GeneveMaxOptionsLength) + { + PCPP_LOG_ERROR("GENEVE options exceed the maximum length of 252 bytes"); + return false; + } + + int offset = static_cast(sizeof(geneve_header) + oldOptionsLength); + size_t optionSize = optionData.size(); + if (!extendLayer(offset, optionSize)) + { + PCPP_LOG_ERROR("Could not extend GeneveLayer by " << optionSize << " bytes"); + return false; + } + + memcpy(m_Data + offset, optionData.data(), optionSize); + getGeneveHeader()->optionsLength = + static_cast((oldOptionsLength + optionSize) / GeneveOptionAlignment); + updateCriticalFlag(); + return true; + } + + bool GeneveLayer::removeOption(uint16_t optionClass, uint8_t optionType) + { + GeneveOptionRange options = getOptions(); + GeneveOptionIterator optionIterator = options.find(optionClass, optionType); + if (optionIterator == options.end()) + return false; + GeneveOption option = *optionIterator; + + size_t oldOptionsLength = getOptionsLength(); + size_t optionSize = option.getTotalSize(); + int offset = static_cast(option.getRecordBasePtr() - m_Data); + if (!shortenLayer(offset, optionSize)) + return false; + + getGeneveHeader()->optionsLength = + static_cast((oldOptionsLength - optionSize) / GeneveOptionAlignment); + updateCriticalFlag(); + return true; + } + + bool GeneveLayer::removeAllOptions() + { + size_t optionsLength = getOptionsLength(); + if (optionsLength == 0) + { + getGeneveHeader()->criticalFlag = 0; + return true; + } + + if (!shortenLayer(sizeof(geneve_header), optionsLength)) + return false; + + getGeneveHeader()->optionsLength = 0; + getGeneveHeader()->criticalFlag = 0; + return true; + } + + void GeneveLayer::updateCriticalFlag() + { + getGeneveHeader()->criticalFlag = 0; + for (GeneveOption option : getOptions()) + { + if (option.isCritical()) + { + getGeneveHeader()->criticalFlag = 1; + return; + } + } + } + + void GeneveLayer::parseNextLayer() + { + size_t headerLength = getHeaderLen(); + if (m_DataLen <= headerLength) + return; + + uint8_t* payload = m_Data + headerLength; + size_t payloadLength = m_DataLen - headerLength; + switch (getProtocolType()) + { + case PCPP_ETHERTYPE_IP: + tryConstructNextLayerWithFallback(payload, payloadLength); + break; + case PCPP_ETHERTYPE_IPV6: + tryConstructNextLayerWithFallback(payload, payloadLength); + break; + case PCPP_ETHERTYPE_VLAN: + case PCPP_ETHERTYPE_IEEE_802_1AD: + tryConstructNextLayerWithFallback(payload, payloadLength); + break; + case PCPP_ETHERTYPE_MPLS: + tryConstructNextLayerWithFallback(payload, payloadLength); + break; + case PCPP_ETHERTYPE_ETHBRIDGE: + if (tryConstructNextLayer(payload, payloadLength) == nullptr) + tryConstructNextLayerWithFallback(payload, payloadLength); + break; + default: + constructNextLayer(payload, payloadLength); + break; + } + } + + void GeneveLayer::computeCalculateFields() + { + updateCriticalFlag(); + if (m_NextLayer == nullptr) + return; + + switch (m_NextLayer->getProtocol()) + { + case IPv4: + setProtocolType(PCPP_ETHERTYPE_IP); + break; + case IPv6: + setProtocolType(PCPP_ETHERTYPE_IPV6); + break; + case VLAN: + setProtocolType(PCPP_ETHERTYPE_VLAN); + break; + case MPLS: + setProtocolType(PCPP_ETHERTYPE_MPLS); + break; + case Ethernet: + case EthernetDot3: + setProtocolType(PCPP_ETHERTYPE_ETHBRIDGE); + break; + default: + break; + } + } + + std::string GeneveLayer::toString() const + { + std::ostringstream result; + result << "GENEVE Layer, VNI: " << getVNI() << ", Protocol type: 0x" << std::hex << getProtocolType(); + return result.str(); + } +} // namespace pcpp diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index 33af7a41ae..fdf10f779c 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -10,6 +10,7 @@ #include "DhcpV6Layer.h" #include "DoIpLayer.h" #include "VxlanLayer.h" +#include "GeneveLayer.h" #include "SipLayer.h" #include "RadiusLayer.h" #include "GtpLayer.h" @@ -108,6 +109,10 @@ namespace pcpp { tryConstructNextLayerWithFallback(udpData, udpDataLen); } + else if (GeneveLayer::isGenevePort(portDst)) + { + tryConstructNextLayerWithFallback(udpData, udpDataLen); + } else if (DnsLayer::isDataValid(udpData, udpDataLen) && (DnsLayer::isDnsPort(portDst) || DnsLayer::isDnsPort(portSrc))) { diff --git a/Tests/Packet++Test/CMakeLists.txt b/Tests/Packet++Test/CMakeLists.txt index e366e22cc4..8922cba0e1 100644 --- a/Tests/Packet++Test/CMakeLists.txt +++ b/Tests/Packet++Test/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable( Tests/DoIpTests.cpp Tests/EthAndArpTests.cpp Tests/FtpTests.cpp + Tests/GeneveTests.cpp Tests/GreTests.cpp Tests/GtpTests.cpp Tests/HttpTests.cpp diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index 71eccbeac6..b5e1ff46af 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -17,6 +17,10 @@ PTF_TEST_CASE(QinQ802_1adParse); PTF_TEST_CASE(MplsLayerTest); PTF_TEST_CASE(VxlanParsingAndCreationTest); +// Implemented in GeneveTests.cpp +PTF_TEST_CASE(GeneveParsingAndCreationTest); +PTF_TEST_CASE(GeneveMalformedPacketTest); + // Implemented in IPv4Tests.cpp PTF_TEST_CASE(IPv4PacketCreation); PTF_TEST_CASE(IPv4PacketParsing); diff --git a/Tests/Packet++Test/Tests/GeneveTests.cpp b/Tests/Packet++Test/Tests/GeneveTests.cpp new file mode 100644 index 0000000000..ae5d13cb55 --- /dev/null +++ b/Tests/Packet++Test/Tests/GeneveTests.cpp @@ -0,0 +1,158 @@ +#include "../TestDefinition.h" +#include "EthLayer.h" +#include "GeneveLayer.h" +#include "IPv4Layer.h" +#include "Packet.h" +#include "PayloadLayer.h" +#include "RawPacket.h" +#include "UdpLayer.h" + +#include + +PTF_TEST_CASE(GeneveParsingAndCreationTest) +{ + pcpp::GeneveLayer geneveLayer(0xabcdef, PCPP_ETHERTYPE_ETHBRIDGE, true); + const uint8_t optionData[] = { 1, 2, 3, 4, 5 }; + PTF_ASSERT_TRUE(geneveLayer.addOption(pcpp::GeneveOptionBuilder(0x0102, 3, optionData, sizeof(optionData)))); + pcpp::GeneveOptionRange firstOptions = geneveLayer.getOptions(); + pcpp::GeneveOptionIterator firstOptionIterator = firstOptions.find(0x0102, 3); + PTF_ASSERT_TRUE(firstOptionIterator != firstOptions.end()); + pcpp::GeneveOption firstOption = *firstOptionIterator; + PTF_ASSERT_EQUAL(firstOption.getOptionClass(), 0x0102); + PTF_ASSERT_EQUAL(firstOption.getType(), 3); + PTF_ASSERT_FALSE(firstOption.isCritical()); + PTF_ASSERT_EQUAL(firstOption.getDataSize(), 8); + PTF_ASSERT_BUF_COMPARE(firstOption.getData(), optionData, sizeof(optionData)); + PTF_ASSERT_EQUAL(firstOption.getData()[5], 0); + PTF_ASSERT_EQUAL(firstOption.getData()[6], 0); + PTF_ASSERT_EQUAL(firstOption.getData()[7], 0); + + PTF_ASSERT_TRUE(geneveLayer.addOption(pcpp::GeneveOptionBuilder(0x0102, 4, nullptr, 0, true))); + pcpp::GeneveOptionRange options = geneveLayer.getOptions(); + pcpp::GeneveOptionIterator secondOptionIterator = options.find(0x0102, 4); + PTF_ASSERT_TRUE(secondOptionIterator != options.end()); + pcpp::GeneveOption secondOption = *secondOptionIterator; + PTF_ASSERT_TRUE(secondOption.isCritical()); + PTF_ASSERT_EQUAL(geneveLayer.getOptionsLength(), 16); + PTF_ASSERT_EQUAL(geneveLayer.getHeaderLen(), 24); + PTF_ASSERT_EQUAL(geneveLayer.getOptionCount(), 2); + PTF_ASSERT_EQUAL(geneveLayer.getGeneveHeader()->criticalFlag, 1); + + pcpp::EthLayer outerEth(pcpp::MacAddress("00:11:22:33:44:55"), pcpp::MacAddress("66:77:88:99:aa:bb")); + pcpp::IPv4Layer outerIp(pcpp::IPv4Address("192.0.2.1"), pcpp::IPv4Address("192.0.2.2")); + pcpp::UdpLayer outerUdp(12345, pcpp::GeneveLayer::DefaultPort); + pcpp::EthLayer innerEth(pcpp::MacAddress("10:11:12:13:14:15"), pcpp::MacAddress("20:21:22:23:24:25")); + pcpp::IPv4Layer innerIp(pcpp::IPv4Address("198.51.100.1"), pcpp::IPv4Address("198.51.100.2")); + const uint8_t payloadData[] = { 0xde, 0xad, 0xbe, 0xef }; + pcpp::PayloadLayer payload(payloadData, sizeof(payloadData)); + + pcpp::Packet craftedPacket(128); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerEth)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerIp)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerUdp)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&geneveLayer)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&innerEth)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&innerIp)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&payload)); + craftedPacket.computeCalculateFields(); + + pcpp::RawPacket rawPacket(*craftedPacket.getRawPacket()); + pcpp::Packet parsedPacket(&rawPacket); + pcpp::GeneveLayer* parsedGeneve = parsedPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(parsedGeneve); + PTF_ASSERT_TRUE(parsedPacket.isPacketOfType(pcpp::Geneve)); + PTF_ASSERT_EQUAL(parsedGeneve->getVNI(), 0xabcdef); + PTF_ASSERT_EQUAL(parsedGeneve->getProtocolType(), PCPP_ETHERTYPE_ETHBRIDGE); + PTF_ASSERT_EQUAL(parsedGeneve->getGeneveHeader()->oamFlag, 1); + PTF_ASSERT_EQUAL(parsedGeneve->getGeneveHeader()->criticalFlag, 1); + PTF_ASSERT_EQUAL(parsedGeneve->getOptionCount(), 2); + PTF_ASSERT_EQUAL(parsedGeneve->getHeaderLen(), 24); + + pcpp::GeneveOptionRange parsedOptions = parsedGeneve->getOptions(); + pcpp::GeneveOptionIterator parsedFirstOptionIterator = parsedOptions.begin(); + PTF_ASSERT_TRUE(parsedFirstOptionIterator != parsedOptions.end()); + pcpp::GeneveOption parsedFirstOption = *parsedFirstOptionIterator; + PTF_ASSERT_EQUAL(parsedFirstOption.getOptionClass(), 0x0102); + PTF_ASSERT_EQUAL(parsedFirstOption.getType(), 3); + pcpp::GeneveOptionIterator parsedSecondOptionIterator = parsedFirstOptionIterator; + ++parsedSecondOptionIterator; + PTF_ASSERT_TRUE(parsedSecondOptionIterator != parsedOptions.end()); + pcpp::GeneveOption parsedSecondOption = *parsedSecondOptionIterator; + PTF_ASSERT_TRUE(parsedSecondOption.isCritical()); + ++parsedSecondOptionIterator; + PTF_ASSERT_TRUE(parsedSecondOptionIterator == parsedOptions.end()); + PTF_ASSERT_EQUAL(parsedGeneve->getNextLayer()->getProtocol(), pcpp::Ethernet, enum); + PTF_ASSERT_EQUAL(parsedGeneve->getNextLayer()->getNextLayer()->getProtocol(), pcpp::IPv4, enum); + + size_t packetLength = parsedPacket.getRawPacket()->getRawDataLen(); + PTF_ASSERT_TRUE(parsedGeneve->removeOption(0x0102, 4)); + PTF_ASSERT_EQUAL(parsedPacket.getRawPacket()->getRawDataLen(), packetLength - 4); + PTF_ASSERT_EQUAL(parsedGeneve->getOptionCount(), 1); + PTF_ASSERT_EQUAL(parsedGeneve->getGeneveHeader()->criticalFlag, 0); + PTF_ASSERT_TRUE(parsedGeneve->removeAllOptions()); + PTF_ASSERT_EQUAL(parsedGeneve->getOptionsLength(), 0); + PTF_ASSERT_EQUAL(parsedGeneve->getHeaderLen(), sizeof(pcpp::geneve_header)); + + pcpp::EthLayer outerEth2(pcpp::MacAddress("00:11:22:33:44:55"), pcpp::MacAddress("66:77:88:99:aa:bb")); + pcpp::IPv4Layer outerIp2(pcpp::IPv4Address("192.0.2.1"), pcpp::IPv4Address("192.0.2.2")); + pcpp::UdpLayer outerUdp2(12346, pcpp::GeneveLayer::DefaultPort); + pcpp::GeneveLayer ipGeneveLayer; + pcpp::IPv4Layer directInnerIp(pcpp::IPv4Address("203.0.113.1"), pcpp::IPv4Address("203.0.113.2")); + pcpp::Packet directIpPacket(96); + PTF_ASSERT_TRUE(directIpPacket.addLayer(&outerEth2)); + PTF_ASSERT_TRUE(directIpPacket.addLayer(&outerIp2)); + PTF_ASSERT_TRUE(directIpPacket.addLayer(&outerUdp2)); + PTF_ASSERT_TRUE(directIpPacket.addLayer(&ipGeneveLayer)); + PTF_ASSERT_TRUE(directIpPacket.addLayer(&directInnerIp)); + directIpPacket.computeCalculateFields(); + PTF_ASSERT_EQUAL(ipGeneveLayer.getProtocolType(), PCPP_ETHERTYPE_IP); + + pcpp::RawPacket directIpRawPacket(*directIpPacket.getRawPacket()); + pcpp::Packet parsedDirectIpPacket(&directIpRawPacket); + pcpp::GeneveLayer* parsedIpGeneve = parsedDirectIpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(parsedIpGeneve); + PTF_ASSERT_NOT_NULL(parsedIpGeneve->getNextLayer()); + PTF_ASSERT_EQUAL(parsedIpGeneve->getNextLayer()->getProtocol(), pcpp::IPv4, enum); +} // GeneveParsingAndCreationTest + +PTF_TEST_CASE(GeneveMalformedPacketTest) +{ + const uint8_t truncatedHeader[7] = {}; + PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(truncatedHeader, sizeof(truncatedHeader))); + + uint8_t unsupportedVersion[8] = { 0x40, 0, 0x65, 0x58, 0, 0, 1, 0 }; + PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(unsupportedVersion, sizeof(unsupportedVersion))); + + uint8_t truncatedOptions[12] = { 1, 0, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x03, 1 }; + PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(truncatedOptions, sizeof(truncatedOptions))); + + uint8_t validZeroLengthOption[12] = { 1, 0, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x03, 0 }; + PTF_ASSERT_TRUE(pcpp::GeneveLayer::isDataValid(validZeroLengthOption, sizeof(validZeroLengthOption))); + + pcpp::EthLayer outerEth(pcpp::MacAddress("00:11:22:33:44:55"), pcpp::MacAddress("66:77:88:99:aa:bb")); + pcpp::IPv4Layer outerIp(pcpp::IPv4Address("192.0.2.1"), pcpp::IPv4Address("192.0.2.2")); + pcpp::UdpLayer outerUdp(12345, pcpp::GeneveLayer::DefaultPort); + pcpp::GeneveLayer geneveLayer(1); + pcpp::EthLayer innerEth(pcpp::MacAddress("10:11:12:13:14:15"), pcpp::MacAddress("20:21:22:23:24:25")); + + pcpp::Packet craftedPacket(96); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerEth)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerIp)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&outerUdp)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&geneveLayer)); + PTF_ASSERT_TRUE(craftedPacket.addLayer(&innerEth)); + craftedPacket.computeCalculateFields(); + + const uint8_t* rawData = craftedPacket.getRawPacket()->getRawData(); + std::vector malformedData(rawData, rawData + craftedPacket.getRawPacket()->getRawDataLen()); + constexpr size_t GeneveOffset = sizeof(pcpp::ether_header) + sizeof(pcpp::iphdr) + sizeof(pcpp::udphdr); + malformedData[GeneveOffset] = 0x40; + timeval timestamp = {}; + pcpp::RawPacket malformedRawPacket(malformedData.data(), static_cast(malformedData.size()), timestamp, false); + pcpp::Packet malformedPacket(&malformedRawPacket); + PTF_ASSERT_NULL(malformedPacket.getLayerOfType()); + pcpp::UdpLayer* parsedUdp = malformedPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(parsedUdp); + PTF_ASSERT_NOT_NULL(parsedUdp->getNextLayer()); + PTF_ASSERT_EQUAL(parsedUdp->getNextLayer()->getProtocol(), pcpp::GenericPayload, enum); +} // GeneveMalformedPacketTest diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index c35c696fde..296661828c 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -130,6 +130,8 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(QinQ802_1adParse, "vlan"); PTF_RUN_TEST(MplsLayerTest, "mpls"); PTF_RUN_TEST(VxlanParsingAndCreationTest, "vxlan"); + PTF_RUN_TEST(GeneveParsingAndCreationTest, "geneve"); + PTF_RUN_TEST(GeneveMalformedPacketTest, "geneve"); PTF_RUN_TEST(IPv4PacketCreation, "ipv4"); PTF_RUN_TEST(IPv4PacketParsing, "ipv4"); From e23b8d19d3d0f739eceb1cdd09e0b132105f0e1f Mon Sep 17 00:00:00 2001 From: alacrity-aya Date: Thu, 27 Aug 2026 22:15:58 +0800 Subject: [PATCH 2/4] Fix GENEVE build on macOS --- Packet++/src/GeneveLayer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Packet++/src/GeneveLayer.cpp b/Packet++/src/GeneveLayer.cpp index d322f91a36..f9133f216b 100644 --- a/Packet++/src/GeneveLayer.cpp +++ b/Packet++/src/GeneveLayer.cpp @@ -1,6 +1,7 @@ #define LOG_MODULE PacketLogModuleGeneveLayer #include "GeneveLayer.h" +#include "EndianPortable.h" #include "EthDot3Layer.h" #include "EthLayer.h" #include "IPv4Layer.h" From 2dadf126cf251136584a83478f4fa9fae57b74ec Mon Sep 17 00:00:00 2001 From: alacrity-aya Date: Fri, 28 Aug 2026 22:23:45 +0800 Subject: [PATCH 3/4] Fix GENEVE protocol validation and ARP handling --- Packet++/src/GeneveLayer.cpp | 16 +++++++- Tests/Packet++Test/Tests/GeneveTests.cpp | 47 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Packet++/src/GeneveLayer.cpp b/Packet++/src/GeneveLayer.cpp index f9133f216b..63958f295e 100644 --- a/Packet++/src/GeneveLayer.cpp +++ b/Packet++/src/GeneveLayer.cpp @@ -1,6 +1,7 @@ #define LOG_MODULE PacketLogModuleGeneveLayer #include "GeneveLayer.h" +#include "ArpLayer.h" #include "EndianPortable.h" #include "EthDot3Layer.h" #include "EthLayer.h" @@ -180,8 +181,12 @@ namespace pcpp auto* header = reinterpret_cast(data); if (header->version != GeneveSupportedVersion) return false; + // RFC 8926 Section 3.4 requires Protocol Type to follow the EtherType convention, + // whose valid encodings start at 0x0600. + if (be16toh(header->protocolType) < 0x0600) + return false; - size_t optionsLength = static_cast(header->optionsLength) * GeneveOptionAlignment; + auto optionsLength = static_cast(header->optionsLength) * GeneveOptionAlignment; if (optionsLength > dataLen - sizeof(geneve_header)) return false; @@ -191,6 +196,9 @@ namespace pcpp { if (!GeneveOption::canAssign(option, remaining)) return false; + if ((reinterpret_cast(option)->type & GeneveOptionCriticalBitMask) != 0 && + header->criticalFlag == 0) + return false; size_t optionLength = getGeneveOptionTotalSize(option); option += optionLength; @@ -351,6 +359,9 @@ namespace pcpp case PCPP_ETHERTYPE_IP: tryConstructNextLayerWithFallback(payload, payloadLength); break; + case PCPP_ETHERTYPE_ARP: + tryConstructNextLayerWithFallback(payload, payloadLength); + break; case PCPP_ETHERTYPE_IPV6: tryConstructNextLayerWithFallback(payload, payloadLength); break; @@ -382,6 +393,9 @@ namespace pcpp case IPv4: setProtocolType(PCPP_ETHERTYPE_IP); break; + case ARP: + setProtocolType(PCPP_ETHERTYPE_ARP); + break; case IPv6: setProtocolType(PCPP_ETHERTYPE_IPV6); break; diff --git a/Tests/Packet++Test/Tests/GeneveTests.cpp b/Tests/Packet++Test/Tests/GeneveTests.cpp index ae5d13cb55..d92b7a0314 100644 --- a/Tests/Packet++Test/Tests/GeneveTests.cpp +++ b/Tests/Packet++Test/Tests/GeneveTests.cpp @@ -1,4 +1,5 @@ #include "../TestDefinition.h" +#include "ArpLayer.h" #include "EthLayer.h" #include "GeneveLayer.h" #include "IPv4Layer.h" @@ -113,22 +114,67 @@ PTF_TEST_CASE(GeneveParsingAndCreationTest) PTF_ASSERT_NOT_NULL(parsedIpGeneve); PTF_ASSERT_NOT_NULL(parsedIpGeneve->getNextLayer()); PTF_ASSERT_EQUAL(parsedIpGeneve->getNextLayer()->getProtocol(), pcpp::IPv4, enum); + + pcpp::EthLayer outerEth3(pcpp::MacAddress("00:11:22:33:44:55"), pcpp::MacAddress("66:77:88:99:aa:bb")); + pcpp::IPv4Layer outerIp3(pcpp::IPv4Address("192.0.2.1"), pcpp::IPv4Address("192.0.2.2")); + pcpp::UdpLayer outerUdp3(12347, pcpp::GeneveLayer::DefaultPort); + pcpp::GeneveLayer arpGeneveLayer; + pcpp::ArpLayer directInnerArp(pcpp::ArpRequest( + pcpp::MacAddress("10:11:12:13:14:15"), pcpp::IPv4Address("198.51.100.1"), pcpp::IPv4Address("198.51.100.2"))); + pcpp::Packet directArpPacket(128); + PTF_ASSERT_TRUE(directArpPacket.addLayer(&outerEth3)); + PTF_ASSERT_TRUE(directArpPacket.addLayer(&outerIp3)); + PTF_ASSERT_TRUE(directArpPacket.addLayer(&outerUdp3)); + PTF_ASSERT_TRUE(directArpPacket.addLayer(&arpGeneveLayer)); + PTF_ASSERT_TRUE(directArpPacket.addLayer(&directInnerArp)); + directArpPacket.computeCalculateFields(); + PTF_ASSERT_EQUAL(arpGeneveLayer.getProtocolType(), PCPP_ETHERTYPE_ARP); + + pcpp::RawPacket directArpRawPacket(*directArpPacket.getRawPacket()); + pcpp::Packet parsedDirectArpPacket(&directArpRawPacket); + pcpp::GeneveLayer* parsedArpGeneve = parsedDirectArpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(parsedArpGeneve); + PTF_ASSERT_NOT_NULL(parsedArpGeneve->getNextLayer()); + PTF_ASSERT_EQUAL(parsedArpGeneve->getNextLayer()->getProtocol(), pcpp::ARP, enum); } // GeneveParsingAndCreationTest PTF_TEST_CASE(GeneveMalformedPacketTest) { + // Raw headers below use the on-wire layout: Ver/Opt Len, O/C/Reserved, Protocol Type, VNI, Reserved, + // followed by options encoded as Option Class, Type/Critical, and Length. + // A GENEVE base header is 8 bytes, so this buffer is one byte short. const uint8_t truncatedHeader[7] = {}; PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(truncatedHeader, sizeof(truncatedHeader))); + // Ver=1 is unsupported; Protocol Type=0x6558 (Transparent Ethernet Bridging) and VNI=1 are valid. uint8_t unsupportedVersion[8] = { 0x40, 0, 0x65, 0x58, 0, 0, 1, 0 }; PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(unsupportedVersion, sizeof(unsupportedVersion))); + // Opt Len=1 declares a 4-byte options area, but the option's Length=1 requires 4 more data bytes. uint8_t truncatedOptions[12] = { 1, 0, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x03, 1 }; PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(truncatedOptions, sizeof(truncatedOptions))); + // Opt Len=1 contains one complete option with Class=0x0102, Type=3, and no option data. uint8_t validZeroLengthOption[12] = { 1, 0, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x03, 0 }; PTF_ASSERT_TRUE(pcpp::GeneveLayer::isDataValid(validZeroLengthOption, sizeof(validZeroLengthOption))); + // 0x05ff is immediately below the EtherType range and is therefore not a valid Protocol Type. + uint8_t invalidProtocolType[8] = { 0, 0, 0x05, 0xff, 0, 0, 1, 0 }; + PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(invalidProtocolType, sizeof(invalidProtocolType))); + // 0x0600 is the lower boundary of the EtherType range accepted for Protocol Type. + uint8_t minimumProtocolType[8] = { 0, 0, 0x06, 0, 0, 0, 1, 0 }; + PTF_ASSERT_TRUE(pcpp::GeneveLayer::isDataValid(minimumProtocolType, sizeof(minimumProtocolType))); + + // Option Type=0x83 sets the critical bit for Type=3, but the base header C bit remains clear. + uint8_t criticalOptionWithoutFlag[12] = { 1, 0, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x83, 0 }; + PTF_ASSERT_FALSE(pcpp::GeneveLayer::isDataValid(criticalOptionWithoutFlag, sizeof(criticalOptionWithoutFlag))); + // The same critical option is valid when 0x40 sets the base header C bit. + uint8_t criticalOptionWithFlag[12] = { 1, 0x40, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x83, 0 }; + PTF_ASSERT_TRUE(pcpp::GeneveLayer::isDataValid(criticalOptionWithFlag, sizeof(criticalOptionWithFlag))); + // C=1 without a critical option is accepted because RFC 8926 only mandates the reverse implication. + uint8_t flagWithoutCriticalOption[12] = { 1, 0x40, 0x65, 0x58, 0, 0, 1, 0, 0x01, 0x02, 0x03, 0 }; + PTF_ASSERT_TRUE(pcpp::GeneveLayer::isDataValid(flagWithoutCriticalOption, sizeof(flagWithoutCriticalOption))); + pcpp::EthLayer outerEth(pcpp::MacAddress("00:11:22:33:44:55"), pcpp::MacAddress("66:77:88:99:aa:bb")); pcpp::IPv4Layer outerIp(pcpp::IPv4Address("192.0.2.1"), pcpp::IPv4Address("192.0.2.2")); pcpp::UdpLayer outerUdp(12345, pcpp::GeneveLayer::DefaultPort); @@ -146,6 +192,7 @@ PTF_TEST_CASE(GeneveMalformedPacketTest) const uint8_t* rawData = craftedPacket.getRawPacket()->getRawData(); std::vector malformedData(rawData, rawData + craftedPacket.getRawPacket()->getRawDataLen()); constexpr size_t GeneveOffset = sizeof(pcpp::ether_header) + sizeof(pcpp::iphdr) + sizeof(pcpp::udphdr); + // Set Ver=1 in the serialized GENEVE header to verify that UDP parsing falls back to a generic payload. malformedData[GeneveOffset] = 0x40; timeval timestamp = {}; pcpp::RawPacket malformedRawPacket(malformedData.data(), static_cast(malformedData.size()), timestamp, false); From 78e3b7cbbf4e5956ed2779e707e6e0e4128b281c Mon Sep 17 00:00:00 2001 From: alacrity-aya Date: Tue, 1 Sep 2026 21:59:37 +0800 Subject: [PATCH 4/4] Refine GENEVE API accessors and validation --- Packet++/header/GeneveLayer.h | 113 ++++++++++++++++++++++- Packet++/src/GeneveLayer.cpp | 87 +++++++---------- Tests/Packet++Test/Tests/GeneveTests.cpp | 4 + 3 files changed, 146 insertions(+), 58 deletions(-) diff --git a/Packet++/header/GeneveLayer.h b/Packet++/header/GeneveLayer.h index 5e32a7a84f..716fe225ec 100644 --- a/Packet++/header/GeneveLayer.h +++ b/Packet++/header/GeneveLayer.h @@ -20,6 +20,9 @@ namespace pcpp #pragma pack(push, 1) struct geneve_header { + static constexpr size_t OptionsLengthUnit = 4; + static constexpr size_t MaxOptionsLength = ((1 << 6) - 1) * OptionsLengthUnit; + #if (BYTE_ORDER == LITTLE_ENDIAN) /// Options length in 4-byte units uint8_t optionsLength : 6; @@ -49,6 +52,33 @@ namespace pcpp uint8_t vni[3]; /// Reserved byte uint8_t reserved2; + + /// @return Options length in bytes + size_t getOptionsLength() const + { + return static_cast(optionsLength) * OptionsLengthUnit; + } + + /// @param[in] value Options length in bytes + /// @pre value must be divisible by 4 and no greater than MaxOptionsLength + void setOptionsLength(size_t value) + { + optionsLength = static_cast(value / OptionsLengthUnit); + } + + /// @return The 24-bit virtual network identifier + uint32_t getVNI() const + { + return (static_cast(vni[0]) << 16) | (static_cast(vni[1]) << 8) | vni[2]; + } + + /// @param[in] value The 24-bit virtual network identifier + void setVNI(uint32_t value) + { + vni[0] = static_cast((value >> 16) & 0xff); + vni[1] = static_cast((value >> 8) & 0xff); + vni[2] = static_cast(value & 0xff); + } }; #pragma pack(pop) static_assert(sizeof(geneve_header) == 8, "geneve_header size is not 8 bytes"); @@ -58,8 +88,19 @@ namespace pcpp #pragma pack(push, 1) struct geneve_option_header { + static constexpr size_t DataLengthUnit = 4; + static constexpr size_t MaxDataLength = ((1 << 5) - 1) * DataLengthUnit; + static constexpr uint8_t TypeMask = 0x7f; + static constexpr uint8_t CriticalBitMask = 0x80; + /// Option namespace assigned by IANA uint16_t optionClass; + /// @return Option class in host byte order + uint16_t getOptionClass() const; + + /// @param[in] value Option class in host byte order + void setOptionClass(uint16_t value); + /// Option type. The most significant bit is the critical bit uint8_t type; #if (BYTE_ORDER == LITTLE_ENDIAN) @@ -73,6 +114,60 @@ namespace pcpp /// Option data length in 4-byte units uint8_t length : 5; #endif + + /// @return The 7-bit option type without the critical bit + uint8_t getType() const + { + return extractType(type); + } + + /// @param[in] value A raw option type, optionally including the critical bit + /// @return The 7-bit option type without the critical bit + static uint8_t extractType(uint8_t value) + { + return static_cast(value & TypeMask); + } + + /// Round an option data length up to the next 4-byte boundary + /// @param[in] value Unpadded option data length in bytes + /// @return Option data length rounded up to a multiple of 4 + static size_t alignDataSize(size_t value) + { + constexpr size_t AlignmentMask = DataLengthUnit - 1; + return (value + AlignmentMask) & ~AlignmentMask; + } + + /// @return True if the critical bit is set + bool isCritical() const + { + return (type & CriticalBitMask) != 0; + } + + /// @return Option data length in bytes + size_t getDataSize() const + { + return static_cast(length) * DataLengthUnit; + } + + /// @param[in] value Option data length in bytes + /// @pre value must be divisible by 4 and no greater than MaxDataLength + void setDataSize(size_t value) + { + length = static_cast(value / DataLengthUnit); + } + + /// @return Total option size including its 4-byte header + size_t getTotalSize() const + { + return sizeof(geneve_option_header) + getDataSize(); + } + + /// @param[in] value The 7-bit option type + /// @param[in] critical Whether to set the critical bit + void setType(uint8_t value, bool critical) + { + type = static_cast(extractType(value) | (critical ? CriticalBitMask : 0)); + } }; #pragma pack(pop) static_assert(sizeof(geneve_option_header) == 4, "geneve_option_header size is not 4 bytes"); @@ -207,6 +302,7 @@ namespace pcpp GeneveOptionIterator find(uint16_t optionClass, uint8_t optionType) const; /// @return The number of structurally valid options in this range + /// @note This operation has O(n) time complexity, where n is the number of options. size_t size() const; /// @return True if this range contains no options @@ -254,6 +350,8 @@ namespace pcpp /// @param[in] dataLen Size of the data in bytes /// @param[in] prevLayer A pointer to the previous layer /// @param[in] packet A pointer to the Packet instance where the layer is stored + /// @note This constructor does not validate the input. Use isDataValid() before constructing a standalone + /// parsed layer. GeneveLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) : Layer(data, dataLen, prevLayer, packet, Geneve) {} @@ -281,32 +379,38 @@ namespace pcpp } /// @return A pointer to the fixed GENEVE header + /// @pre The layer must contain a complete fixed GENEVE header geneve_header* getGeneveHeader() const { return reinterpret_cast(m_Data); } /// @return The VNI in host byte order + /// @pre The layer must contain a complete fixed GENEVE header uint32_t getVNI() const; /// Set the VNI. Only the least significant 24 bits are used /// @param[in] vni The VNI to set + /// @pre The layer must contain a complete fixed GENEVE header void setVNI(uint32_t vni); /// @return Encapsulated protocol EtherType in host byte order + /// @pre The layer must contain a complete fixed GENEVE header uint16_t getProtocolType() const; /// Set the encapsulated protocol EtherType /// @param[in] protocolType EtherType in host byte order + /// @pre The layer must contain a complete fixed GENEVE header void setProtocolType(uint16_t protocolType); - /// @return Total options length in bytes + /// @return Total options length in bytes, or zero if the fixed GENEVE header is unavailable or truncated size_t getOptionsLength() const; - /// @return Number of structurally valid options in this layer + /// @return Number of structurally valid options in this layer, or zero if no valid option range is available size_t getOptionCount() const; - /// @return A non-owning range over the options in this layer + /// @return A non-owning range over the options in this layer, or an empty range if the fixed GENEVE header is + /// unavailable or truncated GeneveOptionRange getOptions() const; /// Add an option after all existing options @@ -327,7 +431,8 @@ namespace pcpp /// Parse the encapsulated protocol according to the Protocol Type field void parseNextLayer() override; - /// @return Fixed header plus the declared options length + /// @return Zero if no data is available; the available data length if the fixed header is truncated; otherwise, + /// the fixed header plus the declared options length, capped at the available data length size_t getHeaderLen() const override; /// Update the Protocol Type and Critical flag from the following layer and options diff --git a/Packet++/src/GeneveLayer.cpp b/Packet++/src/GeneveLayer.cpp index 63958f295e..8492464055 100644 --- a/Packet++/src/GeneveLayer.cpp +++ b/Packet++/src/GeneveLayer.cpp @@ -18,59 +18,47 @@ namespace pcpp { - namespace - { - constexpr uint8_t GeneveSupportedVersion = 0; - constexpr size_t GeneveOptionAlignment = 4; - constexpr uint8_t GeneveOptionLengthMask = 0x1f; - constexpr uint8_t GeneveOptionTypeMask = 0x7f; - constexpr uint8_t GeneveOptionCriticalBitMask = 0x80; - constexpr uint8_t GeneveOptionsLengthMask = 0x3f; - constexpr size_t GeneveMaxOptionDataLength = GeneveOptionLengthMask * GeneveOptionAlignment; - constexpr size_t GeneveMaxOptionsLength = GeneveOptionsLengthMask * GeneveOptionAlignment; - constexpr uint8_t BitsPerByte = 8; - constexpr uint32_t ByteMask = 0xff; - - size_t getGeneveOptionTotalSize(const uint8_t* optionData) - { - constexpr size_t OptionLengthFieldOffset = sizeof(geneve_option_header) - 1; - auto optionDataLength = static_cast(optionData[OptionLengthFieldOffset] & GeneveOptionLengthMask) * - GeneveOptionAlignment; - return sizeof(geneve_option_header) + optionDataLength; - } - } // namespace + uint16_t geneve_option_header::getOptionClass() const + { + return be16toh(optionClass); + } + + void geneve_option_header::setOptionClass(uint16_t value) + { + optionClass = htobe16(value); + } bool GeneveOption::canAssign(const uint8_t* optionRawData, size_t optionDataLen) { if (optionRawData == nullptr || optionDataLen < sizeof(geneve_option_header)) return false; - return getGeneveOptionTotalSize(optionRawData) <= optionDataLen; + return reinterpret_cast(optionRawData)->getTotalSize() <= optionDataLen; } uint16_t GeneveOption::getOptionClass() const { - return be16toh(m_Data->optionClass); + return m_Data->getOptionClass(); } uint8_t GeneveOption::getType() const { - return static_cast(m_Data->type & GeneveOptionTypeMask); + return m_Data->getType(); } bool GeneveOption::isCritical() const { - return (m_Data->type & GeneveOptionCriticalBitMask) != 0; + return m_Data->isCritical(); } size_t GeneveOption::getDataSize() const { - return static_cast(m_Data->length) * GeneveOptionAlignment; + return m_Data->getDataSize(); } size_t GeneveOption::getTotalSize() const { - return sizeof(geneve_option_header) + getDataSize(); + return m_Data->getTotalSize(); } uint8_t* GeneveOption::getData() const @@ -125,7 +113,8 @@ namespace pcpp for (auto iterator = begin(); iterator != end(); ++iterator) { GeneveOption option = *iterator; - if (option.getOptionClass() == optionClass && option.getType() == (optionType & GeneveOptionTypeMask)) + if (option.getOptionClass() == optionClass && + option.getType() == geneve_option_header::extractType(optionType)) return iterator; } @@ -142,21 +131,18 @@ namespace pcpp std::vector GeneveOptionBuilder::build() const { - if (m_RecValueLen > GeneveMaxOptionDataLength) + if (m_RecValueLen > geneve_option_header::MaxDataLength) return {}; - constexpr size_t AlignmentMask = GeneveOptionAlignment - 1; - size_t paddedDataLength = (m_RecValueLen + AlignmentMask) & ~AlignmentMask; + size_t paddedDataLength = geneve_option_header::alignDataSize(m_RecValueLen); size_t totalLength = sizeof(geneve_option_header) + paddedDataLength; std::vector optionData(totalLength, 0); geneve_option_header header = {}; - header.optionClass = htobe16(m_OptionClass); - header.type = static_cast(m_RecType & GeneveOptionTypeMask); - if (m_Critical) - header.type |= GeneveOptionCriticalBitMask; - header.length = static_cast(paddedDataLength / GeneveOptionAlignment); + header.setOptionClass(m_OptionClass); + header.setType(m_RecType, m_Critical); + header.setDataSize(paddedDataLength); memcpy(optionData.data(), &header, sizeof(header)); if (m_RecValueLen > 0) memcpy(optionData.data() + sizeof(geneve_option_header), m_RecValue, m_RecValueLen); @@ -179,14 +165,15 @@ namespace pcpp return false; auto* header = reinterpret_cast(data); - if (header->version != GeneveSupportedVersion) + // RFC 8926 defines GENEVE version 0; this implementation supports that version only. + if (header->version != 0) return false; // RFC 8926 Section 3.4 requires Protocol Type to follow the EtherType convention, // whose valid encodings start at 0x0600. if (be16toh(header->protocolType) < 0x0600) return false; - auto optionsLength = static_cast(header->optionsLength) * GeneveOptionAlignment; + auto optionsLength = header->getOptionsLength(); if (optionsLength > dataLen - sizeof(geneve_header)) return false; @@ -196,11 +183,10 @@ namespace pcpp { if (!GeneveOption::canAssign(option, remaining)) return false; - if ((reinterpret_cast(option)->type & GeneveOptionCriticalBitMask) != 0 && - header->criticalFlag == 0) + if (reinterpret_cast(option)->isCritical() && header->criticalFlag == 0) return false; - size_t optionLength = getGeneveOptionTotalSize(option); + size_t optionLength = reinterpret_cast(option)->getTotalSize(); option += optionLength; remaining -= optionLength; } @@ -210,17 +196,12 @@ namespace pcpp uint32_t GeneveLayer::getVNI() const { - const uint8_t* vni = getGeneveHeader()->vni; - return (static_cast(vni[0]) << (2 * BitsPerByte)) | (static_cast(vni[1]) << BitsPerByte) | - vni[2]; + return getGeneveHeader()->getVNI(); } void GeneveLayer::setVNI(uint32_t vni) { - uint8_t* vniData = getGeneveHeader()->vni; - vniData[0] = static_cast((vni >> (2 * BitsPerByte)) & ByteMask); - vniData[1] = static_cast((vni >> BitsPerByte) & ByteMask); - vniData[2] = static_cast(vni & ByteMask); + getGeneveHeader()->setVNI(vni); } uint16_t GeneveLayer::getProtocolType() const @@ -237,7 +218,7 @@ namespace pcpp { if (m_Data == nullptr || m_DataLen < sizeof(geneve_header)) return 0; - return static_cast(getGeneveHeader()->optionsLength) * GeneveOptionAlignment; + return getGeneveHeader()->getOptionsLength(); } size_t GeneveLayer::getHeaderLen() const @@ -275,7 +256,7 @@ namespace pcpp } size_t oldOptionsLength = getOptionsLength(); - if (oldOptionsLength + optionData.size() > GeneveMaxOptionsLength) + if (oldOptionsLength + optionData.size() > geneve_header::MaxOptionsLength) { PCPP_LOG_ERROR("GENEVE options exceed the maximum length of 252 bytes"); return false; @@ -290,8 +271,7 @@ namespace pcpp } memcpy(m_Data + offset, optionData.data(), optionSize); - getGeneveHeader()->optionsLength = - static_cast((oldOptionsLength + optionSize) / GeneveOptionAlignment); + getGeneveHeader()->setOptionsLength(oldOptionsLength + optionSize); updateCriticalFlag(); return true; } @@ -310,8 +290,7 @@ namespace pcpp if (!shortenLayer(offset, optionSize)) return false; - getGeneveHeader()->optionsLength = - static_cast((oldOptionsLength - optionSize) / GeneveOptionAlignment); + getGeneveHeader()->setOptionsLength(oldOptionsLength - optionSize); updateCriticalFlag(); return true; } diff --git a/Tests/Packet++Test/Tests/GeneveTests.cpp b/Tests/Packet++Test/Tests/GeneveTests.cpp index d92b7a0314..dd1bbdbb0a 100644 --- a/Tests/Packet++Test/Tests/GeneveTests.cpp +++ b/Tests/Packet++Test/Tests/GeneveTests.cpp @@ -12,6 +12,10 @@ PTF_TEST_CASE(GeneveParsingAndCreationTest) { + pcpp::geneve_option_header optionHeader = {}; + optionHeader.setOptionClass(0x1234); + PTF_ASSERT_EQUAL(optionHeader.getOptionClass(), 0x1234); + pcpp::GeneveLayer geneveLayer(0xabcdef, PCPP_ETHERTYPE_ETHBRIDGE, true); const uint8_t optionData[] = { 1, 2, 3, 4, 5 }; PTF_ASSERT_TRUE(geneveLayer.addOption(pcpp::GeneveOptionBuilder(0x0102, 3, optionData, sizeof(optionData))));