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 include/ipfixprobe/ipfix-elements.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ namespace ipxp {

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

#define TCP_RTT(F) F(8057, 904, 8, nullptr)

/**
* IPFIX Templates - list of elements
*
Expand Down Expand Up @@ -461,6 +463,8 @@ namespace ipxp {
F(MQTT_PUBLISH_FLAGS) \
F(MQTT_TOPICS)

#define IPFIX_TCP_RTT_TEMPLATE(F) F(TCP_RTT)

#define IPFIX_PSTATS_TEMPLATE(F) \
F(STATS_PCKT_SIZES) \
F(STATS_PCKT_TIMESTAMPS) \
Expand Down
1 change: 1 addition & 0 deletions pkg/rpm/ipfixprobe-msec.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ source /opt/rh/gcc-toolset-14/enable
%{_libdir}/ipfixprobe/process/libipfixprobe-process-passivedns.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssadetector.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssdp.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-tcp-rtt.so

%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache.so

Expand Down
1 change: 1 addition & 0 deletions pkg/rpm/ipfixprobe-nemea.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ source /opt/rh/gcc-toolset-14/enable
%{_libdir}/ipfixprobe/process/libipfixprobe-process-passivedns.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssadetector.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssdp.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-tcp-rtt.so

%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache.so

Expand Down
1 change: 1 addition & 0 deletions pkg/rpm/ipfixprobe.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ source /opt/rh/gcc-toolset-14/enable
%{_libdir}/ipfixprobe/process/libipfixprobe-process-passivedns.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssadetector.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-ssdp.so
%{_libdir}/ipfixprobe/process/libipfixprobe-process-tcp-rtt.so

%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache.so

Expand Down
1 change: 1 addition & 0 deletions src/plugins/process/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_subdirectory(smtp)
add_subdirectory(quic)
add_subdirectory(tls)
add_subdirectory(http)
add_subdirectory(tcpRtt)

if (ENABLE_PROCESS_EXPERIMENTAL)
add_subdirectory(sip)
Expand Down
31 changes: 31 additions & 0 deletions src/plugins/process/tcpRtt/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
project(ipfixprobe-process-tcp-rtt VERSION 1.0.0 DESCRIPTION "ipfixprobe-process-tcp-rtt plugin")

add_library(ipfixprobe-process-tcp-rtt MODULE
src/tcpRtt.cpp
src/tcpRtt.hpp
)

set_target_properties(ipfixprobe-process-tcp-rtt PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)

target_include_directories(ipfixprobe-process-tcp-rtt PRIVATE
${CMAKE_SOURCE_DIR}/include/
)

target_link_libraries(ipfixprobe-process-tcp-rtt PRIVATE
ipfixprobe-output-ipfix
)

if(ENABLE_NEMEA)
target_link_libraries(ipfixprobe-process-tcp-rtt PRIVATE
-Wl,--whole-archive ipfixprobe-nemea-fields -Wl,--no-whole-archive
unirec::unirec
trap::trap
)
endif()

install(TARGETS ipfixprobe-process-tcp-rtt
LIBRARY DESTINATION "${INSTALL_DIR_LIB}/ipfixprobe/process/"
)
Empty file.
107 changes: 107 additions & 0 deletions src/plugins/process/tcpRtt/src/tcpRtt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* @file
* @brief Plugin for accounting round trip time of tcp handshakes.
* @author Damir Zainullin <[email protected]>
*
* Copyright (c) 2025 CESNET
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include "tcpRtt.hpp"

#include <ipfixprobe/pluginFactory/pluginManifest.hpp>
#include <ipfixprobe/pluginFactory/pluginRegistrar.hpp>

namespace ipxp {

static const PluginManifest tcpRttPluginManifest = {
.name = "tcprtt",
.description = "Process plugin to obtain round trip time of TCP connection.",
.pluginVersion = "1.0.0",
.apiVersion = "1.0.0",
.usage =
[]() {
OptionsParser parser("tcprtt", "Calculate tcp rtt");
parser.usage(std::cout);
},
};

TCPRTTPlugin::TCPRTTPlugin(const std::string& params, int pluginID)
: ProcessPlugin(pluginID)
{
init(params.c_str());
}

OptionsParser* TCPRTTPlugin::get_parser() const
{
return new OptionsParser("tcprtt", "Calculate tcp rtt");
}

std::string TCPRTTPlugin::get_name() const
{
return "tcprtt";
}

RecordExtTCPRTT* TCPRTTPlugin::get_ext() const
{
return new RecordExtTCPRTT(m_pluginID);
}

void TCPRTTPlugin::init([[maybe_unused]] const char* params) {}

TCPRTTPlugin::TCPRTTPlugin(const TCPRTTPlugin& other) noexcept
: ProcessPlugin(other.m_pluginID)
{
}

ProcessPlugin* TCPRTTPlugin::copy()
{
return new TCPRTTPlugin(*this);
}

int TCPRTTPlugin::post_create(Flow& rec, const Packet& pkt)
{
if (m_prealloced_extension == nullptr) {
m_prealloced_extension.reset(get_ext());
}

if (pkt.ip_proto == IPPROTO_TCP) {
rec.add_extension(m_prealloced_extension.release());
}

update_tcp_rtt_record(rec, pkt);
return 0;
}

int TCPRTTPlugin::pre_update(Flow& rec, Packet& pkt)
{
update_tcp_rtt_record(rec, pkt);
return 0;
}

constexpr static inline bool is_tcp_syn(uint8_t tcp_flags) noexcept
{
return tcp_flags & 0b10;
}

constexpr static inline bool is_tcp_syn_ack(uint8_t tcp_flags) noexcept
{
return (tcp_flags & 0b10) && (tcp_flags & 0b10000);
}

void TCPRTTPlugin::update_tcp_rtt_record(Flow& rec, const Packet& pkt) noexcept
{
auto* extension = static_cast<RecordExtTCPRTT*>(rec.get_extension(m_pluginID));

if (extension != nullptr && is_tcp_syn_ack(pkt.tcp_flags)) {
extension->tcp_synack_timestamp = pkt.ts;
} else if (extension != nullptr && is_tcp_syn(pkt.tcp_flags)) {
extension->tcp_syn_timestamp = pkt.ts;
}
}

static const PluginRegistrar<TCPRTTPlugin, ProcessPluginFactory>
tcpRttRegistrar(tcpRttPluginManifest);

} // namespace ipxp
153 changes: 153 additions & 0 deletions src/plugins/process/tcpRtt/src/tcpRtt.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* @file
* @brief Plugin for accounting round trip time of tcp handshakes.
* @author Damir Zainullin <[email protected]>
*
* Copyright (c) 2025 CESNET
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#pragma once

#include <chrono>
#include <limits>
#include <memory>
#include <sstream>

#include <ipfixprobe/flowifc.hpp>
#include <ipfixprobe/ipfix-basiclist.hpp>
#include <ipfixprobe/ipfix-elements.hpp>
#include <ipfixprobe/packet.hpp>
#include <ipfixprobe/processPlugin.hpp>

#ifdef WITH_NEMEA
#include "fields.h"
#endif

using namespace std::chrono_literals;

namespace ipxp {

#define TCPRTT_UNIREC_TEMPLATE "TCPRTT_TIME"
UR_FIELDS(uint64 TCPRTT_TIME)

/**
* @brief Convert timeval struct to count of milliseconds since epoch
* @param timeval Timeval to convert
* @return Count of milliseconds since epoch
*/
constexpr static inline uint64_t timeval_to_msec(timeval timeval) noexcept
{
constexpr std::size_t MSEC_IN_SEC = std::chrono::milliseconds(1s).count();
constexpr std::size_t USEC_IN_MSEC = std::chrono::microseconds(1ms).count();
return timeval.tv_sec * MSEC_IN_SEC + timeval.tv_usec / USEC_IN_MSEC;
}

/**
* \brief Flow record extension header for storing observed handshake timestamps.
*/
struct RecordExtTCPRTT : public RecordExt {
private:
constexpr static timeval NO_TIMESTAMP = timeval {std::numeric_limits<time_t>::min(), 0};

constexpr inline static bool has_no_value(timeval timeval) noexcept
{
return timeval.tv_sec == NO_TIMESTAMP.tv_sec && timeval.tv_usec == NO_TIMESTAMP.tv_usec;
}

public:
timeval tcp_syn_timestamp {NO_TIMESTAMP}; ///< Timestamp of last observed TCP SYN packet
timeval tcp_synack_timestamp {NO_TIMESTAMP}; ///< Timestamp of last observed TCP SYNACK packet

RecordExtTCPRTT(int pluginID)
: RecordExt(pluginID)
{
}

#ifdef WITH_NEMEA
virtual void fill_unirec(ur_template_t* tmplt, void* record)
{
if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
ur_set(tmplt, record, F_TCPRTT_TIME, std::numeric_limits<uint64_t>::max());
return;
}

const ur_time_t round_trip_time = ur_timediff(
ur_time_from_sec_usec(tcp_synack_timestamp.tv_sec, tcp_synack_timestamp.tv_usec),
ur_time_from_sec_usec(tcp_syn_timestamp.tv_sec, tcp_syn_timestamp.tv_usec));
ur_set(tmplt, record, F_TCPRTT_TIME, round_trip_time);
}

const char* get_unirec_tmplt() const { return TCPRTT_UNIREC_TEMPLATE; }

#endif // ifdef WITH_NEMEA

int fill_ipfix(uint8_t* buffer, int size) override
{
if (size < static_cast<ssize_t>(sizeof(uint64_t))) {
return -1;
}

if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
*reinterpret_cast<uint64_t*>(buffer) = std::numeric_limits<uint64_t>::max();
return static_cast<int>(sizeof(uint64_t));
}

const uint64_t round_trip_time
= timeval_to_msec(tcp_synack_timestamp) - timeval_to_msec(tcp_syn_timestamp);
*reinterpret_cast<uint64_t*>(buffer) = round_trip_time;
return static_cast<int>(sizeof(round_trip_time));
}

