Skip to content

Commit 7cc669e

Browse files
Zainullin DamirZainullin Damir
authored andcommitted
Process plugins - Introduce PacketHistogram process plugin
1 parent 8cdf254 commit 7cc669e

File tree

10 files changed

+510
-401
lines changed

10 files changed

+510
-401
lines changed

src/plugins/process/phists/CMakeLists.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
project(ipfixprobe-process-phists VERSION 1.0.0 DESCRIPTION "ipfixprobe-process-phists plugin")
22

33
add_library(ipfixprobe-process-phists MODULE
4-
src/phists.cpp
5-
src/phists.hpp
4+
src/packetHistogram.cpp
5+
src/packetHistogram.hpp
6+
src/packetHistogramContext.hpp
7+
src/packetHistogramFields.hpp
68
)
79

810
set_target_properties(ipfixprobe-process-phists PROPERTIES
911
CXX_VISIBILITY_PRESET hidden
1012
VISIBILITY_INLINES_HIDDEN YES
1113
)
1214

13-
target_include_directories(ipfixprobe-process-phists PRIVATE
15+
target_include_directories(ipfixprobe-process-phists PRIVATE
1416
${CMAKE_SOURCE_DIR}/include/
17+
${CMAKE_SOURCE_DIR}/include/ipfixprobe/processPlugin
18+
${CMAKE_SOURCE_DIR}/include/ipfixprobe/pluginFactory
19+
${CMAKE_SOURCE_DIR}/src/plugins/process/common
20+
${adaptmon_SOURCE_DIR}/lib/include/public/
1521
)
1622

1723
target_link_libraries(ipfixprobe-process-phists PRIVATE
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# PacketHistogram Plugin
2+
3+
The **PacketHistogram Plugin** builds histograms based on packet sizes and inter-arrival times.
4+
5+
## Features
6+
7+
- Builds and exports packet histograms.
8+
- Histograms are built based on packet sizes and inter-arrival times.
9+
- Bins are separated exponentially. Every next bin has double size of the previous one.
10+
11+
## Output Fields
12+
13+
| Field Name | Data Type | Description |
14+
| ---------------- | ------------------- | ----------------------------------------------------- |
15+
| `S_PHISTS_SIZES` | `array of uint32_t` | Packet sizes histogram (source → destination). |
16+
| `S_PHISTS_IPT` | `array of uint32_t` | Inter-arrival times histogram (source → destination). |
17+
| `D_PHISTS_SIZES` | `array of uint32_t` | Packet sizes histogram (destination → source). |
18+
| `D_PHISTS_IPT` | `array of uint32_t` | Inter-arrival times histogram (destination → source). |
19+
20+
## Usage
21+
22+
### YAML Configuration
23+
24+
Add the plugin to your ipfixprobe YAML configuration:
25+
26+
```yaml
27+
process_plugins:
28+
- phists
29+
```
30+
31+
### CLI Usage
32+
33+
You can also enable the plugin directly from the command line:
34+
35+
`ipfixprobe -p phists ...`
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/**
2+
* @file
3+
* @brief Plugin for parsing phists traffic.
4+
* @author Karel Hynek <[email protected]>
5+
* @author Pavel Siska <[email protected]>
6+
* @author Damir Zainullin <[email protected]>
7+
* @date 2025
8+
*
9+
* Provides a plugin that creates histograms based on packet sizes and inter-arrival times,
10+
* stores them in per-flow plugin data, and exposes fields via FieldManager.
11+
*
12+
* @copyright Copyright (c) 2025 CESNET, z.s.p.o.
13+
*/
14+
15+
#include "packetHistogram.hpp"
16+
17+
#include "packetHistogramGetters.hpp"
18+
#include "packetHistogramOptionsParser.hpp"
19+
20+
#include <algorithm>
21+
#include <array>
22+
#include <bit>
23+
#include <iostream>
24+
25+
#include <fieldGroup.hpp>
26+
#include <fieldManager.hpp>
27+
#include <flowRecord.hpp>
28+
#include <pluginFactory.hpp>
29+
#include <pluginManifest.hpp>
30+
#include <pluginRegistrar.hpp>
31+
#include <utils.hpp>
32+
#include <utils/spanUtils.hpp>
33+
34+
namespace ipxp::process::packet_histogram {
35+
36+
static const PluginManifest packetHistogramPluginManifest = {
37+
.name = "phists",
38+
.description = "Phists process plugin for parsing phists traffic.",
39+
.pluginVersion = "1.0.0",
40+
.apiVersion = "1.0.0",
41+
.usage =
42+
[]() {
43+
PacketHistogramOptionsParser parser;
44+
parser.usage(std::cout);
45+
},
46+
};
47+
48+
static FieldGroup createPacketHistogramSchema(
49+
FieldManager& fieldManager,
50+
FieldHandlers<PacketHistogramFields>& handlers) noexcept
51+
{
52+
FieldGroup schema = fieldManager.createFieldGroup("phists");
53+
54+
auto [forwardSizesField, reverseSizesField] = schema.addVectorDirectionalFields(
55+
"S_PHISTS_SIZES",
56+
"D_PHISTS_SIZES",
57+
[](const void* context) { return getPacketLengthsField(context, Direction::Forward); },
58+
[](const void* context) { return getPacketLengthsField(context, Direction::Reverse); });
59+
handlers.insert(PacketHistogramFields::S_PHISTS_SIZES, forwardSizesField);
60+
handlers.insert(PacketHistogramFields::D_PHISTS_SIZES, reverseSizesField);
61+
62+
auto [forwardIPTField, reverseIPTField] = schema.addVectorDirectionalFields(
63+
"S_PHISTS_IPT",
64+
"D_PHISTS_IPT",
65+
[](const void* context) { return getPacketTimediffsField(context, Direction::Forward); },
66+
[](const void* context) { return getPacketTimediffsField(context, Direction::Reverse); });
67+
handlers.insert(PacketHistogramFields::S_PHISTS_IPT, forwardIPTField);
68+
handlers.insert(PacketHistogramFields::D_PHISTS_IPT, reverseIPTField);
69+
70+
return schema;
71+
}
72+
73+
PacketHistogramPlugin::PacketHistogramPlugin(
74+
[[maybe_unused]] const std::string& params,
75+
FieldManager& manager)
76+
{
77+
createPacketHistogramSchema(manager, m_fieldHandlers);
78+
}
79+
80+
constexpr static uint32_t fastlog2(const uint32_t value)
81+
{
82+
constexpr auto lookup
83+
= std::to_array<uint32_t>({0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
84+
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31});
85+
86+
// Set all bits after highest to 1
87+
const uint32_t filledValue = std::bit_ceil(value) - 1;
88+
89+
return lookup[(filledValue * 0x07C4ACDD) >> 27];
90+
}
91+
92+
constexpr static void incrementWithoutOverflow(uint32_t& valueToIncrement) noexcept
93+
{
94+
uint32_t valueBeforeIncrement {valueToIncrement};
95+
if (__builtin_add_overflow(valueBeforeIncrement, 1, &valueToIncrement)) {
96+
// overflow occurred
97+
valueToIncrement = valueBeforeIncrement;
98+
}
99+
}
100+
101+
/*
102+
* 0-15 1. bin
103+
* 16-31 2. bin
104+
* 32-63 3. bin
105+
* 64-127 4. bin
106+
* 128-255 5. bin
107+
* 256-511 6. bin
108+
* 512-1023 7. bin
109+
* 1024 > 8. bin
110+
*/
111+
constexpr static void updateHistogram(
112+
const uint32_t value,
113+
std::array<uint32_t, PacketHistogramContext::HISTOGRAM_SIZE>& histogram) noexcept
114+
{
115+
// first bin starts on 2^4, -1 for indexing from 0
116+
constexpr std::size_t firstBinOffset = 3;
117+
const std::size_t binIndex = std::clamp<uint32_t>(
118+
fastlog2(value),
119+
firstBinOffset,
120+
histogram.size() - 1 + firstBinOffset)
121+
- firstBinOffset;
122+
incrementWithoutOverflow(histogram[binIndex]);
123+
}
124+
125+
void PacketHistogramPlugin::updateExportData(
126+
const uint16_t realPacketLength,
127+
const amon::types::Timestamp packetTimestamp,
128+
const Direction direction,
129+
PacketHistogramContext& packetHistogramContext) noexcept
130+
{
131+
if (realPacketLength == 0 && !m_countEmptyPackets) {
132+
return;
133+
}
134+
135+
updateHistogram(
136+
static_cast<uint32_t>(realPacketLength),
137+
packetHistogramContext.packetLengths[direction]);
138+
139+
if (!packetHistogramContext.processingState.lastTimestamps[direction].has_value()) {
140+
packetHistogramContext.processingState.lastTimestamps[direction]
141+
= packetTimestamp.nanoseconds();
142+
return;
143+
}
144+
145+
const int64_t timediff = std::max<int64_t>(
146+
0,
147+
(packetTimestamp.nanoseconds()
148+
- *packetHistogramContext.processingState.lastTimestamps[direction]));
149+
packetHistogramContext.processingState.lastTimestamps[direction]
150+
= packetTimestamp.nanoseconds();
151+
updateHistogram(
152+
static_cast<uint32_t>(timediff),
153+
packetHistogramContext.packetTimediffs[direction]);
154+
}
155+
156+
OnInitResult PacketHistogramPlugin::onInit(const FlowContext& flowContext, void* pluginContext)
157+
{
158+
auto& packetHistogramContext
159+
= *std::construct_at(reinterpret_cast<PacketHistogramContext*>(pluginContext));
160+
161+
const std::optional<std::size_t> realPacketLength
162+
= getIPPayloadLength(*flowContext.packetContext.packet);
163+
if (!realPacketLength.has_value()) {
164+
return OnInitResult::Irrelevant;
165+
}
166+
167+
updateExportData(
168+
*realPacketLength,
169+
flowContext.packetContext.packet->timestamp,
170+
Direction::Forward,
171+
packetHistogramContext);
172+
173+
return OnInitResult::ConstructedNeedsUpdate;
174+
}
175+
176+
OnUpdateResult PacketHistogramPlugin::onUpdate(const FlowContext& flowContext, void* pluginContext)
177+
{
178+
auto& packetHistogramContext = *reinterpret_cast<PacketHistogramContext*>(pluginContext);
179+
180+
const std::optional<std::size_t> realPacketLength
181+
= getIPPayloadLength(*flowContext.packetContext.packet);
182+
if (!realPacketLength.has_value()) {
183+
return OnUpdateResult::NeedsUpdate;
184+
}
185+
186+
updateExportData(
187+
*realPacketLength,
188+
flowContext.packetContext.packet->timestamp,
189+
flowContext.packetDirection,
190+
packetHistogramContext);
191+
192+
return OnUpdateResult::NeedsUpdate;
193+
}
194+
195+
OnExportResult
196+
PacketHistogramPlugin::onExport(const FlowRecord& flowRecord, [[maybe_unused]] void* pluginContext)
197+
{
198+
const std::size_t packetsTotal = flowRecord.directionalData[Direction::Forward].packets
199+
+ flowRecord.directionalData[Direction::Reverse].packets;
200+
const TCPFlags tcpFlags = flowRecord.directionalData[Direction::Forward].tcpFlags
201+
| flowRecord.directionalData[Direction::Reverse].tcpFlags;
202+
203+
// do not export phists for single packets flows, usually port scans
204+
constexpr std::size_t MIN_FLOW_LENGTH = 1;
205+
if (packetsTotal <= MIN_FLOW_LENGTH && tcpFlags.bitfields.synchronize) {
206+
return OnExportResult::Remove;
207+
}
208+
209+
m_fieldHandlers[PacketHistogramFields::S_PHISTS_SIZES].setAsAvailable(flowRecord);
210+
m_fieldHandlers[PacketHistogramFields::S_PHISTS_IPT].setAsAvailable(flowRecord);
211+
m_fieldHandlers[PacketHistogramFields::D_PHISTS_SIZES].setAsAvailable(flowRecord);
212+
m_fieldHandlers[PacketHistogramFields::D_PHISTS_IPT].setAsAvailable(flowRecord);
213+
214+
return OnExportResult::NoAction;
215+
}
216+
217+
void PacketHistogramPlugin::onDestroy(void* pluginContext) noexcept
218+
{
219+
std::destroy_at(reinterpret_cast<PacketHistogramContext*>(pluginContext));
220+
}
221+
222+
PluginDataMemoryLayout PacketHistogramPlugin::getDataMemoryLayout() const noexcept
223+
{
224+
return {
225+
.size = sizeof(PacketHistogramContext),
226+
.alignment = alignof(PacketHistogramContext),
227+
};
228+
}
229+
230+
static const PluginRegistrar<
231+
PacketHistogramPlugin,
232+
PluginFactory<ProcessPlugin, const std::string&, FieldManager&>>
233+
packetHistogramRegistrar(packetHistogramPluginManifest);
234+
235+
} // namespace ipxp::process::packet_histogram

0 commit comments

Comments
 (0)