Skip to content

Commit 3d27af2

Browse files
author
Damir Zainullin
committed
TCP RTT - Add TCP round-trip-time process plugin
1 parent a24b8e2 commit 3d27af2

File tree

6 files changed

+293
-0
lines changed

6 files changed

+293
-0
lines changed

include/ipfixprobe/ipfix-elements.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ namespace ipxp {
306306

307307
#define MPLS_TOP_LABEL_STACK_SECTION F(0, 70, -1, nullptr)
308308

309+
#define TCP_RTT(F) F(8057, 1040, 4, nullptr)
310+
309311
/**
310312
* IPFIX Templates - list of elements
311313
*
@@ -461,6 +463,8 @@ namespace ipxp {
461463
F(MQTT_PUBLISH_FLAGS) \
462464
F(MQTT_TOPICS)
463465

466+
#define IPFIX_TCP_RTT_TEMPLATE(F) F(TCP_RTT)
467+
464468
#define IPFIX_PSTATS_TEMPLATE(F) \
465469
F(STATS_PCKT_SIZES) \
466470
F(STATS_PCKT_TIMESTAMPS) \

src/plugins/process/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_subdirectory(smtp)
2121
add_subdirectory(quic)
2222
add_subdirectory(tls)
2323
add_subdirectory(http)
24+
add_subdirectory(tcpRtt)
2425

2526
if (ENABLE_PROCESS_EXPERIMENTAL)
2627
add_subdirectory(sip)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
project(ipfixprobe-process-tcp-rtt VERSION 1.0.0 DESCRIPTION "ipfixprobe-process-tcp-rtt plugin")
2+
3+
add_library(ipfixprobe-process-tcp-rtt MODULE
4+
src/tcpRtt.cpp
5+
src/tcpRtt.hpp
6+
)
7+
8+
set_target_properties(ipfixprobe-process-tcp-rtt PROPERTIES
9+
CXX_VISIBILITY_PRESET hidden
10+
VISIBILITY_INLINES_HIDDEN YES
11+
)
12+
13+
target_include_directories(ipfixprobe-process-tcp-rtt PRIVATE
14+
${CMAKE_SOURCE_DIR}/include/
15+
)
16+
17+
target_link_libraries(ipfixprobe-process-tcp-rtt PRIVATE
18+
ipfixprobe-output-ipfix
19+
)
20+
21+
if(ENABLE_NEMEA)
22+
target_link_libraries(ipfixprobe-process-tcp-rtt PRIVATE
23+
-Wl,--whole-archive ipfixprobe-nemea-fields -Wl,--no-whole-archive
24+
unirec::unirec
25+
trap::trap
26+
)
27+
endif()
28+
29+
install(TARGETS ipfixprobe-process-tcp-rtt
30+
LIBRARY DESTINATION "${INSTALL_DIR_LIB}/ipfixprobe/process/"
31+
)

src/plugins/process/tcpRtt/README.md

Whitespace-only changes.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @file
3+
* @brief Plugin for accounting round trip time of tcp handshakes.
4+
* @author Damir Zainullin <[email protected]>
5+
*
6+
* Copyright (c) 2025 CESNET
7+
*
8+
* SPDX-License-Identifier: BSD-3-Clause
9+
*/
10+
11+
#include "tcpRtt.hpp"
12+
13+
#include <ipfixprobe/pluginFactory/pluginManifest.hpp>
14+
#include <ipfixprobe/pluginFactory/pluginRegistrar.hpp>
15+
16+
namespace ipxp {
17+
18+
static const PluginManifest tcpRttPluginManifest = {
19+
.name = "tcprtt",
20+
.description = "Process plugin to obtain round trip time of TCP connection.",
21+
.pluginVersion = "1.0.0",
22+
.apiVersion = "1.0.0",
23+
.usage =
24+
[]() {
25+
OptionsParser parser("tcprtt", "Calculate tcp rtt");
26+
parser.usage(std::cout);
27+
},
28+
};
29+
30+
TCPRTTPlugin::TCPRTTPlugin(const std::string& params, int pluginID)
31+
: ProcessPlugin(pluginID)
32+
{
33+
init(params.c_str());
34+
}
35+
36+
OptionsParser* TCPRTTPlugin::get_parser() const
37+
{
38+
return new OptionsParser("tcprtt", "Calculate tcp rtt");
39+
}
40+
41+
std::string TCPRTTPlugin::get_name() const
42+
{
43+
return "tcprtt";
44+
}
45+
46+
RecordExtTCPRTT* TCPRTTPlugin::get_ext() const
47+
{
48+
return new RecordExtTCPRTT(m_pluginID);
49+
}
50+
51+
void TCPRTTPlugin::init([[maybe_unused]] const char* params) {}
52+
53+
TCPRTTPlugin::TCPRTTPlugin(const TCPRTTPlugin& other) noexcept
54+
: ProcessPlugin(other.m_pluginID)
55+
{
56+
}
57+
58+
ProcessPlugin* TCPRTTPlugin::copy()
59+
{
60+
return new TCPRTTPlugin(*this);
61+
}
62+
63+
int TCPRTTPlugin::post_create(Flow& rec, const Packet& pkt)
64+
{
65+
if (m_prealloced_extension == nullptr) {
66+
m_prealloced_extension.reset(get_ext());
67+
}
68+
69+
if (pkt.ip_proto == IPPROTO_TCP) {
70+
rec.add_extension(m_prealloced_extension.release());
71+
}
72+
73+
update_tcp_rtt_record(rec, pkt);
74+
return 0;
75+
}
76+
77+
int TCPRTTPlugin::pre_update(Flow& rec, Packet& pkt)
78+
{
79+
update_tcp_rtt_record(rec, pkt);
80+
return 0;
81+
}
82+
83+
constexpr static inline bool is_tcp_syn(uint8_t tcp_flags) noexcept
84+
{
85+
return tcp_flags & 0b10;
86+
}
87+
88+
constexpr static inline bool is_tcp_syn_ack(uint8_t tcp_flags) noexcept
89+
{
90+
return (tcp_flags & 0b10) && (tcp_flags & 0b10000);
91+
}
92+
93+
void TCPRTTPlugin::update_tcp_rtt_record(Flow& rec, const Packet& pkt) noexcept
94+
{
95+
auto* extension = static_cast<RecordExtTCPRTT*>(rec.get_extension(m_pluginID));
96+
97+
if (extension != nullptr && is_tcp_syn_ack(pkt.tcp_flags)) {
98+
extension->tcp_synack_timestamp = pkt.ts;
99+
} else if (extension != nullptr && is_tcp_syn(pkt.tcp_flags)) {
100+
extension->tcp_syn_timestamp = pkt.ts;
101+
}
102+
}
103+
104+
static const PluginRegistrar<TCPRTTPlugin, ProcessPluginFactory>
105+
tcpRttRegistrar(tcpRttPluginManifest);
106+
107+
} // namespace ipxp
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**
2+
* @file
3+
* @brief Plugin for accounting round trip time of tcp handshakes.
4+
* @author Damir Zainullin <[email protected]>
5+
*
6+
* Copyright (c) 2025 CESNET
7+
*
8+
* SPDX-License-Identifier: BSD-3-Clause
9+
*/
10+
11+
#pragma once
12+
13+
#include <limits>
14+
#include <memory>
15+
#include <sstream>
16+
17+
#include <ipfixprobe/flowifc.hpp>
18+
#include <ipfixprobe/ipfix-basiclist.hpp>
19+
#include <ipfixprobe/ipfix-elements.hpp>
20+
#include <ipfixprobe/packet.hpp>
21+
#include <ipfixprobe/processPlugin.hpp>
22+
23+
#ifdef WITH_NEMEA
24+
#include "fields.h"
25+
#endif
26+
27+
namespace ipxp {
28+
29+
#define TCPRTT_UNIREC_TEMPLATE "TCPRTT_TIME"
30+
UR_FIELDS(uint64 TCPRTT_TIME)
31+
32+
/**
33+
* @brief Convert timeval struct to count of milliseconds since epoch
34+
* @param timeval Timeval to convert
35+
* @return Count of milliseconds since epoch
36+
*/
37+
constexpr static inline uint64_t timeval_to_msec(timeval timeval) noexcept
38+
{
39+
constexpr size_t MSEC_IN_SEC = 1'000;
40+
constexpr size_t USEC_IN_MSEC = 1'000;
41+
return timeval.tv_sec * MSEC_IN_SEC + timeval.tv_usec / USEC_IN_MSEC;
42+
}
43+
44+
/**
45+
* \brief Flow record extension header for storing observed handshake timestamps.
46+
*/
47+
struct RecordExtTCPRTT : public RecordExt {
48+
private:
49+
constexpr static timeval NO_TIMESTAMP = timeval {std::numeric_limits<time_t>::min(), 0};
50+
51+
constexpr inline static bool has_no_value(timeval timeval) noexcept
52+
{
53+
return timeval.tv_sec == NO_TIMESTAMP.tv_sec && timeval.tv_usec == NO_TIMESTAMP.tv_usec;
54+
}
55+
56+
public:
57+
timeval tcp_syn_timestamp {NO_TIMESTAMP}; ///< Timestamp of last observed TCP SYN packet
58+
timeval tcp_synack_timestamp {NO_TIMESTAMP}; ///< Timestamp of last observed TCP SYNACK packet
59+
60+
RecordExtTCPRTT(int pluginID)
61+
: RecordExt(pluginID)
62+
{
63+
}
64+
65+
#ifdef WITH_NEMEA
66+
virtual void fill_unirec(ur_template_t* tmplt, void* record)
67+
{
68+
if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
69+
ur_set(tmplt, record, F_TCPRTT_TIME, std::numeric_limits<uint64_t>::max());
70+
return;
71+
}
72+
73+
const ur_time_t round_trip_time = ur_timediff(
74+
ur_time_from_sec_usec(tcp_synack_timestamp.tv_sec, tcp_synack_timestamp.tv_usec),
75+
ur_time_from_sec_usec(tcp_syn_timestamp.tv_sec, tcp_syn_timestamp.tv_usec));
76+
ur_set(tmplt, record, F_TCPRTT_TIME, round_trip_time);
77+
}
78+
79+
const char* get_unirec_tmplt() const { return TCPRTT_UNIREC_TEMPLATE; }
80+
81+
#endif // ifdef WITH_NEMEA
82+
83+
int fill_ipfix(uint8_t* buffer, int size) override
84+
{
85+
if (size < static_cast<ssize_t>(sizeof(uint64_t))) {
86+
return -1;
87+
}
88+
89+
if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
90+
*reinterpret_cast<uint64_t*>(buffer) = std::numeric_limits<uint64_t>::max();
91+
return static_cast<int>(sizeof(uint64_t));
92+
}
93+
94+
const uint64_t round_trip_time
95+
= timeval_to_msec(tcp_synack_timestamp) - timeval_to_msec(tcp_syn_timestamp);
96+
*reinterpret_cast<uint64_t*>(buffer) = round_trip_time;
97+
return static_cast<int>(sizeof(round_trip_time));
98+
}
99+
100+
const char** get_ipfix_tmplt() const
101+
{
102+
static const char* ipfix_template[] = {IPFIX_TLS_TEMPLATE(IPFIX_FIELD_NAMES) nullptr};
103+
104+
return ipfix_template;
105+
}
106+
107+
std::string get_text() const override
108+
{
109+
std::ostringstream out;
110+
111+
if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
112+
out << "tcprtt = UNKNOWN";
113+
} else {
114+
out << "tcprtt = "
115+
<< timeval_to_msec(tcp_synack_timestamp) - timeval_to_msec(tcp_syn_timestamp);
116+
}
117+
118+
return out.str();
119+
}
120+
};
121+
122+
class TCPRTTPlugin : public ProcessPlugin {
123+
public:
124+
TCPRTTPlugin(const std::string& params, int pluginID);
125+
126+
TCPRTTPlugin(const TCPRTTPlugin&) noexcept;
127+
128+
~TCPRTTPlugin() override = default;
129+
130+
void init(const char* params) override;
131+
132+
OptionsParser* get_parser() const override;
133+
134+
std::string get_name() const override;
135+
136+
RecordExtTCPRTT* get_ext() const override;
137+
138+
ProcessPlugin* copy();
139+
140+
int post_create(Flow& rec, const Packet& pkt) override;
141+
142+
int pre_update(Flow& rec, Packet& pkt) override;
143+
144+
private:
145+
void update_tcp_rtt_record(Flow& rec, const Packet& pkt) noexcept;
146+
147+
std::unique_ptr<RecordExtTCPRTT> m_prealloced_extension {get_ext()};
148+
};
149+
150+
} // namespace ipxp

0 commit comments

Comments
 (0)