const char** get_ipfix_tmplt() const
{
static const char* ipfix_template[] = {IPFIX_TLS_TEMPLATE(IPFIX_FIELD_NAMES) nullptr};

return ipfix_template;
}

std::string get_text() const override
{
std::ostringstream out;

if (has_no_value(tcp_syn_timestamp) || has_no_value(tcp_synack_timestamp)) {
out << "tcprtt = UNKNOWN";
} else {
out << "tcprtt = "
<< timeval_to_msec(tcp_synack_timestamp) - timeval_to_msec(tcp_syn_timestamp);
}

return out.str();
}
};

class TCPRTTPlugin : public ProcessPlugin {
public:
TCPRTTPlugin(const std::string& params, int pluginID);

TCPRTTPlugin(const TCPRTTPlugin&) noexcept;

~TCPRTTPlugin() override = default;

void init(const char* params) override;

OptionsParser* get_parser() const override;

std::string get_name() const override;

RecordExtTCPRTT* get_ext() const override;

ProcessPlugin* copy();

int post_create(Flow& rec, const Packet& pkt) override;

int pre_update(Flow& rec, Packet& pkt) override;

private:
void update_tcp_rtt_record(Flow& rec, const Packet& pkt) noexcept;

std::unique_ptr<RecordExtTCPRTT> m_prealloced_extension {get_ext()};
};

} // namespace ipxp
1 change: 1 addition & 0 deletions tests/functional/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_process_plugin_test(QuicProcessPlugin quic quic_initial-sample.pcap)
add_process_plugin_test(SmtpProcessPlugin smtp smtp.pcap)
add_process_plugin_test(SsadetectorProcessPlugin ssadetector ovpn.pcap)
add_process_plugin_test(SsdpProcessPlugin ssdp ssdp.pcap)
add_process_plugin_test(TcpRttProcessPlugin tcprtt rtsp.pcap)
add_process_plugin_test(TlsProcessPlugin tls tls.pcap)
add_process_plugin_test(VlanProcessPlugin vlan vlan.pcap)
add_process_plugin_test(WgProcessPlugin wg wg.pcap)
Expand Down
3 changes: 3 additions & 0 deletions tests/functional/outputs/tcprtt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
82.211.92.253,81.131.231.67,1124,471,0,1652,2005-07-03T09:52:37.027000,2005-07-03T09:52:45.019000,bc:df:20:00:02:00,00:00:02:00:00:00,6,5,554,3925,0,6,27,27
82.211.92.253,81.131.231.67,646,431,0,1238,2005-07-03T09:52:43.781000,2005-07-03T09:52:49.337000,bc:df:20:00:02:00,00:00:02:00:00:00,5,4,554,3937,0,6,27,27
ipaddr DST_IP,ipaddr SRC_IP,uint64 BYTES,uint64 BYTES_REV,uint64 LINK_BIT_FIELD,uint64 TCPRTT_TIME,time TIME_FIRST,time TIME_LAST,macaddr DST_MAC,macaddr SRC_MAC,uint32 PACKETS,uint32 PACKETS_REV,uint16 DST_PORT,uint16 SRC_PORT,uint8 DIR_BIT_FIELD,uint8 PROTOCOL,uint8 TCP_FLAGS,uint8 TCP_FLAGS_REV