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
4 changes: 4 additions & 0 deletions WHATSNEW
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ Notable bug fixes are the following:

The HT mode factory now includes band and preamble format in its cache key. A 5 GHz request after an equivalent 2.4 GHz request therefore receives the requested band and timing. Previously, that request could receive the earlier mode. Identical complete requests still reuse one object.

2. IPsec reports the datagrams it discards

IPsec discarded datagrams, for example by a Security Policy Database entry with the action DISCARD, without emitting packetDropped, so PacketDropVisualizer and the statistics built on that signal did not see these drops. IPsec now emits packetDropped at each discard it counts, with the reason LIFETIME_EXPIRED when an inbound packet arrives on a Security Association whose hard lifetime has ended or whose byte limit it would exceed, and OTHER_PACKET_DROP otherwise, and records the statistics packetDropLifetimeExpired and packetDropUndefined. A discarded Encapsulating Security Payload (ESP) dummy packet is still not reported. The event trajectory does not change.


INET-4.7 (July 2026) — feature release
--------------------------------------
Expand Down
42 changes: 42 additions & 0 deletions src/inet/networklayer/ipsec/IPsec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,14 @@ void IPsec::sendDummyPacket(SecurityAssociation *sadEntry)
send(packet, "ipOut");
}

void IPsec::emitPacketDropped(Packet *packet, PacketDropReason reason)
{
// the hook returns DROP after this, and the network layer deletes the packet
PacketDropDetails details;
details.setReason(reason);
emit(packetDroppedSignal, packet, &details);
}

