Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .oppfeatures
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,7 @@
inet.physicallayer.wireless.common.neighborcache
inet.physicallayer.wireless.common.obstacleloss
inet.physicallayer.wireless.common.pathloss
inet.physicallayer.wireless.common.pcap
inet.physicallayer.wireless.common.propagation
inet.physicallayer.wireless.common.radio
inet.physicallayer.wireless.common.signal
Expand Down
17 changes: 17 additions & 0 deletions WHATSNEW
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ Notable backward incompatible changes are the following:
counted arrivals or summed bytes reported twice the true value and now reports
the true one.

4. PCAP writer contract and PCAPng capture limits

Custom IPcapWriter implementations must implement writePacketWithPrefix(),
which writes capture metadata followed by the selected packet bytes. See
the migration guide for the signature and length semantics.

PcapngWriter now obeys finite snaplen limits while retaining the original
record length. A zero PCAPng snaplen remains unlimited.

Notable backward compatible changes are the following:

1. IEEE 802.11 per-station rate statistics
Expand Down Expand Up @@ -119,6 +128,14 @@ Notable backward compatible changes are the following:
Those two tags now survive the measurement.


6. Opt-in IEEE 802.11 Radiotap capture

PcapRecorder can use registered protocol-specific capture adapters to write
Radiotap metadata for legacy, HT and VHT SU frames. Enable
enableProtocolSpecificCaptureAdapters together with enableConvertingPackets.
Bare IEEE 802.11 (link type 105) remains the default. Typed A-MPDUs are split
into MPDU records, and intact imported frames retain their FCS indication.

INET-4.7 (July 2026) — feature release
--------------------------------------

Expand Down
26 changes: 26 additions & 0 deletions doc/src/migration-guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ Migrating Code from INET 3.x
============================
Release: |release|

Migrating PCAP Writers
----------------------

``IPcapWriter`` now requires ``writePacketWithPrefix()``. Custom implementations
must implement this pure virtual method, or derive from a built-in writer that
implements it. Its signature is:

.. code-block:: c++

void writePacketWithPrefix(simtime_t time, const std::vector<uint8_t>& prefix,
const Packet *packet, b frontOffset, b backOffset, Direction direction,
NetworkInterface *ie, PcapLinkType linkType) override;

Write the prefix followed by the selected packet range. Count both parts in
the original record length and apply the capture length limit to their combined
length. Do not modify or take ownership of the packet. The existing
``writePacket()`` entry point remains available for records without a prefix.

PCAPng Capture Limits
---------------------

``PcapngWriter`` now honors the configured ``snaplen``. Captures exceeding a
finite limit are truncated while retaining their original length; zero means
unlimited for PCAPng. Set a sufficiently large limit or zero if existing
PCAPng consumers require complete records.

IEEE 802.11 Beacon and Probe Response Fields
------------------------------------------

Expand Down
33 changes: 33 additions & 0 deletions doc/src/users-guide/ch-collecting-results.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,39 @@ protocol work and verification rather than merely changing recorder output, so
they are part of the simulation configuration and should be held constant when
comparing runs.

IEEE 802.11 Radiotap captures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

IEEE 802.11 captures use bare MAC frames (link type 105) by default. To include
Radiotap metadata (link type 127), enable protocol-specific capture adapters:

.. code-block:: ini

*.host.numPcapRecorders = 1
*.host.pcapRecorder[0].pcapFile = "wlan.pcap"
*.host.pcapRecorder[0].fileFormat = "pcap"
*.host.pcapRecorder[0].dumpProtocols = "ieee80211mac"
*.host.pcapRecorder[0].enableProtocolSpecificCaptureAdapters = true
*.host.pcapRecorder[0].enableConvertingPackets = true
*.host.wlan[*].mac.fcsMode = "computed"

Both adapter and conversion options must be enabled. PCAPng also supports
Radiotap; use it when a file contains multiple interfaces or link types.
The capture length limit includes the Radiotap header.

The adapter records available legacy rate and short preamble, HT MCS, VHT SU,
channel, power and direction information. Metadata depends on the observed signal and PHY model;
unavailable fields are omitted. HE and EHT metadata are not supported.
FCS presence is taken from the selected frame's typed trailer or, for an intact
MAC frame imported from a capture, its FCS indication. An explicit indication
of absence takes precedence over a trailer. FCS indications are not applied to
selected subranges or individual members of an aggregate.

Recognized typed A-MPDUs produce one record per MPDU, without delimiters and
padding. Inbound records include A-MPDU status. Unparseable aggregates are
preserved as one whole-PSDU record; an analyzer may not decode that fallback as
a MAC frame. Raw bytes are not guessed to be an aggregate.

