Skip to content

Add GENEVE protocol layer - #2239

Open
alacrity-aya wants to merge 6 commits into
seladb:devfrom
alacrity-aya:feature/geneve
Open

alacrity-aya wants to merge 6 commits into
seladb:devfrom
alacrity-aya:feature/geneve

Conversation

@alacrity-aya

@alacrity-aya alacrity-aya commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add parsing, validation, crafting, and editing support for GENEVE packets.

Features

  • Add GeneveLayer parsing and packet creation support.
  • Add accessors for VNI and encapsulated protocol EtherType.
  • Add endian-safe accessors for GENEVE option classes and fields.
  • Add a non-owning range and iterator interface for GENEVE options.
  • Support adding, finding, iterating, and removing GENEVE options.
  • Detect GENEVE traffic on UDP destination port 6081.
  • Parse encapsulated Ethernet II, IEEE 802.3, IPv4, IPv6, ARP, VLAN, and MPLS payloads.
  • Validate GENEVE version, EtherType protocol values, option lengths, critical flags, and truncated packets.
  • Add tests for parsing, packet creation, option editing, ARP payloads, and malformed packets.

API Example

pcpp::GeneveLayer geneveLayer(
    0x123456,                 // 24-bit VNI
    PCPP_ETHERTYPE_IPV4,     // Encapsulated protocol
    true                      // OAM flag
);

const uint8_t optionData[] = {1, 2, 3, 4};

geneveLayer.addOption(
    pcpp::GeneveOptionBuilder(
        0x0102,               // Option class
        0x03,                 // Option type
        optionData,
        sizeof(optionData),
        true                  // Critical option
    )
);

geneveLayer.setVNI(0xabcdef);

for (const auto& option : geneveLayer.getOptions())
{
    std::cout << "Class: " << option.getOptionClass()
              << ", Type: " << static_cast<int>(option.getType())
              << ", Critical: " << option.isCritical()
              << ", Data size: " << option.getDataSize()
              << std::endl;
}

auto option = geneveLayer.getOptions().find(0x0102, 0x03);
if (option != geneveLayer.getOptions().end())
    geneveLayer.removeOption(0x0102, 0x03);

Validation

Malformed packets are rejected when they contain:

  • Unsupported GENEVE versions.
  • Protocol values outside the EtherType range.
  • Options exceeding the declared options area.
  • Invalid option lengths.
  • Critical options without the base GENEVE critical flag.

Notes

Partially addresses #1712.

TODO

missing translation

@codecov

codecov Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.66%. Comparing base (ad344a8) to head (f37ce94).

Files with missing lines Patch % Lines
Packet++/src/GeneveLayer.cpp 79.66% 60 Missing ⚠️
Packet++/header/GeneveLayer.h 97.93% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #2239      +/-   ##
==========================================
+ Coverage   82.61%   82.66%   +0.05%     
==========================================
  Files         339      342       +3     
  Lines       61875    62425     +550     
  Branches    13036    13114      +78     
==========================================
+ Hits        51115    51602     +487     
- Misses       9877     9948      +71     
+ Partials      883      875       -8     
Flag Coverage Δ
23.11.6 7.04% <0.26%> (-0.05%) ⬇️
24.11.5 7.03% <0.26%> (-0.05%) ⬇️
25.11.1 7.04% <0.26%> (-0.07%) ⬇️
alpine320 76.59% <77.51%> (+<0.01%) ⬆️
fedora42 76.13% <76.89%> (-0.01%) ⬇️
macos-15 82.33% <82.48%> (+<0.01%) ⬆️
macos-26 82.34% <82.48%> (+<0.01%) ⬆️
macos-26-intel ?
mingw32 70.93% <59.89%> (-0.15%) ⬇️
mingw64 70.48% <60.10%> (-0.04%) ⬇️
npcap ?
rhel94 75.95% <76.97%> (+0.02%) ⬆️
ubuntu2204 75.96% <77.24%> (-0.02%) ⬇️
ubuntu2404 76.26% <77.24%> (+<0.01%) ⬆️
ubuntu2604 76.22% <76.81%> (+<0.01%) ⬆️
ubuntu2604-arm64 76.09% <76.63%> (+0.02%) ⬆️
ubuntu2604-icpx 59.08% <67.52%> (+0.07%) ⬆️
unittest 82.66% <88.88%> (+0.05%) ⬆️
windows-2022 85.44% <84.16%> (+0.09%) ⬆️
windows-2025 85.18% <84.16%> (+0.09%) ⬆️
winpcap 85.49% <84.16%> (+0.20%) ⬆️
xdp 53.89% <77.24%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alacrity-aya
alacrity-aya marked this pull request as ready for review September 1, 2026 14:26
@alacrity-aya
alacrity-aya requested a review from seladb as a code owner September 1, 2026 14:26
Comment on lines +262 to +263
/// GENEVE protocol
const ProtocolType Geneve = 65;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can you move it below QUICv1 to keep the numerical order?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can you please update the README.md file and all of its translations?