INetfilter::IHook::Result IPsec::datagramPostRoutingHook(Packet *packet)
{
// a dummy packet is already protected
Expand Down Expand Up @@ -525,12 +533,14 @@ INetfilter::IHook::Result IPsec::processEgressPacket(Packet *packet, const L3Add
}
else if (spdEntry->getAction() == Action::DISCARD) {
EV_INFO << "IPsec OUT DROP rule, packet: " << egressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(outDropSignal, 1L);
outDrop++;
return INetfilter::IHook::DROP;
}
}
EV_INFO << "IPsec OUT BYPASS, no matching rule, packet: " << egressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(outDropSignal, 1L);
outDrop++;
return INetfilter::IHook::DROP;
Expand Down Expand Up @@ -882,6 +892,7 @@ INetfilter::IHook::Result IPsec::protectDatagram(Packet *packet, const PacketInf
// RFC 4301 section 5.1: the entry wants protection, and no SA of it covers the packet;
// with no key management to create one, the packet is discarded, not sent in clear
EV_INFO << "IPsec OUT DROP, no SA of the PROTECT entry matches, packet: " << packetInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(outDropSignal, 1L);
outDrop++;
return INetfilter::IHook::DROP;
Expand Down Expand Up @@ -939,6 +950,10 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
if (sadEntry != nullptr) {
if (sadEntry->getProtection() != Protection::AH) {
EV_INFO << "IPsec IN DROP AH, SA does not prescribe AH, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as received
packet->insertAtFront(ah);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -956,6 +971,11 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
// RFC 4301 section 4.4.2.1: an SA whose hard lifetime has ended accepts nothing
if (!sadEntry->isUsable(simTime(), packet->getByteLength())) {
EV_INFO << "IPsec IN DROP AH, the hard lifetime of the SA has ended, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as AH processing would give it back
setNetworkHeaderProtocol(netHeader, (IpProtocolId)ah->getNextHeader());
updateNetworkHeaderLength(netHeader, packet);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, LIFETIME_EXPIRED);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -972,6 +992,7 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
if (ah->getNextHeader() != IP_PROT_ESP) {
if (!matchesSelectorsOfSa(packet, sadEntry)) {
EV_INFO << "IPsec IN DROP AH, the packet does not match the selectors of its SA, packet: " << ingressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand Down Expand Up @@ -1004,6 +1025,10 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)

if (sadEntry == nullptr) {
EV_INFO << "IPsec IN DROP AH, no matching rule, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as received
packet->insertAtFront(ah);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -1019,6 +1044,10 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
if (sadEntry != nullptr) {
if (sadEntry->getProtection() != Protection::ESP) {
EV_INFO << "IPsec IN DROP ESP, SA does not prescribe ESP, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as ESP processing received it
packet->insertAtFront(espHeader);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -1044,6 +1073,11 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
// RFC 4301 section 4.4.2.1: an SA whose hard lifetime has ended accepts nothing
if (!sadEntry->isUsable(simTime(), packet->getByteLength())) {
EV_INFO << "IPsec IN DROP ESP, the hard lifetime of the SA has ended, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as ESP processing would give it back
setNetworkHeaderProtocol(netHeader, (IpProtocolId)espTrailer->getNextHeader());
updateNetworkHeaderLength(netHeader, packet);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, LIFETIME_EXPIRED);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -1055,6 +1089,7 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)

if (!matchesSelectorsOfSa(packet, sadEntry) || (ahSadEntry != nullptr && !matchesSelectorsOfSa(packet, ahSadEntry))) {
EV_INFO << "IPsec IN DROP ESP, the packet does not match the selectors of its SA, packet: " << ingressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand Down Expand Up @@ -1086,6 +1121,10 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)

if (sadEntry == nullptr) {
EV_INFO << "IPsec IN DROP ESP, no matching rule, packet: " << ingressPacketInfo.str() << std::endl;
// report the datagram as ESP processing received it
packet->insertAtFront(espHeader);
packet->insertAtFront(netHeader);
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inProtectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -1102,12 +1141,14 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
}
else if (spdEntry->getAction() == Action::DISCARD) {
EV_INFO << "IPsec DROP rule, packet: " << ingressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inUnprotectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
}
else {
EV_INFO << "IPsec IN DROP due to wrong rule, packet: " << ingressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inUnprotectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand All @@ -1116,6 +1157,7 @@ INetfilter::IHook::Result IPsec::processIngressPacket(Packet *packet)
}

EV_INFO << "IPsec IN DROP, no matching rule, packet: " << ingressPacketInfo.str() << std::endl;
emitPacketDropped(packet, OTHER_PACKET_DROP);
emit(inUnprotectedDropSignal, 1L);
inDrop++;
return INetfilter::IHook::DROP;
Expand Down
2 changes: 2 additions & 0 deletions src/inet/networklayer/ipsec/IPsec.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "inet/common/INETDefs.h"
#include "inet/common/SimpleModule.h"
#include "inet/common/Simsignals.h"
#include "inet/networklayer/contract/INetfilter.h"
#include "inet/common/ModuleAccess.h"
#include "inet/common/Protocol.h"
Expand Down Expand Up @@ -112,6 +113,7 @@ class INET_API IPsec : public SimpleModule, NetfilterBase::HookBase
virtual INetfilter::IHook::Result protectDatagram(Packet *ipv4datagram, const PacketInfo& packetInfo, SecurityPolicy *spdEntry);
virtual bool matchesSelectorsOfSa(Packet *packet, SecurityAssociation *sadEntry);
virtual void sendDummyPacket(SecurityAssociation *sadEntry);
virtual void emitPacketDropped(Packet *packet, PacketDropReason reason);

virtual int getIntegrityCheckValueBitLength(EncryptionAlg alg);
virtual int getInitializationVectorBitLength(EncryptionAlg alg);
Expand Down
3 changes: 3 additions & 0 deletions src/inet/networklayer/ipsec/IPsec.ned
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ simple IPsec extends SimpleModule
@signal[outDrop](type=long);
@signal[inProcessDelay](type=double);
@signal[outProcessDelay](type=double);
@signal[packetDropped](type=inet::Packet);

@statistic[inProtectedAccept](title="Incoming Protected Packet Accepted"; record=count,vector);
@statistic[inProtectedDrop](title="Incoming Protected Packet Dropped"; record=count,vector);
Expand All @@ -242,6 +243,8 @@ simple IPsec extends SimpleModule
@statistic[outDrop](title="Outgoing Packet Dropped"; record=count,vector);
@statistic[inProcessDelay](title="Incoming Processing Delay"; record=vector,stats);
@statistic[outProcessDelay](title="Outgoing Processing Delay"; record=vector,stats);
@statistic[packetDropLifetimeExpired](title="packet drop: lifetime expired"; source=packetDropReasonIsLifetimeExpired(packetDropped); record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none);
@statistic[packetDropUndefined](title="packet drop: undefined"; source=packetDropReasonIsUndefined(packetDropped); record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none);

gates:
input ipIn; // unused: nothing is dispatched up to IPsec
Expand Down
79 changes: 79 additions & 0 deletions tests/module/IPsec_packet_dropped.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
%description:
Tests that IPsec emits packetDropped when it discards a datagram, so that the
packetDropUndefined statistic of the IPsec module counts the discards that its
outDrop, inUnprotectedDrop and inProtectedDrop statistics count.

client sends three UDP datagrams to each of three ports of server.
- Port 2000: the client's Security Policy Database (SPD) has an OUT entry with
the action DISCARD, so the client's IPsec discards the datagrams.
- Port 2001: the client's SPD has an OUT entry with the action BYPASS, and the
server's SPD has no entry for them, so the server's IPsec discards them on
arrival.
- Port 2002: the client's SPD protects the datagrams with the Encapsulating
Security Payload (ESP) under Security Parameters Index (SPI) 7, and the
server has no Security Association (SA) and no SPD entry for them, so the server's IPsec
discards them after removing their network and ESP headers.

%#--------------------------------------------------------------------------------------------------------------
%file: test.ned
import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator;
import inet.node.inet.StandardHost;
import ned.DatarateChannel;

network IpsecDropNetwork
{
submodules:
configurator: Ipv4NetworkConfigurator;
client: StandardHost;
server: StandardHost;
connections:
client.pppg++ <--> { datarate = 10Mbps; delay = 0.1us; } <--> server.pppg++;
}
%#--------------------------------------------------------------------------------------------------------------
%inifile: omnetpp.ini
[General]
ned-path = ../../../../src
network = IpsecDropNetwork
sim-time-limit = 5s
record-vector-results = false
cmdenv-express-mode = true

**.hasIpsec = true
**.client.**.spdConfig = xml("<Device>\
<SecurityPolicy><Selector><Protocol>UDP</Protocol><RemotePort>2000</RemotePort></Selector><Direction>OUT</Direction><Action>DISCARD</Action></SecurityPolicy>\
<SecurityPolicy><Selector><Protocol>UDP</Protocol><RemotePort>2001</RemotePort></Selector><Direction>OUT</Direction><Action>BYPASS</Action></SecurityPolicy>\
<SecurityPolicy><Selector><Protocol>UDP</Protocol><RemotePort>2002</RemotePort></Selector><Direction>OUT</Direction><Action>PROTECT</Action><Protection>ESP</Protection><EspMode>CONFIDENTIALITY</EspMode><EncryptionAlg>AES_CBC_128</EncryptionAlg><SecurityAssociation><SPI>7</SPI></SecurityAssociation></SecurityPolicy>\
</Device>")
**.server.**.spdConfig = xml("<Device></Device>")

**.client.numApps = 3
**.client.app[*].typename = "UdpBasicApp"
**.client.app[*].destAddresses = "server"
**.client.app[0].destPort = 2000
**.client.app[1].destPort = 2001
**.client.app[2].destPort = 2002
**.client.app[*].messageLength = 100B
**.client.app[*].startTime = 1s
**.client.app[*].sendInterval = 1s
**.client.app[*].stopTime = 3.5s
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: gawk '$1 == "scalar" && $2 ~ /\.ipv4\.ipsec$/ && ($3 == "outDrop:count" || $3 == "inUnprotectedDrop:count" || $3 == "inProtectedDrop:count" || $3 == "packetDropUndefined:count") { print $2, $3, $4 }' results/*.sca > drops.out
%contains: drops.out
IpsecDropNetwork.client.ipv4.ipsec outDrop:count 3
%#--------------------------------------------------------------------------------------------------------------
%contains: drops.out
IpsecDropNetwork.client.ipv4.ipsec packetDropUndefined:count 3
%#--------------------------------------------------------------------------------------------------------------
%contains: drops.out
IpsecDropNetwork.server.ipv4.ipsec inUnprotectedDrop:count 3
%#--------------------------------------------------------------------------------------------------------------
%contains: drops.out
IpsecDropNetwork.server.ipv4.ipsec inProtectedDrop:count 3
%#--------------------------------------------------------------------------------------------------------------
%contains: drops.out
IpsecDropNetwork.server.ipv4.ipsec packetDropUndefined:count 6
%#--------------------------------------------------------------------------------------------------------------
%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true
%not-contains: test_undisposed.out
undisposed object: (
%#--------------------------------------------------------------------------------------------------------------
Loading