.. _ug:sec:results:recording-routing-tables:

Recording Routing Tables
Expand Down
65 changes: 65 additions & 0 deletions src/inet/common/packet/recorder/IPcapCaptureAdapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// Copyright (C) 2026 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

#ifndef __INET_IPCAPCAPTUREADAPTER_H
#define __INET_IPCAPCAPTUREADAPTER_H

#include <optional>
#include <vector>

#include "inet/common/DirectionTag_m.h"
#include "inet/common/IPrintableObject.h"
#include "inet/common/packet/Packet.h"
#include "inet/common/packet/recorder/IPcapWriter.h"

namespace inet {

class INET_API PcapCaptureObservation
{
public:
const Packet *const packet;
const Direction direction;
const IPrintableObject *const transmission;
const IPrintableObject *const reception;

PcapCaptureObservation(const Packet *packet, Direction direction, const IPrintableObject *transmission = nullptr, const IPrintableObject *reception = nullptr) :
packet(packet), direction(direction), transmission(transmission), reception(reception) {}
};

class INET_API PcapCaptureRecord
{
protected:
std::vector<uint8_t> prefix;

public:
const b frontOffset;
const b backOffset;

PcapCaptureRecord(b frontOffset, b backOffset, std::vector<uint8_t> prefix = {}) :
prefix(std::move(prefix)), frontOffset(frontOffset), backOffset(backOffset) {}

const std::vector<uint8_t>& getPrefix() const { return prefix; }
};

class INET_API IPcapCaptureAdapter
{
public:
virtual ~IPcapCaptureAdapter() {}
virtual PcapLinkType getLinkType() const = 0;
virtual std::optional<std::pair<b, b>> tryResolvePacket(const Packet *, b, b) const = 0;
virtual std::vector<PcapCaptureRecord> createRecords(const PcapCaptureObservation& observation, b frontOffset, b backOffset) const = 0;
};

class INET_API IPcapCaptureObservationAdapter
{
public:
virtual ~IPcapCaptureObservationAdapter() {}
virtual std::optional<PcapCaptureObservation> tryCreateObservation(const cObject *object, Direction direction) const = 0;
};

} // namespace inet

#endif
12 changes: 11 additions & 1 deletion src/inet/common/packet/recorder/IPcapWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#ifndef __INET_IPCAPWRITER_H
#define __INET_IPCAPWRITER_H

#include <vector>

#include "inet/common/DirectionTag_m.h"
#include "inet/common/packet/Packet.h"
#include "inet/networklayer/common/NetworkInterface.h"
Expand Down Expand Up @@ -212,9 +214,17 @@ class INET_API IPcapWriter
virtual void setFlush(bool flush) = 0;

virtual void writePacket(simtime_t time, const Packet *packet, b frontOffset, b backOffset, Direction direction, NetworkInterface *ie, PcapLinkType linkType) = 0;

/**
* Writes an octet prefix followed by the selected range of the original packet.
* Protocol-specific capture adapters use the prefix as part of the selected link-layer
* record format. Implementations must include the prefix in the original record length
* and apply the capture length limit to the prefix and packet bytes together.
*/
virtual void writePacketWithPrefix(simtime_t time, const std::vector<uint8_t>& prefix, const Packet *packet, b frontOffset, b backOffset,
Direction direction, NetworkInterface *ie, PcapLinkType linkType) = 0;
};

} // namespace inet

#endif

82 changes: 82 additions & 0 deletions src/inet/common/packet/recorder/PcapCaptureAdapterRegistry.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// Copyright (C) 2026 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

#include "inet/common/packet/recorder/PcapCaptureAdapterRegistry.h"

