From 69d4282a3f854398568b1754192ca87de6b7eea9 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Wed, 11 Mar 2026 16:01:32 -0500 Subject: [PATCH] [nexus] update packet capture to pcapng format (#12660) This commit updates the Nexus test framework to use the pcapng format for packet capture instead of the legacy pcap format. The pcapng format provides several advantages over legacy pcap, most importantly the ability to support multiple interface captures within a single file. This change prepares the Nexus framework for more comprehensive border router testing, where capturing traffic from both the Thread (802.15.4) and infrastructure (Ethernet) interfaces simultaneously is required. Changes: - Implemented Section Header Block (SHB) and Interface Description Block (IDB) in Pcap::Open. - Updated Pcap::WriteFrame to use Enhanced Packet Block (EPB). - Added proper 32-bit alignment padding for EPB records as required by the pcapng specification. - Updated the test runner script to use the .pcapng extension. --- tests/nexus/platform/nexus_pcap.cpp | 93 +++++++++++++++++++++-------- tests/nexus/platform/nexus_pcap.hpp | 19 +++--- tests/nexus/run_nexus_tests.sh | 2 +- 3 files changed, 80 insertions(+), 34 deletions(-) diff --git a/tests/nexus/platform/nexus_pcap.cpp b/tests/nexus/platform/nexus_pcap.cpp index 73380433e..230f91a09 100644 --- a/tests/nexus/platform/nexus_pcap.cpp +++ b/tests/nexus/platform/nexus_pcap.cpp @@ -44,30 +44,51 @@ Pcap::~Pcap(void) { Close(); } void Pcap::Open(const char *aFilename) { OT_TOOL_PACKED_BEGIN - struct PcapHeader + struct Shb { - uint32_t mMagicNumber; + uint32_t mBlockType; + uint32_t mBlockTotalLength; + uint32_t mByteOrderMagic; uint16_t mVersionMajor; uint16_t mVersionMinor; - int32_t mThisZone; - uint32_t mSigFigs; + uint64_t mSectionLength; + uint32_t mBlockTotalLength2; + } OT_TOOL_PACKED_END shb; + + OT_TOOL_PACKED_BEGIN + struct Idb + { + uint32_t mBlockType; + uint32_t mBlockTotalLength; + uint16_t mLinkType; + uint16_t mReserved; uint32_t mSnapLen; - uint32_t mNetwork; - } OT_TOOL_PACKED_END header; + uint32_t mBlockTotalLength2; + } OT_TOOL_PACKED_END idb; Close(); mFile = fopen(aFilename, "wb"); VerifyOrExit(mFile != nullptr); - ClearAllBytes(header); - header.mMagicNumber = LittleEndian::HostSwap(kPcapMagicNumber); - header.mVersionMajor = LittleEndian::HostSwap(kPcapVersionMajor); - header.mVersionMinor = LittleEndian::HostSwap(kPcapVersionMinor); - header.mSnapLen = LittleEndian::HostSwap(kPcapSnapLen); - header.mNetwork = LittleEndian::HostSwap(kPcapDlt154Tap); + ClearAllBytes(shb); + shb.mBlockType = LittleEndian::HostSwap(kPcapngShbType); + shb.mBlockTotalLength = LittleEndian::HostSwap(static_cast(sizeof(shb))); + shb.mByteOrderMagic = LittleEndian::HostSwap(kPcapngByteOrderMagic); + shb.mVersionMajor = LittleEndian::HostSwap(kPcapngVersionMajor); + shb.mVersionMinor = LittleEndian::HostSwap(kPcapngVersionMinor); + shb.mSectionLength = 0xffffffffffffffffULL; + shb.mBlockTotalLength2 = shb.mBlockTotalLength; + VerifyOrExit(fwrite(&shb, sizeof(shb), 1, mFile) == 1, Close()); + + ClearAllBytes(idb); + idb.mBlockType = LittleEndian::HostSwap(kPcapngIdbType); + idb.mBlockTotalLength = LittleEndian::HostSwap(static_cast(sizeof(idb))); + idb.mLinkType = LittleEndian::HostSwap(static_cast(kPcapngLinkTypeIeee802154)); + idb.mSnapLen = LittleEndian::HostSwap(kPcapngSnapLen); + idb.mBlockTotalLength2 = idb.mBlockTotalLength; + VerifyOrExit(fwrite(&idb, sizeof(idb), 1, mFile) == 1, Close()); - VerifyOrExit(fwrite(&header, sizeof(header), 1, mFile) == 1, Close()); VerifyOrExit(fflush(mFile) == 0, Close()); exit: @@ -88,13 +109,16 @@ exit: void Pcap::WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs) { OT_TOOL_PACKED_BEGIN - struct PcapRecordHeader + struct Epb { - uint32_t mTsSec; - uint32_t mTsUsec; - uint32_t mInclLen; - uint32_t mOrigLen; - } OT_TOOL_PACKED_END recordHeader; + uint32_t mBlockType; + uint32_t mBlockTotalLength; + uint32_t mInterfaceId; + uint32_t mTimestampHigh; + uint32_t mTimestampLow; + uint32_t mCapturedLen; + uint32_t mOriginalLen; + } OT_TOOL_PACKED_END epb; OT_TOOL_PACKED_BEGIN struct TapHeader @@ -104,6 +128,10 @@ void Pcap::WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs) } OT_TOOL_PACKED_END tapHeader; uint32_t tapLength; + uint32_t packetLen; + uint32_t paddedLen; + uint32_t blockTotalLength; + uint32_t padding = 0; VerifyOrExit(mFile != nullptr); @@ -112,12 +140,19 @@ void Pcap::WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs) // FCS TLV: Type(2), Length(2), Value(1), Padding(3) -> Total 8 bytes. Value length = 1. // Channel TLV: Type(2), Length(2), Channel(2), Page(1), Padding(1) -> Total 8 bytes. Value length = 3. - ClearAllBytes(recordHeader); - recordHeader.mTsSec = LittleEndian::HostSwap(static_cast(aTimeUs / 1000000)); - recordHeader.mTsUsec = LittleEndian::HostSwap(static_cast(aTimeUs % 1000000)); - recordHeader.mInclLen = LittleEndian::HostSwap(static_cast(tapLength + aFrame.mLength)); - recordHeader.mOrigLen = recordHeader.mInclLen; - VerifyOrExit(fwrite(&recordHeader, sizeof(recordHeader), 1, mFile) == 1, Close()); + packetLen = tapLength + aFrame.mLength; + paddedLen = (packetLen + 3) & ~3u; + blockTotalLength = sizeof(epb) + paddedLen + sizeof(uint32_t); + + ClearAllBytes(epb); + epb.mBlockType = LittleEndian::HostSwap(kPcapngEpbType); + epb.mBlockTotalLength = LittleEndian::HostSwap(blockTotalLength); + epb.mInterfaceId = 0; + epb.mTimestampHigh = LittleEndian::HostSwap(static_cast(aTimeUs >> 32)); + epb.mTimestampLow = LittleEndian::HostSwap(static_cast(aTimeUs & 0xffffffff)); + epb.mCapturedLen = LittleEndian::HostSwap(packetLen); + epb.mOriginalLen = LittleEndian::HostSwap(packetLen); + VerifyOrExit(fwrite(&epb, sizeof(epb), 1, mFile) == 1, Close()); ClearAllBytes(tapHeader); tapHeader.mVersion = LittleEndian::HostSwap(static_cast(kTapVersion)); @@ -142,6 +177,14 @@ void Pcap::WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs) VerifyOrExit(fwrite(channelTlv, sizeof(channelTlv), 1, mFile) == 1, Close()); VerifyOrExit(fwrite(aFrame.mPsdu, aFrame.mLength, 1, mFile) == 1, Close()); + + if (paddedLen > packetLen) + { + VerifyOrExit(fwrite(&padding, paddedLen - packetLen, 1, mFile) == 1, Close()); + } + + VerifyOrExit(fwrite(&epb.mBlockTotalLength, sizeof(epb.mBlockTotalLength), 1, mFile) == 1, Close()); + VerifyOrExit(fflush(mFile) == 0, Close()); exit: diff --git a/tests/nexus/platform/nexus_pcap.hpp b/tests/nexus/platform/nexus_pcap.hpp index 128681fcc..201ba7b72 100644 --- a/tests/nexus/platform/nexus_pcap.hpp +++ b/tests/nexus/platform/nexus_pcap.hpp @@ -44,19 +44,19 @@ public: ~Pcap(void); /** - * Opens a pcap file. + * Opens a pcapng file. * * @param[in] aFilename The filename to open. */ void Open(const char *aFilename); /** - * Closes the pcap file. + * Closes the pcapng file. */ void Close(void); /** - * Writes a frame to the pcap file. + * Writes a frame to the pcapng file. * * @param[in] aFrame The frame to write. * @param[in] aTimeUs The timestamp in microseconds. @@ -64,11 +64,14 @@ public: void WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs); private: - static constexpr uint32_t kPcapMagicNumber = 0xa1b2c3d4; - static constexpr uint16_t kPcapVersionMajor = 2; - static constexpr uint16_t kPcapVersionMinor = 4; - static constexpr uint32_t kPcapSnapLen = 65535; - static constexpr uint32_t kPcapDlt154Tap = 283; + static constexpr uint32_t kPcapngShbType = 0x0a0d0d0a; + static constexpr uint32_t kPcapngByteOrderMagic = 0x1a2b3c4d; + static constexpr uint16_t kPcapngVersionMajor = 1; + static constexpr uint16_t kPcapngVersionMinor = 0; + static constexpr uint32_t kPcapngIdbType = 0x00000001; + static constexpr uint32_t kPcapngEpbType = 0x00000006; + static constexpr uint32_t kPcapngSnapLen = 65535; + static constexpr uint32_t kPcapngLinkTypeIeee802154 = 283; // DLT_IEEE802_15_4_TAP static constexpr uint8_t kTapVersion = 0; diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 3a33139e1..46cb86179 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -194,7 +194,7 @@ run_test() local test_name="nexus_${test_base}" local json_file="test_${test_full}.json" - local pcap_file="test_${test_full}.pcap" + local pcap_file="test_${test_full}.pcapng" local verify_script="${REPO_ROOT}/tests/nexus/verify_${test_base}.py" local nexus_bin="${NEXUS_BIN_DIR}/${test_name}"