From 09a3da0821c86429754fbea9fd45baf63ed21ec4 Mon Sep 17 00:00:00 2001 From: Alfredo Cardigliano Date: Mon, 4 Nov 2024 10:25:31 +0100 Subject: [PATCH] ndpi: initial implementation of nDPI plugin Ticket: #7231 --- configure.ac | 55 +++ doc/userguide/rules/index.rst | 2 + doc/userguide/rules/ndpi-protocol.rst | 43 ++ doc/userguide/rules/ndpi-risk.rst | 49 +++ plugins/Makefile.am | 4 + plugins/ndpi/Makefile.am | 13 + plugins/ndpi/ndpi.c | 541 ++++++++++++++++++++++++++ suricata.yaml.in | 1 + 8 files changed, 708 insertions(+) create mode 100644 doc/userguide/rules/ndpi-protocol.rst create mode 100644 doc/userguide/rules/ndpi-risk.rst create mode 100644 plugins/ndpi/Makefile.am create mode 100644 plugins/ndpi/ndpi.c diff --git a/configure.ac b/configure.ac index 93e728fc2246..7a91d024ba76 100644 --- a/configure.ac +++ b/configure.ac @@ -2310,6 +2310,57 @@ fi ]) AC_SUBST(RUST_FEATURES) +# nDPI support (no library checks for this stub) + NDPI_HOME= + AC_ARG_ENABLE(ndpi, + AS_HELP_STRING([--enable-ndpi], [Enable nDPI support]), + [enable_ndpi=$enableval],[enable_ndpi=no]) + AC_ARG_WITH([ndpi], + [ --with-ndpi= path to nDPI source tree.], + [NDPI_HOME="$withval"]) + + # Require --with-ndpi to be provided with an argument. + AS_IF([test "x$NDPI_HOME" = "xyes"], [ + AC_MSG_ERROR([--with-ndpi requires a path]) + exit 1 + ]) + + AS_IF([test "x$enable_dpi" = "xyes"], [ + if test "x$enable_shared" = "xno"; then + echo + echo " ERROR! ndpi cannot be enabled with --disable-shared" + echo + exit 1 + fi + ]) + + if test "x$enable_ndpi" = "xyes"; then + AC_MSG_CHECKING(for nDPI source) + if test "x$NDPI_HOME" != "x"; then + AC_MSG_RESULT(found in $NDPI_HOME) + NDPI_LIB=$NDPI_HOME/src/lib/libndpi.a + AC_MSG_CHECKING(for $NDPI_LIB) + if test -r $NDPI_LIB ; then : + AC_MSG_RESULT(found $NDPI_LIB) + fi + CPPFLAGS="${CPPFLAGS} -I$NDPI_HOME/src/include" + NDPI_LIB="$NDPI_HOME/src/lib/libndpi.a" + AC_SUBST([NDPI_LIB]) + else + AC_MSG_RESULT(not found) + enable_ndpi="no" + fi + fi + + if test "x$enable_ndpi" = "xyes"; then + AM_CONDITIONAL([BUILD_NDPI], [true]) + ndpi_comment="" + else + AM_CONDITIONAL([BUILD_NDPI], [false]) + ndpi_comment="#" + fi + AC_SUBST([ndpi_comment]) + AC_ARG_ENABLE(warnings, AS_HELP_STRING([--enable-warnings], [Enable supported C compiler warnings]),[enable_warnings=$enableval],[enable_warnings=no]) AS_IF([test "x$enable_warnings" = "xyes"], [ @@ -2531,6 +2582,7 @@ AC_CONFIG_FILES(examples/lib/simple/Makefile examples/lib/simple/Makefile.exampl AC_CONFIG_FILES(plugins/Makefile) AC_CONFIG_FILES(plugins/pfring/Makefile) AC_CONFIG_FILES(plugins/napatech/Makefile) +AC_CONFIG_FILES(plugins/ndpi/Makefile) AC_OUTPUT @@ -2587,6 +2639,9 @@ SURICATA_BUILD_CONF="Suricata Configuration: Plugin support (experimental): ${plugin_support} DPDK Bond PMD: ${enable_dpdk_bond_pmd} +Plugins: + nDPI ${enable_ndpi} + Development settings: Coccinelle / spatch: ${enable_coccinelle} Unit tests enabled: ${enable_unittests} diff --git a/doc/userguide/rules/index.rst b/doc/userguide/rules/index.rst index c8b586fecaa7..b96e21ba03b0 100644 --- a/doc/userguide/rules/index.rst +++ b/doc/userguide/rules/index.rst @@ -38,6 +38,8 @@ Suricata Rules smtp-keywords websocket-keywords app-layer + ndpi-protocol + ndpi-risk xbits noalert thresholding diff --git a/doc/userguide/rules/ndpi-protocol.rst b/doc/userguide/rules/ndpi-protocol.rst new file mode 100644 index 000000000000..f6ea4a6a0439 --- /dev/null +++ b/doc/userguide/rules/ndpi-protocol.rst @@ -0,0 +1,43 @@ +nDPI Protocol Keyword +===================== + +ndpi-protocol +------------- + +Match on the Layer-7 protocol detected by nDPI. + +Suricata should be compiled with the nDPI support and the ``ndpi`` +plugin must be loaded before it can be used. + +Example of configuring Suricata to be compiled with nDPI support: + +.. code-block:: console + + ./configure --enable-ndpi --with-ndpi=/home/user/nDPI + +Example of suricata.yaml configuration file to load the ``ndpi`` plugin:: + + plugins: + - /usr/lib/suricata/ndpi.so + +Syntax:: + + ndpi-protocol:[!]; + +Where protocol is one of the application protocols detected by nDPI. +Plase check ndpiReader -H for the full list. +It is possible to specify the transport protocol, the application +protocol, or both (dot-separated). + +Examples:: + + ndpi-protocol:HTTP; + ndpi-protocol:!TLS; + ndpi-protocol:TLS.YouTube; + +Here is an example of a rule matching TLS traffic on port 53: + +.. container:: example-rule + + alert tcp any any -> any 53 (msg:"TLS traffic over DNS standard port"; ndpi-protocol:TLS; sid:1;) + diff --git a/doc/userguide/rules/ndpi-risk.rst b/doc/userguide/rules/ndpi-risk.rst new file mode 100644 index 000000000000..41b36b700d00 --- /dev/null +++ b/doc/userguide/rules/ndpi-risk.rst @@ -0,0 +1,49 @@ +nDPI Risk Keyword +================= + +ndpi-risk +--------- + +Match on the flow risks detected by nDPI. Risks are potential issues detected +by nDPI during the packet dissection and include: + +- Known Proto on Non Std Port +- Binary App Transfer +- Self-signed Certificate +- Susp DGA Domain name +- Malware host contacted +- and many other... + +Suricata should be compiled with the nDPI support and the ``ndpi`` +plugin must be loaded before it can be used. + +Example of configuring Suricata to be compiled with nDPI support: + +.. code-block:: console + + ./configure --enable-ndpi --with-ndpi=/home/user/nDPI + +Example of suricata.yaml configuration file to load the ``ndpi`` plugin:: + + plugins: + - /usr/lib/suricata/ndpi.so + +Syntax:: + + ndpi-risk:[!]; + +Where risk is one (or multiple comma-separated) of the risk codes supported by +nDPI (e.g. NDPI_BINARY_APPLICATION_TRANSFER). Please check ndpiReader -H for the +full list. + +Examples:: + + ndpi-risk:NDPI_BINARY_APPLICATION_TRANSFER; + ndpi-risk:NDPI_TLS_OBSOLETE_VERSION,NDPI_TLS_WEAK_CIPHER; + +Here is an example of a rule matching HTTP traffic transferring a binary application: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Binary application transfer over HTTP"; ndpi-protocol:HTTP; ndpi-risk:NDPI_BINARY_APPLICATION_TRANSFER; sid:1;) + diff --git a/plugins/Makefile.am b/plugins/Makefile.am index cb7041326031..5d2dca1f5bc1 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -7,3 +7,7 @@ endif if BUILD_NAPATECH SUBDIRS += napatech endif + +if BUILD_NDPI +SUBDIRS += ndpi +endif diff --git a/plugins/ndpi/Makefile.am b/plugins/ndpi/Makefile.am new file mode 100644 index 000000000000..4d5dcbe4ed0d --- /dev/null +++ b/plugins/ndpi/Makefile.am @@ -0,0 +1,13 @@ +pkglib_LTLIBRARIES = ndpi.la + +ndpi_la_LDFLAGS = -module -avoid-version -shared +ndpi_la_LIBADD = @NDPI_LIB@ + +# Only required to find these headers when building plugins from the +# source directory. +ndpi_la_CFLAGS = -I../../rust/gen -I../../rust/dist + +ndpi_la_SOURCES = ndpi.c + +install-exec-hook: + cd $(DESTDIR)$(pkglibdir) && $(RM) $(pkglib_LTLIBRARIES) diff --git a/plugins/ndpi/ndpi.c b/plugins/ndpi/ndpi.c new file mode 100644 index 000000000000..55fbeb2c8336 --- /dev/null +++ b/plugins/ndpi/ndpi.c @@ -0,0 +1,541 @@ +/* Copyright (C) 2024 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. + */ + +/* License note: While this "glue" code to the nDPI library is GPLv2, + * nDPI is itself LGPLv3 which is known to be incompatible with the + * GPLv2. */ + +#include "suricata-common.h" +#include "suricata-plugin.h" + +#include "detect-engine-helper.h" +#include "detect-parse.h" +#include "flow-callbacks.h" +#include "flow-storage.h" +#include "output-eve.h" +#include "thread-callbacks.h" +#include "thread-storage.h" +#include "util-debug.h" + +#include "ndpi_api.h" + +static ThreadStorageId thread_storage_id = { .id = -1 }; +static FlowStorageId flow_storage_id = { .id = -1 }; +static int ndpi_protocol_keyword_id = -1; +static int ndpi_risk_keyword_id = -1; + +struct NdpiThreadContext { + struct ndpi_detection_module_struct *ndpi; +}; + +struct NdpiFlowContext { + struct ndpi_flow_struct *ndpi_flow; + ndpi_protocol detected_l7_protocol; + uint8_t detection_completed; +}; + +typedef struct DetectnDPIProtocolData_ { + ndpi_master_app_protocol l7_protocol; + uint8_t negated; +} DetectnDPIProtocolData; + +typedef struct DetectnDPIRiskData_ { + ndpi_risk risk_mask; /* uint64 */ + uint8_t negated; +} DetectnDPIRiskData; + +static void ThreadStorageFree(void *ptr) +{ + SCLogDebug("Free'ing nDPI thread storage"); + struct NdpiThreadContext *context = ptr; + ndpi_exit_detection_module(context->ndpi); + SCFree(context); +} + +static void FlowStorageFree(void *ptr) +{ + struct NdpiFlowContext *ctx = ptr; + ndpi_flow_free(ctx->ndpi_flow); + SCFree(ctx); +} + +static void OnFlowInit(ThreadVars *tv, Flow *f, const Packet *p, void *_data) +{ + struct NdpiFlowContext *flowctx = SCCalloc(1, sizeof(*flowctx)); + if (flowctx == NULL) { + FatalError("Failed to allocate nDPI flow context"); + } + + flowctx->ndpi_flow = ndpi_flow_malloc(SIZEOF_FLOW_STRUCT); + if (flowctx->ndpi_flow == NULL) { + FatalError("Failed to allocate nDPI flow"); + } + + memset(flowctx->ndpi_flow, 0, SIZEOF_FLOW_STRUCT); + flowctx->detection_completed = 0; + FlowSetStorageById(f, flow_storage_id, flowctx); +} + +static void OnFlowUpdate(ThreadVars *tv, Flow *f, Packet *p, void *_data) +{ + struct NdpiThreadContext *threadctx = ThreadGetStorageById(tv, thread_storage_id); + struct NdpiFlowContext *flowctx = FlowGetStorageById(f, flow_storage_id); + uint16_t ip_len = 0; + void *ip_ptr = NULL; + + if (!threadctx->ndpi || !flowctx->ndpi_flow) { + return; + } + + if (PacketIsIPv4(p)) { + const IPV4Hdr *ip4h = PacketGetIPv4(p); + ip_len = IPV4_GET_RAW_IPLEN(ip4h); + ip_ptr = (void *)PacketGetIPv4(p); + } else if (PacketIsIPv6(p)) { + const IPV6Hdr *ip6h = PacketGetIPv6(p); + ip_len = IPV6_HEADER_LEN + IPV6_GET_RAW_PLEN(ip6h); + ip_ptr = (void *)PacketGetIPv6(p); + } + + if (!flowctx->detection_completed && ip_ptr != NULL && ip_len > 0) { + uint64_t time_ms = ((uint64_t)p->ts.secs) * 1000 + p->ts.usecs / 1000; + + SCLogDebug("Performing nDPI detection..."); + + 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 = 1; + } + } else { + u_int16_t max_num_pkts = (f->proto == IPPROTO_UDP) ? 8 : 24; + + if ((f->todstpktcnt + f->tosrcpktcnt) > max_num_pkts) { + u_int8_t proto_guessed; + + flowctx->detected_l7_protocol = + ndpi_detection_giveup(threadctx->ndpi, flowctx->ndpi_flow, &proto_guessed); + flowctx->detection_completed = 1; + } + } + + if (flowctx->detection_completed) { + SCLogDebug("Detected protocol: %s | app protocol: %s | category: %s", + ndpi_get_proto_name( + threadctx->ndpi, flowctx->detected_l7_protocol.proto.master_protocol), + ndpi_get_proto_name( + threadctx->ndpi, flowctx->detected_l7_protocol.proto.app_protocol), + ndpi_category_get_name( + threadctx->ndpi, flowctx->detected_l7_protocol.category)); + } + } +} + +static void OnFlowFinish(ThreadVars *tv, Flow *f, void *_data) +{ + /* Nothing to do here, the storage API has taken care of cleaning + * up storage, just here for example purposes. */ + SCLogDebug("Flow %p is now finished", f); +} + +static void OnThreadInit(ThreadVars *tv, void *_data) +{ + struct NdpiThreadContext *context = SCCalloc(1, sizeof(*context)); + if (context == NULL) { + FatalError("Failed to allocate nDPI thread context"); + } + context->ndpi = ndpi_init_detection_module(NULL); + 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); + ThreadSetStorageById(tv, thread_storage_id, context); +} + +static int DetectnDPIProtocolPacketMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + const Flow *f = p->flow; + struct NdpiFlowContext *flowctx = FlowGetStorageById(f, flow_storage_id); + const DetectnDPIProtocolData *data = (const DetectnDPIProtocolData *)ctx; + bool r; + + SCEnter(); + + /* if the sig is PD-only we only match when PD packet flags are set */ + /* + if (s->type == SIG_TYPE_PDONLY && + (p->flags & (PKT_PROTO_DETECT_TS_DONE | PKT_PROTO_DETECT_TC_DONE)) == 0) { + SCLogDebug("packet %"PRIu64": flags not set", p->pcap_cnt); + SCReturnInt(0); + } + */ + + if (!flowctx->detection_completed) { + SCLogDebug("packet %" PRIu64 ": ndpi protocol not yet detected", p->pcap_cnt); + SCReturnInt(0); + } + + if (f == NULL) { + SCLogDebug("packet %" PRIu64 ": no flow", p->pcap_cnt); + SCReturnInt(0); + } + + r = ndpi_is_proto_equals(flowctx->detected_l7_protocol.proto, data->l7_protocol, false); + r = r ^ data->negated; + + if (r) { + SCLogDebug("ndpi protocol match on protocol = %u.%u (match %u)", + flowctx->detected_l7_protocol.proto.app_protocol, + flowctx->detected_l7_protocol.proto.master_protocol, + data->l7_protocol.app_protocol); + SCReturnInt(1); + } + SCReturnInt(0); +} + +static DetectnDPIProtocolData *DetectnDPIProtocolParse(const char *arg, bool negate) +{ + DetectnDPIProtocolData *data; + 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); + if (unlikely(ndpi_struct == NULL)) + return NULL; + + ndpi_struct = ndpi_init_detection_module(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); + + if (ndpi_is_proto_unknown(l7_protocol)) { + SCLogError("failure parsing nDPI protocol '%s'", l7_protocol_name); + return NULL; + } + + data = SCMalloc(sizeof(DetectnDPIProtocolData)); + if (unlikely(data == NULL)) + return NULL; + + memcpy(&data->l7_protocol, &l7_protocol, sizeof(ndpi_master_app_protocol)); + data->negated = negate; + + return data; +} + +static bool nDPIProtocolDataHasConflicts( + const DetectnDPIProtocolData *us, const DetectnDPIProtocolData *them) +{ + /* check for mix of negated and non negated */ + if (them->negated ^ us->negated) + return true; + + /* check for multiple non-negated */ + if (!us->negated) + return true; + + /* check for duplicate */ + if (ndpi_is_proto_equals(us->l7_protocol, them->l7_protocol, true)) + return true; + + return false; +} + +static int DetectnDPIProtocolSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + DetectnDPIProtocolData *data = NULL; + + data = DetectnDPIProtocolParse(arg, s->init_data->negated); + if (data == NULL) + goto error; + + SigMatch *tsm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + for (; tsm != NULL; tsm = tsm->next) { + if (tsm->type == ndpi_protocol_keyword_id) { + const DetectnDPIProtocolData *them = (const DetectnDPIProtocolData *)tsm->ctx; + + if (nDPIProtocolDataHasConflicts(data, them)) { + SCLogError("can't mix " + "positive ndpi-protocol match with negated"); + goto error; + } + } + } + + if (SigMatchAppendSMToList(de_ctx, s, ndpi_protocol_keyword_id, (SigMatchCtx *)data, + DETECT_SM_LIST_MATCH) == NULL) { + goto error; + } + return 0; + +error: + if (data != NULL) + SCFree(data); + return -1; +} + +static void DetectnDPIProtocolFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +static int DetectnDPIRiskPacketMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + const Flow *f = p->flow; + struct NdpiFlowContext *flowctx = FlowGetStorageById(f, flow_storage_id); + const DetectnDPIRiskData *data = (const DetectnDPIRiskData *)ctx; + bool r; + + SCEnter(); + + if (!flowctx->detection_completed) { + SCLogDebug("packet %" PRIu64 ": ndpi risks not yet detected", p->pcap_cnt); + SCReturnInt(0); + } + + if (f == NULL) { + SCLogDebug("packet %" PRIu64 ": no flow", p->pcap_cnt); + SCReturnInt(0); + } + + r = ((flowctx->ndpi_flow->risk & data->risk_mask) == data->risk_mask); + r = r ^ data->negated; + + if (r) { + SCLogDebug("ndpi risks match on risk bitmap = %" PRIu64 " (matching bitmap %" PRIu64 ")", + flowctx->ndpi_flow->risk, data->risk_mask); + SCReturnInt(1); + } + + SCReturnInt(0); +} + +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); + if (unlikely(ndpi_struct == NULL)) + return NULL; + + ndpi_struct = ndpi_init_detection_module(NULL); + NDPI_BITMASK_SET_ALL(all); + ndpi_set_protocol_detection_bitmask2(ndpi_struct, &all); + ndpi_finalize_initialization(ndpi_struct); + + if (isdigit(arg[0])) + risk_mask = atoll(arg); + else { + char *dup = SCStrdup(arg), *tmp, *token; + + NDPI_ZERO_BIT(risk_mask); + + if (dup != NULL) { + token = strtok_r(dup, ",", &tmp); + + while (token != NULL) { + ndpi_risk_enum risk_id = ndpi_code2risk(token); + if (risk_id >= NDPI_MAX_RISK) { + SCLogError("unrecognized risk '%s', " + "please check ndpiReader -H for valid risk codes", + token); + return NULL; + } + NDPI_SET_BIT(risk_mask, risk_id); + token = strtok_r(NULL, ",", &tmp); + } + + SCFree(dup); + } + } + + data = SCMalloc(sizeof(DetectnDPIRiskData)); + if (unlikely(data == NULL)) + return NULL; + + data->risk_mask = risk_mask; + data->negated = negate; + + return data; +} + +static bool nDPIRiskDataHasConflicts(const DetectnDPIRiskData *us, const DetectnDPIRiskData *them) +{ + /* check for duplicate */ + if (us->risk_mask == them->risk_mask) + return true; + + return false; +} + +static int DetectnDPIRiskSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + DetectnDPIRiskData *data = NULL; + + data = DetectnDPIRiskParse(arg, s->init_data->negated); + if (data == NULL) + goto error; + + SigMatch *tsm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + for (; tsm != NULL; tsm = tsm->next) { + if (tsm->type == ndpi_risk_keyword_id) { + const DetectnDPIRiskData *them = (const DetectnDPIRiskData *)tsm->ctx; + + if (nDPIRiskDataHasConflicts(data, them)) { + SCLogError("can't mix " + "positive ndpi-risk match with negated"); + goto error; + } + } + } + + if (SigMatchAppendSMToList(de_ctx, s, ndpi_risk_keyword_id, (SigMatchCtx *)data, + DETECT_SM_LIST_MATCH) == NULL) { + goto error; + } + return 0; + +error: + if (data != NULL) + SCFree(data); + return -1; +} + +static void DetectnDPIRiskFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +static void EveCallback(ThreadVars *tv, const Packet *p, Flow *f, JsonBuilder *jb, void *data) +{ + struct NdpiThreadContext *threadctx = ThreadGetStorageById(tv, thread_storage_id); + struct NdpiFlowContext *flowctx = FlowGetStorageById(f, flow_storage_id); + ndpi_serializer serializer; + char *buffer; + u_int32_t buffer_len; + + SCLogDebug("EveCallback: tv=%p, p=%p, f=%p", tv, p, f); + + if (f == NULL) + return; + + ndpi_init_serializer(&serializer, ndpi_serialization_format_inner_json); + + /* Use ndpi_dpi2json to get a JSON with nDPI metadata */ + ndpi_dpi2json(threadctx->ndpi, flowctx->ndpi_flow, flowctx->detected_l7_protocol, &serializer); + + buffer = ndpi_serializer_get_buffer(&serializer, &buffer_len); + + /* Inject the nDPI JSON to the JsonBuilder */ + jb_set_formatted(jb, buffer); + + ndpi_term_serializer(&serializer); +} + +static void NdpInitRiskKeyword(void) +{ + /* SCSigTableElmt and DetectHelperKeywordRegister don't yet + * support all the fields required to register the nDPI keywords, + * so we'll just register with an empty keyword specifier to get + * the ID, then fill in the ID. */ + SCSigTableElmt keyword = {}; + ndpi_protocol_keyword_id = DetectHelperKeywordRegister(&keyword); + SCLogDebug("Registered new ndpi-protocol keyword with ID %" PRIu32, ndpi_protocol_keyword_id); + + sigmatch_table[ndpi_protocol_keyword_id].name = "ndpi-protocol"; + sigmatch_table[ndpi_protocol_keyword_id].desc = "match on the detected nDPI protocol"; + sigmatch_table[ndpi_protocol_keyword_id].url = "/rules/ndpi-protocol.html"; + sigmatch_table[ndpi_protocol_keyword_id].Match = DetectnDPIProtocolPacketMatch; + sigmatch_table[ndpi_protocol_keyword_id].Setup = DetectnDPIProtocolSetup; + sigmatch_table[ndpi_protocol_keyword_id].Free = DetectnDPIProtocolFree; + sigmatch_table[ndpi_protocol_keyword_id].flags = + (SIGMATCH_QUOTES_OPTIONAL | SIGMATCH_HANDLE_NEGATION); + + ndpi_risk_keyword_id = DetectHelperKeywordRegister(&keyword); + SCLogDebug("Registered new ndpi-risk keyword with ID %" PRIu32, ndpi_risk_keyword_id); + + sigmatch_table[ndpi_risk_keyword_id].name = "ndpi-risk"; + sigmatch_table[ndpi_risk_keyword_id].desc = "match on the detected nDPI risk"; + sigmatch_table[ndpi_risk_keyword_id].url = "/rules/ndpi-risk.html"; + sigmatch_table[ndpi_risk_keyword_id].Match = DetectnDPIRiskPacketMatch; + sigmatch_table[ndpi_risk_keyword_id].Setup = DetectnDPIRiskSetup; + sigmatch_table[ndpi_risk_keyword_id].Free = DetectnDPIRiskFree; + sigmatch_table[ndpi_risk_keyword_id].flags = + (SIGMATCH_QUOTES_OPTIONAL | SIGMATCH_HANDLE_NEGATION); +} + +static void NdpiInit(void) +{ + SCLogDebug("Initializing nDPI plugin"); + + /* Register thread storage. */ + thread_storage_id = ThreadStorageRegister("ndpi", sizeof(void *), NULL, ThreadStorageFree); + if (thread_storage_id.id < 0) { + FatalError("Failed to register nDPI thread storage"); + } + + /* Register flow storage. */ + flow_storage_id = FlowStorageRegister("ndpi", sizeof(void *), NULL, FlowStorageFree); + if (flow_storage_id.id < 0) { + FatalError("Failed to register nDPI flow storage"); + } + + /* Register flow lifecycle callbacks. */ + SCFlowRegisterInitCallback(OnFlowInit, NULL); + SCFlowRegisterUpdateCallback(OnFlowUpdate, NULL); + + /* Not needed for nDPI, but exists for completeness. */ + SCFlowRegisterFinishCallback(OnFlowFinish, NULL); + + /* Register thread init callback. */ + SCThreadRegisterInitCallback(OnThreadInit, NULL); + + /* Register an EVE callback. */ + SCEveRegisterCallback(EveCallback, NULL); + + NdpInitRiskKeyword(); +} + +const SCPlugin PluginRegistration = { + .name = "ndpi", + .author = "Luca Deri", + .license = "GPLv3", + .Init = NdpiInit, +}; + +const SCPlugin *SCPluginRegister() +{ + return &PluginRegistration; +} diff --git a/suricata.yaml.in b/suricata.yaml.in index 7dd7cb588675..314ac13836e6 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -82,6 +82,7 @@ stats: plugins: @pfring_comment@- @prefix@/lib/@PACKAGE_NAME@/pfring.so @napatech_comment@- @prefix@/lib/@PACKAGE_NAME@/napatech.so + @ndpi_comment@- @prefix@/lib/@PACKAGE_NAME@/ndpi.so # - /path/to/plugin.so # Configure the type of alert (and other) logging you would like.