namespace inet {

PcapCaptureAdapterRegistry::~PcapCaptureAdapterRegistry()
{
for (auto entry : protocolAdapters)
delete entry.second;
for (auto entry : observationAdapters)
delete entry.second;
}

// Each protocol, resolver key, and observation key has one owner per network setup. Silently
// replacing an entry would make capture behavior depend on registration order, so conflicts fail fast.
void PcapCaptureAdapterRegistry::registerProtocolAdapter(const Protocol *protocol, const IPcapCaptureAdapter *adapter)
{
if (protocol == nullptr || adapter == nullptr || protocolAdapters.find(protocol) != protocolAdapters.end()) {
delete adapter;
throw cRuntimeError("Duplicate or invalid PCAP capture protocol adapter registration");
}
protocolAdapters.emplace(protocol, adapter);
}

void PcapCaptureAdapterRegistry::registerProtocolResolver(const Protocol *outerProtocol, const Protocol *captureProtocol)
{
if (outerProtocol == nullptr || captureProtocol == nullptr || protocolResolvers.find(outerProtocol) != protocolResolvers.end())
throw cRuntimeError("Duplicate or invalid PCAP capture protocol resolver registration");
protocolResolvers.emplace(outerProtocol, captureProtocol);
}

void PcapCaptureAdapterRegistry::registerObservationAdapter(const char *key, const IPcapCaptureObservationAdapter *adapter)
{
if (opp_isempty(key) || adapter == nullptr || observationAdapters.find(key) != observationAdapters.end()) {
delete adapter;
throw cRuntimeError("Duplicate or invalid PCAP capture observation adapter registration for '%s'", key == nullptr ? "" : key);
}
observationAdapters.emplace(key, adapter);
}

const IPcapCaptureAdapter *PcapCaptureAdapterRegistry::findProtocolAdapter(const Protocol *protocol) const
{
auto iterator = protocolAdapters.find(protocol);
return iterator == protocolAdapters.end() ? nullptr : iterator->second;
}

std::optional<std::tuple<const Protocol *, b, b, const IPcapCaptureAdapter *>> PcapCaptureAdapterRegistry::tryResolveProtocolWithAdapter(const Protocol *outerProtocol, const Packet *packet, b frontOffset, b backOffset) const
{
auto resolver = protocolResolvers.find(outerProtocol);
if (resolver == protocolResolvers.end())
return std::nullopt;
auto adapter = findProtocolAdapter(resolver->second);
if (adapter == nullptr)
return std::nullopt;
auto offsets = adapter->tryResolvePacket(packet, frontOffset, backOffset);
return offsets.has_value() ? std::optional<std::tuple<const Protocol *, b, b, const IPcapCaptureAdapter *>>({resolver->second, offsets->first, offsets->second, adapter}) : std::nullopt;
}

std::optional<PcapCaptureObservation> PcapCaptureAdapterRegistry::tryCreateObservation(const cObject *object, Direction direction) const
{
for (const auto& entry : observationAdapters) {
auto observation = entry.second->tryCreateObservation(object, direction);
if (observation.has_value())
return observation;
}
return std::nullopt;
}

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

} // namespace inet
43 changes: 43 additions & 0 deletions src/inet/common/packet/recorder/PcapCaptureAdapterRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright (C) 2026 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

#ifndef __INET_PCAPCAPTUREADAPTERREGISTRY_H
#define __INET_PCAPCAPTUREADAPTERREGISTRY_H

#include <map>
#include <tuple>

#include "inet/common/packet/recorder/IPcapCaptureAdapter.h"

namespace inet {

#define Register_Pcap_Capture_Adapter(PROTOCOL, CLASSNAME) EXECUTE_PRE_NETWORK_SETUP(::inet::PcapCaptureAdapterRegistry::getInstance().registerProtocolAdapter(PROTOCOL, new CLASSNAME()));
#define Register_Pcap_Capture_Protocol_Resolver(OUTER_PROTOCOL, CAPTURE_PROTOCOL) EXECUTE_PRE_NETWORK_SETUP(::inet::PcapCaptureAdapterRegistry::getInstance().registerProtocolResolver(OUTER_PROTOCOL, CAPTURE_PROTOCOL));
#define Register_Pcap_Capture_Observation_Adapter(KEY, CLASSNAME) EXECUTE_PRE_NETWORK_SETUP(::inet::PcapCaptureAdapterRegistry::getInstance().registerObservationAdapter(KEY, new CLASSNAME()));

class INET_API PcapCaptureAdapterRegistry
{
protected:
std::map<const Protocol *, const IPcapCaptureAdapter *> protocolAdapters;
std::map<const Protocol *, const Protocol *> protocolResolvers;
std::map<std::string, const IPcapCaptureObservationAdapter *> observationAdapters;

public:
~PcapCaptureAdapterRegistry();

void registerProtocolAdapter(const Protocol *protocol, const IPcapCaptureAdapter *adapter);
void registerProtocolResolver(const Protocol *outerProtocol, const Protocol *captureProtocol);
void registerObservationAdapter(const char *key, const IPcapCaptureObservationAdapter *adapter);
const IPcapCaptureAdapter *findProtocolAdapter(const Protocol *protocol) const;
std::optional<std::tuple<const Protocol *, b, b, const IPcapCaptureAdapter *>> tryResolveProtocolWithAdapter(const Protocol *outerProtocol, const Packet *packet, b frontOffset, b backOffset) const;
std::optional<PcapCaptureObservation> tryCreateObservation(const cObject *object, Direction direction) const;

static PcapCaptureAdapterRegistry& getInstance();
};

} // namespace inet

#endif
Loading
Loading