Skip to content

Commit 2cca845

Browse files
pcap: trust computed IEEE 802.11 FCS metadata
Avoid serializing every captured MPDU and recomputing its FCS when the typed trailer is marked FCS_COMPUTED. Standard INET capture paths calculate that trailer after the final MAC fields are set, so the adapter now treats it as present and trusted while continuing to map FCS_DECLARED_INCORRECT to Radiotap BADFCS. Document the capture design decisions raised during review: typed-chunk requirements for FCS and A-MPDU recognition, aggregate padding and whole-PSDU fallback, Radiotap field layout, adapter registry uniqueness and lifecycle, protocol-adapter record semantics, wireless observation fallback, and PCAPng interface and snaplen handling. Update the Radiotap FCS unit coverage so a deliberately mismatching computed value remains trusted, and remove the obsolete serialization-failure fixture. Verified with the release build and the focused PCAP adapter, A-MPDU, registry, and writer-prefix unit tests.
1 parent 266eecd commit 2cca845

6 files changed

Lines changed: 32 additions & 26 deletions

File tree

src/inet/common/packet/recorder/PcapCaptureAdapterRegistry.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ PcapCaptureAdapterRegistry::~PcapCaptureAdapterRegistry()
1616
delete entry.second;
1717
}
1818

19+
// Each protocol, resolver key, and observation key has one owner per network setup. Silently
20+
// replacing an entry would make capture behavior depend on registration order, so conflicts fail fast.
1921
void PcapCaptureAdapterRegistry::registerProtocolAdapter(const Protocol *protocol, const IPcapCaptureAdapter *adapter)
2022
{
2123
if (protocol == nullptr || adapter == nullptr || protocolAdapters.find(protocol) != protocolAdapters.end()) {
@@ -71,6 +73,8 @@ std::optional<PcapCaptureObservation> PcapCaptureAdapterRegistry::tryCreateObser
7173

7274
PcapCaptureAdapterRegistry& PcapCaptureAdapterRegistry::getInstance()
7375
{
76+
// SharedDataManager scopes the registry to the current network lifecycle, allowing the
77+
// pre-network registration fragments to run again after the previous network is deleted.
7478
static int handle = cSimulationOrSharedDataManager::registerSharedVariableName("inet::PcapCaptureAdapterRegistry::instance");
7579
return getSimulationOrSharedDataManager()->getSharedVariable<PcapCaptureAdapterRegistry>(handle);
7680
}

src/inet/common/packet/recorder/PcapRecorder.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ void PcapRecorder::receiveSignal(cComponent *source, simsignal_t signalID, cObje
180180
auto observation = PcapCaptureAdapterRegistry::getInstance().tryCreateObservation(obj, direction);
181181
if (observation.has_value())
182182
recordPacket(*observation, source);
183+
// Observation adapters are optional enrichers. If none accepts the object, retain the
184+
// generic cPacket path; non-INET packet payloads eventually remain unrecorded as before.
183185
else if (auto packet = dynamic_cast<const cPacket *>(obj))
184186
recordPacket(packet, direction, source);
185187
}
@@ -191,13 +193,19 @@ void PcapRecorder::writePacket(const Protocol *protocol, const PcapCaptureObserv
191193
if (enableProtocolSpecificCaptureAdapters && enableConvertingPackets) {
192194
auto adapter = PcapCaptureAdapterRegistry::getInstance().findProtocolAdapter(protocol);
193195
if (adapter != nullptr) {
196+
// A protocol adapter owns its output link type and complete record layout, so its
197+
// records bypass the generic link-type matching and packet-conversion helpers below.
194198
auto records = adapter->createRecords(observation, frontOffset, backOffset);
195199
for (const auto& record : records) {
196200
auto dataLength = packet->getDataLength() - record.frontOffset - record.backOffset;
201+
// A protocol-specific prefix is meaningful capture data, so a prefix-only record
202+
// is not considered empty even when recordEmptyPackets is false.
197203
if (recordEmptyPackets || !record.getPrefix().empty() || dataLength != b(0)) {
198204
pcapWriter->writePacketWithPrefix(simTime(), record.getPrefix(), packet, record.frontOffset, record.backOffset,
199205
observation.direction, networkInterface, adapter->getLinkType());
200206
numRecorded++;
207+
// Emit once per written record, but retain the original observed packet as the
208+
// signal value; split records such as A-MPDU MPDUs therefore share that value.
201209
emit(packetRecordedSignal, packet);
202210
}
203211
}

src/inet/common/packet/recorder/PcapngWriter.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ void PcapngWriter::writePacketWithPrefix(simtime_t stime, const std::vector<uint
209209
if (!dumpfile)
210210
throw cRuntimeError("Cannot write frame: pcap output file is not open");
211211

212+
// Enhanced Packet Blocks refer to an Interface Description Block, unlike classic PCAP
213+
// records. Fail explicitly when no interface can be resolved instead of dereferencing null.
212214
if (networkInterface == nullptr)
213215
throw cRuntimeError("The interface entry not found for packet");
214216

@@ -224,6 +226,8 @@ void PcapngWriter::writePacketWithPrefix(simtime_t stime, const std::vector<uint
224226

225227
b packetLength = packet->getDataLength() - frontOffset - backOffset;
226228
size_t originalLength = prefix.size() + packetLength.get<B>();
229+
// Advertise and enforce the configured snaplen for PCAPng too. The captured length is
230+
// truncated, while originalPacketLength below retains the complete untruncated record length.
227231
size_t capturedLength = std::min<size_t>(originalLength, snaplen);
228232
uint32_t optionsLength = (4 + 4) + 4;
229233
uint32_t blockTotalLength = 32 + roundUp(capturedLength) + optionsLength;

src/inet/linklayer/ieee80211/pcap/Ieee80211RadiotapPcapCaptureAdapter.cc

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
#include "inet/common/INETMath.h"
1616
#include "inet/common/ProtocolTag_m.h"
17-
#include "inet/common/checksum/Checksum.h"
18-
#include "inet/common/packet/chunk/BytesChunk.h"
1917
#include "inet/common/packet/recorder/PcapCaptureAdapterRegistry.h"
2018
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
2119
#include "inet/physicallayer/wireless/common/contract/packetlevel/INarrowbandSignalAnalogModel.h"
@@ -164,6 +162,8 @@ AmpduParseResult getIeee80211AmpduMpduRanges(const Packet *packet, b frontOffset
164162
if (frontOffset + ieee80211::LENGTH_A_MPDU_SUBFRAME_HEADER > endOffset)
165163
return AmpduParseResult::NOT_AGGREGATE;
166164

165+
// Delimiters are recognized only as typed chunks at their exact boundaries. A serialized
166+
// BytesChunk is deliberately not guessed to be an aggregate; it is captured as one PSDU below.
167167
auto peekDelimiter = [&] (b offset) {
168168
return dynamicPtrCast<const ieee80211::Ieee80211MpduSubframeHeader>(packet->peekDataAt(offset, b(-1), parsingFlags));
169169
};
@@ -193,6 +193,7 @@ AmpduParseResult getIeee80211AmpduMpduRanges(const Packet *packet, b frontOffset
193193
if (offset == endOffset)
194194
return AmpduParseResult::VALID;
195195
auto paddingLength = B((4 - (delimiter->getChunkLength() + mpduLength).get<B>() % 4) % 4);
196+
// This mirrors MpduAggregation::aggregateFrames(): pad between MPDUs, but not after the last one.
196197
// IEEE 802.11-2024, 9.7.1 and 10.12.6 permit exact final-subframe alignment padding for VHT/HE-family PPDUs.
197198
// Without PHY-mode provenance, accept the structurally complete equality case instead of discarding its MPDUs.
198199
if (offset + paddingLength > endOffset)
@@ -219,6 +220,8 @@ FcsMetadata getIeee80211FcsMetadata(const Packet *packet, b frontOffset, b backO
219220
return {};
220221
FcsMetadata metadata;
221222
try {
223+
// A typed trailer is authoritative evidence that the final four octets are an FCS. For a
224+
// raw BytesChunk they may instead be payload, so the adapter does not infer FCS presence.
222225
auto trailer = dynamicPtrCast<const ieee80211::Ieee80211MacTrailer>(packet->peekDataAt(endOffset - B(4), B(4)));
223226
if (trailer == nullptr)
224227
return metadata;
@@ -227,11 +230,11 @@ FcsMetadata getIeee80211FcsMetadata(const Packet *packet, b frontOffset, b backO
227230
case FCS_DECLARED_INCORRECT:
228231
metadata.isBad = true;
229232
break;
230-
case FCS_COMPUTED: {
231-
auto data = packet->peekDataAt<BytesChunk>(frontOffset, endOffset - frontOffset - trailer->getChunkLength());
232-
metadata.isBad = ethernetFcs(data->getBytes()) != trailer->getFcs();
233+
case FCS_COMPUTED:
234+
// On standard INET capture paths, a typed FCS_COMPUTED trailer was produced by INET
235+
// after the final MAC fields were set. Trust it instead of serializing the MPDU and
236+
// repeating the linear-time FCS calculation solely for packet capture.
233237
break;
234-
}
235238
case FCS_DECLARED_CORRECT:
236239
default:
237240
break;
@@ -338,6 +341,8 @@ RadiotapPpduFields extractRadiotapPpduFields(const Packet *packet, Direction dir
338341

339342
std::vector<uint8_t> serializeRadiotapHeader(const RadiotapPpduFields& fields, const RadiotapRecordMetadata& metadata)
340343
{
344+
// Fields are appended in increasing present-bit order. Padding is relative to the beginning
345+
// of this buffer, which already contains the fixed eight-octet Radiotap header.
341346
uint32_t present = 0;
342347
auto setPresentBit = [&] (RadiotapPresentBit bit) { present |= 1U << bit; };
343348
std::vector<uint8_t> bytes(8, 0);
@@ -482,6 +487,8 @@ std::vector<PcapCaptureRecord> Ieee80211RadiotapPcapCaptureAdapter::createRecord
482487
return records;
483488
}
484489

490+
// Malformed aggregates and delimiter-only input still represent an observed wireless frame.
491+
// Preserve it as one whole-PSDU record instead of silently producing no capture records.
485492
RadiotapRecordMetadata metadata;
486493
auto fcsMetadata = getIeee80211FcsMetadata(packet, frontOffset, backOffset);
487494
metadata.hasFcs = fcsMetadata.isPresent;

src/inet/physicallayer/wireless/common/pcap/WirelessPcapCaptureObservationAdapter.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ std::optional<PcapCaptureObservation> WirelessPcapCaptureObservationAdapter::try
2020
{
2121
if (auto signal = dynamic_cast<const Signal *>(object)) {
2222
auto packet = dynamic_cast<const Packet *>(signal->getEncapsulatedPacket());
23+
// Returning nullopt keeps the recorder's generic cPacket fallback available. If the
24+
// encapsulated object is not an INET Packet, that fallback ignores it as before.
2325
return packet != nullptr ? std::optional<PcapCaptureObservation>(PcapCaptureObservation(packet, direction)) : std::nullopt;
2426
}
2527
else if (auto transmission = dynamic_cast<const ITransmission *>(object))

tests/unit/PcapRecorderRadiotapHtVht_1.test

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Test legacy Rate, HT MCS, VHT, and FCS Radiotap fields without fabricating unsup
33

44
%includes:
55
#include "inet/common/packet/chunk/BytesChunk.h"
6-
#include "inet/common/packet/chunk/FieldsChunk.h"
76
#include "inet/common/ProtocolTag_m.h"
87
#include "inet/common/checksum/Checksum.h"
98
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
@@ -22,16 +21,6 @@ using namespace inet::physicallayer;
2221

2322
#define REQUIRE(...) do { if (!(__VA_ARGS__)) throw cRuntimeError("REQUIRE failed at line %d: %s", __LINE__, #__VA_ARGS__); } while (false)
2423

25-
class UnserializableFieldsChunk : public FieldsChunk
26-
{
27-
public:
28-
UnserializableFieldsChunk() { setChunkLength(B(4)); }
29-
UnserializableFieldsChunk(const UnserializableFieldsChunk& other) = default;
30-
31-
virtual UnserializableFieldsChunk *dup() const override { return new UnserializableFieldsChunk(*this); }
32-
virtual const Ptr<Chunk> dupShared() const override { return makeShared<UnserializableFieldsChunk>(*this); }
33-
};
34-
3524
static uint16_t readUint16(const std::vector<uint8_t>& bytes, size_t offset)
3625
{
3726
return bytes.at(offset) | bytes.at(offset + 1) << 8;
@@ -157,7 +146,7 @@ mismatchedTrailer->setFcsMode(FCS_COMPUTED);
157146
mismatchedTrailer->setFcs(ethernetFcs(fcsPayload) ^ 1);
158147
computedMismatch.insertAtBack(mismatchedTrailer);
159148
auto computedMismatchHeader = createRadiotapHeader(adapter, computedMismatch);
160-
REQUIRE(computedMismatchHeader.at(8) == 0x50); // typed FCS is present and authoritatively mismatches
149+
REQUIRE(computedMismatchHeader.at(8) == 0x10); // computed FCS is present and trusted without verification
161150

162151
Packet genericBitError("genericBitError");
163152
genericBitError.insertAtBack(makeShared<BytesChunk>(fcsPayload));
@@ -177,14 +166,6 @@ declaredIncorrect.insertAtBack(incorrectTrailer);
177166
auto declaredIncorrectHeader = createRadiotapHeader(adapter, declaredIncorrect);
178167
REQUIRE(declaredIncorrectHeader.at(8) == 0x50);
179168

180-
Packet unserializablePayload("unserializablePayload");
181-
unserializablePayload.insertAtBack(makeShared<UnserializableFieldsChunk>());
182-
auto unserializablePayloadTrailer = makeShared<Ieee80211MacTrailer>();
183-
unserializablePayloadTrailer->setFcsMode(FCS_COMPUTED);
184-
unserializablePayload.insertAtBack(unserializablePayloadTrailer);
185-
auto unserializablePayloadHeader = createRadiotapHeader(adapter, unserializablePayload);
186-
REQUIRE(unserializablePayloadHeader.at(8) == 0x10); // typed FCS remains present when payload comparison fails
187-
188169
EV << "Legacy, HT, VHT, and FCS Radiotap fields tested successfully.\n";
189170

190171
%contains: stdout

0 commit comments

Comments
 (0)