Comment on lines +57 to +81
size_t getOptionsLength() const
{
return static_cast<size_t>(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<uint8_t>(value / OptionsLengthUnit);
}

/// @return The 24-bit virtual network identifier
uint32_t getVNI() const
{
return (static_cast<uint32_t>(vni[0]) << 16) | (static_cast<uint32_t>(vni[1]) << 8) | vni[2];
}

/// @param[in] value The 24-bit virtual network identifier
void setVNI(uint32_t value)
{
vni[0] = static_cast<uint8_t>((value >> 16) & 0xff);
vni[1] = static_cast<uint8_t>((value >> 8) & 0xff);
vni[2] = static_cast<uint8_t>(value & 0xff);
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We usually keep the struct simple and keep these methods only on the layer class which is the main interface for the protocol. Moreover, we can keep the struct private inside GeneveLayer.

Same goes for geneve_option_header that we can keep as private inside GeneveOption and move the struct's methods to the class.


uint16_t GeneveLayer::getProtocolType() const
{
return be16toh(getGeneveHeader()->protocolType);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We have wrapper methods in SystemUtils.h so we don't need to include "EndianPortable.h" directly

Comment on lines +219 to +220
if (m_Data == nullptr || m_DataLen < sizeof(geneve_header))
return 0;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This check is probably not needed because PcapPlusPlus or users shouldn't be able to generate a malformed layer.

Ditto in getHeaderLen() and in getOptions()

/// @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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The pattern we usually use in PcapPlusPlus is getFirstOption() and getNextOption(option) directly on the layer. We usually also expose getOption(type) on the layer. This approach requires much less code (which makes it simpler) and achieves the same goals

Comment on lines +330 to +332
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)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'd vote for a "builder" class only if we have multiple c'tors with different types of data. Currently there's only one c'tor that accepts a byte array. If we plan to keep it like this we can probably move these parameters to addOption() and build it there directly


const uint8_t* option = data + sizeof(geneve_header);
size_t remaining = optionsLength;
while (remaining > 0)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I don't think we need to parse all the options inside of isDataValid() which is part of the fast path. Instead, the methods to parse and return the options should take into account that the data might be malformed

/// @return True if all options were removed
bool removeAllOptions();

/// Parse the encapsulated protocol according to the Protocol Type field

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'd mention which layers we support parsing as the next layer

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This tests is written in an older style that we don't use anymore. I'd propose a few changes:

  1. Separate the parsing, creation and update to different tests
  2. For the parsing test, get a sample pcap with real packet(s), create .dat files for each packet using Wireshark's "Copy as a hex stream" (see the screenshot), read them inside the test and make sure the packet is parsed correctly. You can see other tests as references, try to look at newer tests that were written more recently
  3. We use a "sub-test" convention to separate different things in the same test. You can look at QuicTests.cpp as an example
  4. Try to make sure you cover as much as the code as possible. You can look at the Codecov report to see which lines and use-cases are covered
Image

@alacrity-aya

Copy link
Copy Markdown
Contributor Author

@seladb Thanks for the review! I'm a little tied up at the moment, but I'll update the PR as soon as I have a free slot over the next few days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants