Skip to content

Commit 982967b

Browse files
Zainullin DamirZainullin Damir
authored andcommitted
Process plugins - Introduce PacketHistogram process plugin
1 parent 356ecad commit 982967b

File tree

10 files changed

+496
-401
lines changed

10 files changed

+496
-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: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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+
updateExportData(
161+
flowContext.packetContext.features->ipPayloadLength,
162+
flowContext.packetContext.packet->timestamp,
163+
Direction::Forward,
164+
packetHistogramContext);
165+
166+
return OnInitResult::ConstructedNeedsUpdate;
167+
}
168+
169+
OnUpdateResult PacketHistogramPlugin::onUpdate(const FlowContext& flowContext, void* pluginContext)
170+
{
171+
auto& packetHistogramContext = *reinterpret_cast<PacketHistogramContext*>(pluginContext);
172+
updateExportData(
173+
flowContext.packetContext.features->ipPayloadLength,
174+
flowContext.packetContext.packet->timestamp,
175+
flowContext.packetContext.features->direction,
176+
packetHistogramContext);
177+
178+
return OnUpdateResult::NeedsUpdate;
179+
}
180+
181+
OnExportResult
182+
PacketHistogramPlugin::onExport(const FlowRecord& flowRecord, [[maybe_unused]] void* pluginContext)
183+
{
184+
const std::size_t packetsTotal = flowRecord.directionalData[Direction::Forward].packets
185+
+ flowRecord.directionalData[Direction::Reverse].packets;
186+
const TCPFlags tcpFlags = flowRecord.directionalData[Direction::Forward].tcpFlags
187+
| flowRecord.directionalData[Direction::Reverse].tcpFlags;
188+
189+
// do not export phists for single packets flows, usually port scans
190+
constexpr std::size_t MIN_FLOW_LENGTH = 1;
191+
if (packetsTotal <= MIN_FLOW_LENGTH && tcpFlags.bitfields.synchronize) {
192+
return OnExportResult::Remove;
193+
}
194+
195+
m_fieldHandlers[PacketHistogramFields::S_PHISTS_SIZES].setAsAvailable(flowRecord);
196+
m_fieldHandlers[PacketHistogramFields::S_PHISTS_IPT].setAsAvailable(flowRecord);
197+
m_fieldHandlers[PacketHistogramFields::D_PHISTS_SIZES].setAsAvailable(flowRecord);
198+
m_fieldHandlers[PacketHistogramFields::D_PHISTS_IPT].setAsAvailable(flowRecord);
199+
200+
return OnExportResult::NoAction;
201+
}
202+
203+
void PacketHistogramPlugin::onDestroy(void* pluginContext) noexcept
204+
{
205+
std::destroy_at(reinterpret_cast<PacketHistogramContext*>(pluginContext));
206+
}
207+
208+
PluginDataMemoryLayout PacketHistogramPlugin::getDataMemoryLayout() const noexcept
209+
{
210+
return {
211+
.size = sizeof(PacketHistogramContext),
212+
.alignment = alignof(PacketHistogramContext),
213+
};
214+
}
215+
216+
static const PluginRegistrar<
217+
PacketHistogramPlugin,
218+
PluginFactory<ProcessPlugin, const std::string&, FieldManager&>>
219+
packetHistogramRegistrar(packetHistogramPluginManifest);
220+
221+
} // namespace ipxp::process::packet_histogram
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
#pragma once
16+
17+
#include "packetHistogramContext.hpp"
18+
#include "packetHistogramFields.hpp"
19+
20+
#include <sstream>
21+
#include <string>
22+
23+
#include <fieldHandlersEnum.hpp>
24+
#include <fieldManager.hpp>
25+
#include <processPlugin.hpp>
26+
27+
namespace ipxp::process::packet_histogram {
28+
29+
/**
30+
* @class PacketHistogramPlugin
31+
* @brief A plugin for collecting and exporting packet histogram statistics.
32+
*
33+
* Empty packets can optionally be omitted from statistics.
34+
*/
35+
class PacketHistogramPlugin : public ProcessPlugin {
36+
public:
37+
/**
38+
* @brief Constructs the PacketHistogram plugin.
39+
*
40+
* @param parameters Plugin parameters as a string (currently unused).
41+
* @param fieldManager Reference to the FieldManager for field registration.
42+
*/
43+
PacketHistogramPlugin(const std::string& params, FieldManager& manager);
44+
45+
/**
46+
* @brief Initializes plugin data for a new flow.
47+
*
48+
* Constructs `PacketHistogramContext` in `pluginContext` and initializes histograms
49+
* with values from the first packet.
50+
*
51+
* @param flowContext Contextual information about the flow to fill new record.
52+
* @param pluginContext Pointer to pre-allocated memory to create record.
53+
* @return Result of the initialization process, always requires new packets.
54+
*/
55+
OnInitResult onInit(const FlowContext& flowContext, void* pluginContext) override;
56+
57+
/**
58+
* @brief Updates plugin data with values from new packet.
59+
*
60+
* Updates histograms of `PacketHistogramContext` with length and inter-arrival time.
61+
*
62+
* @param flowContext Contextual information about the flow to be updated.
63+
* @param pluginContext Pointer to `PacketHistogramContext`.
64+
* @return Result of the update, always requires new packets.
65+
*/
66+
OnUpdateResult onUpdate(const FlowContext& flowContext, void* pluginContext) override;
67+
68+
/**
69+
* @brief Prepare the export data.
70+
*
71+
* Removes record if it seems to be TCP scan.
72+
* Sets all fields as available otherwise.
73+
*
74+
* @param flowRecord The flow record containing aggregated flow data.
75+
* @param pluginContext Pointer to `PacketHistogramContext`.
76+
* @return Remove plugin if it is TCP scan.
77+
*/
78+
OnExportResult onExport(const FlowRecord& flowRecord, void* pluginContext) override;
79+
80+
/**
81+
* @brief Cleans up and destroys `PacketHistogramContext`.
82+
* @param pluginContext Pointer to `PacketHistogramContext`.
83+
*/
84+
void onDestroy(void* pluginContext) noexcept override;
85+
86+
/**
87+
* @brief Provides the memory layout of `PacketHistogramContext`.
88+
* @return Memory layout description for the plugin data.
89+
*/
90+
PluginDataMemoryLayout getDataMemoryLayout() const noexcept override;
91+
92+
private:
93+
void updateExportData(
94+
const uint16_t realPacketLength,
95+
const amon::types::Timestamp packetTimestamp,
96+
const Direction direction,
97+
PacketHistogramContext& packetHistogramContext) noexcept;
98+
99+
bool m_countEmptyPackets {false};
100+
101+
FieldHandlers<PacketHistogramFields> m_fieldHandlers;
102+
};
103+
104+
} // namespace ipxp::process::packet_histogram

0 commit comments

Comments
 (0)