ndpi: build against nDPI 4.12, 5.0 and 6.0

nDPI 5.0 removed the protocol bitmask, ndpi_extra_dissection_possible()
and the third argument of ndpi_detection_giveup(), and introduced a
classification state; nDPI 6.0 added a license argument to
ndpi_init_detection_module(). The plugin only built against nDPI 4.x.

Wrap these differences in a small header of static inline helpers
selected on NDPI_MAJOR so that ndpi.c stays version agnostic. On 5.0
and later a flow is final once classified with no extra dissection
pending, as in ndpiReader, or in monitoring state, where the
classification can no longer change. The packet cap now also applies
to classified flows still under extra dissection.

Ticket: 8077
pull/16354/head
TripleA150 1 month ago
parent 7ad9874c05
commit da7c5b409f

@ -7,7 +7,7 @@ ndpi_la_LIBADD = @NDPI_LIB@
# source directory.
ndpi_la_CFLAGS = -I../../rust/gen -I../../rust/dist
ndpi_la_SOURCES = ndpi.c
ndpi_la_SOURCES = ndpi.c ndpi-compat.h
install-exec-hook:
cd $(DESTDIR)$(pkglibdir) && $(RM) $(pkglib_LTLIBRARIES)

@ -0,0 +1,113 @@
/* Copyright (C) 2025 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* Compatibility layer over the parts of the nDPI API that differ between
* nDPI 4.12, 5.0 and 6.0, selected at compile time from NDPI_MAJOR so
* that ndpi.c stays version agnostic.
*/
#ifndef SURICATA_NDPI_COMPAT_H
#define SURICATA_NDPI_COMPAT_H
#include <stdbool.h>
#include <stdint.h>
#include "ndpi_api.h"
#if NDPI_MAJOR < 4 || (NDPI_MAJOR == 4 && NDPI_MINOR < 12)
#error "the nDPI plugin requires nDPI 4.12 or later"
#endif
/**
* \brief Allocate a detection module.
*
* nDPI 6.0 requires the embedding application to declare under which
* license it uses the library. Not-for-profit use enables every dissector.
*/
static inline struct ndpi_detection_module_struct *NdpiCompatInitModule(
struct ndpi_global_context *g_ctx)
{
#if NDPI_MAJOR >= 6
return ndpi_init_detection_module(g_ctx, NDPI_LICENSE_NOT_FOR_PROFIT_LGPL);
#else
return ndpi_init_detection_module(g_ctx);
#endif
}
/* nDPI 4.x needs every protocol enabled explicitly before finalization,
* later releases enable them by default and removed the bitmask. */
static inline void NdpiCompatEnableAllProtocols(struct ndpi_detection_module_struct *ndpi)
{
#if NDPI_MAJOR < 5
NDPI_PROTOCOL_BITMASK all;
NDPI_BITMASK_SET_ALL(all);
ndpi_set_protocol_detection_bitmask2(ndpi, &all);
#else
(void)ndpi;
#endif
}
/* Whether nDPI reached a final classification for the flow. Once it has,
* the plugin stops feeding it packets and the keywords start matching.
*
* On nDPI 5.0 and later NDPI_STATE_CLASSIFIED with no extra dissection
* pending is final, as in nDPI's ndpiReader. NDPI_STATE_MONITORING is
* treated as final too: the classification will not change and nDPI
* would only extract more metadata, so stopping there bounds the per
* packet cost at the expense of that metadata. */
static inline bool NdpiCompatClassificationFinal(struct ndpi_detection_module_struct *ndpi,
struct ndpi_flow_struct *flow, const ndpi_protocol *proto)
{
#if NDPI_MAJOR >= 5
(void)ndpi;
if (proto->state == NDPI_STATE_MONITORING)
return true;
return proto->state == NDPI_STATE_CLASSIFIED && flow->extra_packets_func == NULL;
#else
return ndpi_is_protocol_detected(*proto) != 0 && !ndpi_is_proto_unknown(proto->proto) &&
!ndpi_extra_dissection_possible(ndpi, flow);
#endif
}
/* Whether nDPI has no classification for the flow at all, in which case
* giving up on it goes through ndpi_detection_giveup() for a guess. */
static inline bool NdpiCompatIsUnclassified(const ndpi_protocol *proto)
{
#if NDPI_MAJOR >= 5
return proto->state != NDPI_STATE_CLASSIFIED && proto->state != NDPI_STATE_MONITORING;
#else
return ndpi_is_protocol_detected(*proto) == 0;
#endif
}
/* nDPI 5.0 dropped the output argument telling whether the protocol was
* guessed, the flow now carries it in protocol_was_guessed. */
static inline ndpi_protocol NdpiCompatGiveup(
struct ndpi_detection_module_struct *ndpi, struct ndpi_flow_struct *flow)
{
#if NDPI_MAJOR >= 5
return ndpi_detection_giveup(ndpi, flow);
#else
uint8_t protocol_was_guessed;
return ndpi_detection_giveup(ndpi, flow, &protocol_was_guessed);
#endif
}
#endif /* SURICATA_NDPI_COMPAT_H */

@ -32,7 +32,7 @@
#include "thread-storage.h"
#include "util-debug.h"
#include "ndpi_api.h"
#include "ndpi-compat.h"
static SCThreadStorageId thread_storage_id = { .id = -1 };
static SCFlowStorageId flow_storage_id = { .id = -1 };
@ -81,6 +81,20 @@ static inline struct NdpiFlowContext *NdpiGetFlowContext(const Flow *f)
return SCFlowGetStorageById(f, flow_storage_id);
}
/**
* Allocate and finalize a detection module with every protocol enabled.
*/
static struct ndpi_detection_module_struct *NdpiModuleNew(void)
{
struct ndpi_detection_module_struct *ndpi = NdpiCompatInitModule(NULL);
if (ndpi == NULL)
return NULL;
NdpiCompatEnableAllProtocols(ndpi);
ndpi_finalize_initialization(ndpi);
return ndpi;
}
static void ThreadStorageFree(void *ptr)
{
SCLogDebug("Free'ing nDPI thread storage");
@ -166,20 +180,20 @@ static void OnFlowUpdate(ThreadVars *tv, Flow *f, Packet *p, void *_data)
flowctx->detected_l7_protocol = ndpi_detection_process_packet(
threadctx->ndpi, flowctx->ndpi_flow, ip_ptr, ip_len, time_ms, NULL);
if (ndpi_is_protocol_detected(flowctx->detected_l7_protocol) != 0) {
if (!ndpi_is_proto_unknown(flowctx->detected_l7_protocol.proto)) {
if (!ndpi_extra_dissection_possible(threadctx->ndpi, flowctx->ndpi_flow))
flowctx->detection_completed = true;
}
if (NdpiCompatClassificationFinal(
threadctx->ndpi, flowctx->ndpi_flow, &flowctx->detected_l7_protocol)) {
flowctx->detection_completed = true;
} else {
uint16_t max_num_pkts = (flow_proto == IPPROTO_UDP) ? 8 : 24;
/* stop feeding nDPI after a few packets, taking its best guess
* for flows it could not classify */
const uint16_t max_num_pkts = (flow_proto == IPPROTO_UDP) ? 8 : 24;
if ((SCFlowGetToServerPacketCount(f) + SCFlowGetToClientPacketCount(f)) >
max_num_pkts) {
uint8_t proto_guessed;
flowctx->detected_l7_protocol =
ndpi_detection_giveup(threadctx->ndpi, flowctx->ndpi_flow, &proto_guessed);
if (NdpiCompatIsUnclassified(&flowctx->detected_l7_protocol)) {
flowctx->detected_l7_protocol =
NdpiCompatGiveup(threadctx->ndpi, flowctx->ndpi_flow);
}
flowctx->detection_completed = true;
}
}
@ -209,14 +223,10 @@ static void OnThreadInit(ThreadVars *tv, void *_data)
if (context == NULL) {
FatalError("Failed to allocate nDPI thread context");
}
context->ndpi = ndpi_init_detection_module(NULL);
context->ndpi = NdpiModuleNew();
if (context->ndpi == NULL) {
FatalError("Failed to initialize nDPI detection module");
}
NDPI_PROTOCOL_BITMASK protos;
NDPI_BITMASK_SET_ALL(protos);
ndpi_set_protocol_detection_bitmask2(context->ndpi, &protos);
ndpi_finalize_initialization(context->ndpi);
SCThreadSetStorageById(tv, thread_storage_id, context);
}
@ -272,17 +282,12 @@ static DetectnDPIProtocolData *DetectnDPIProtocolParse(const char *arg, bool neg
struct ndpi_detection_module_struct *ndpi_struct;
ndpi_master_app_protocol l7_protocol;
char *l7_protocol_name = (char *)arg;
NDPI_PROTOCOL_BITMASK all;
/* convert protocol name (string) to ID */
ndpi_struct = ndpi_init_detection_module(NULL);
ndpi_struct = NdpiModuleNew();
if (unlikely(ndpi_struct == NULL))
return NULL;
NDPI_BITMASK_SET_ALL(all);
ndpi_set_protocol_detection_bitmask2(ndpi_struct, &all);
ndpi_finalize_initialization(ndpi_struct);
l7_protocol = ndpi_get_protocol_by_name(ndpi_struct, l7_protocol_name);
ndpi_exit_detection_module(ndpi_struct);
@ -401,16 +406,11 @@ static DetectnDPIRiskData *DetectnDPIRiskParse(const char *arg, bool negate)
DetectnDPIRiskData *data;
struct ndpi_detection_module_struct *ndpi_struct;
ndpi_risk risk_mask;
NDPI_PROTOCOL_BITMASK all;
/* convert list of risk names (string) to mask */
ndpi_struct = ndpi_init_detection_module(NULL);
ndpi_struct = NdpiModuleNew();
if (unlikely(ndpi_struct == NULL))
return NULL;
NDPI_BITMASK_SET_ALL(all);
ndpi_set_protocol_detection_bitmask2(ndpi_struct, &all);
ndpi_finalize_initialization(ndpi_struct);
ndpi_exit_detection_module(ndpi_struct);
if (isdigit(arg[0]))

Loading…
Cancel
Save