From: Alfredo Cardigliano Date: Tue, 7 Nov 2017 10:49:47 +0000 (+0100) Subject: pfring: hw bypass support X-Git-Tag: suricata-4.1.0-beta1~561 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b6baafb3e3be5bef3dc6e027a5e64f8b73c33dc5;p=thirdparty%2Fsuricata.git pfring: hw bypass support This patch adds support for hw bypass by enabling flow offload in the network card (when supported) and implementing the BypassPacketsFlow callback. Hw bypass support is disabled by default, and can be enabled by setting "bypass: yes" in the pfring interface configuration section in suricata.yaml. --- diff --git a/configure.ac b/configure.ac index c0c76463a3..54ee1f6d7b 100644 --- a/configure.ac +++ b/configure.ac @@ -1185,6 +1185,24 @@ exit 1 fi fi + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE([[ + #include + #ifndef PF_RING_FLOW_OFFLOAD + # error PF_RING_FLOW_OFFLOAD not defined + #endif + ]])], + [ + AC_DEFINE([HAVE_PF_RING_FLOW_OFFLOAD], [1], [PF_RING bypass support enabled]) + ], + [ + echo + echo " Warning! Pfring hw bypass not supported by this library version < 7," + echo " please upgrade to a newer version to use this feature." + echo + echo " Continuing for now with hw bypass support disabled..." + echo + ]) else if test "x$enable_pfring" = "xyes"; then echo diff --git a/src/decode.h b/src/decode.h index 16c54a28b0..d3d5b21a92 100644 --- a/src/decode.h +++ b/src/decode.h @@ -69,6 +69,7 @@ enum PktSrcEnum { #include "source-af-packet.h" #include "source-mpipe.h" #include "source-netmap.h" +#include "source-pfring.h" #include "action-globals.h" @@ -460,6 +461,9 @@ typedef struct Packet_ #ifdef HAVE_NETMAP NetmapPacketVars netmap_v; #endif +#ifdef HAVE_PFRING + PfringPacketVars pfring_v; +#endif /** libpcap vars: shared by Pcap Live mode and Pcap File mode */ PcapPacketVars pcap_v; diff --git a/src/runmode-pfring.c b/src/runmode-pfring.c index 3e0fb3f25e..1a1683dc56 100644 --- a/src/runmode-pfring.c +++ b/src/runmode-pfring.c @@ -195,6 +195,7 @@ static void *ParsePfringConfig(const char *iface) cluster_type default_ctype = CLUSTER_ROUND_ROBIN; int getctype = 0; const char *bpf_filter = NULL; + int bool_val; if (unlikely(pfconf == NULL)) { return NULL; @@ -354,6 +355,19 @@ static void *ParsePfringConfig(const char *iface) } } + if (ConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &bool_val) == 1) { + if (bool_val) { +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + SCLogConfig("Enabling bypass support in PF_RING for iface %s (if supported by underlying hw)", pfconf->iface); + pfconf->flags |= PFRING_CONF_FLAGS_BYPASS; +#else + SCLogError(SC_ERR_BYPASS_NOT_SUPPORTED, "Bypass is not supported by this Pfring version, please upgrade"); + SCFree(pfconf); + return NULL; +#endif + } + } + return pfconf; } diff --git a/src/source-pfring.c b/src/source-pfring.c index ed387583c2..b423778c73 100644 --- a/src/source-pfring.c +++ b/src/source-pfring.c @@ -127,9 +127,9 @@ static SCMutex pfring_bpf_set_filter_lock = SCMUTEX_INITIALIZER; #define LIBPFRING_REENTRANT 0 #define LIBPFRING_WAIT_FOR_INCOMING 1 -typedef enum { - PFRING_FLAGS_ZERO_COPY = 0x1 -} PfringThreadVarsFlags; +/* PfringThreadVars flags */ +#define PFRING_FLAGS_ZERO_COPY (1 << 0) +#define PFRING_FLAGS_BYPASS (1 << 1) /** * \brief Structure to hold thread specific variables. @@ -145,6 +145,7 @@ typedef struct PfringThreadVars_ uint16_t capture_kernel_packets; uint16_t capture_kernel_drops; + uint16_t capture_bypassed; uint32_t flags; @@ -209,10 +210,19 @@ static inline void PfringDumpCounters(PfringThreadVars *ptv) * to the interface counter */ uint64_t th_pkts = StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_packets); uint64_t th_drops = StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_drops); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + uint64_t th_bypassed = StatsGetLocalCounterValue(ptv->tv, ptv->capture_bypassed); +#endif SC_ATOMIC_ADD(ptv->livedev->pkts, pfring_s.recv - th_pkts); SC_ATOMIC_ADD(ptv->livedev->drop, pfring_s.drop - th_drops); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + SC_ATOMIC_ADD(ptv->livedev->bypassed, pfring_s.shunt - th_bypassed); +#endif StatsSetUI64(ptv->tv, ptv->capture_kernel_packets, pfring_s.recv); StatsSetUI64(ptv->tv, ptv->capture_kernel_drops, pfring_s.drop); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + StatsSetUI64(ptv->tv, ptv->capture_bypassed, pfring_s.shunt); +#endif } } @@ -283,6 +293,42 @@ static inline void PfringProcessPacket(void *user, struct pfring_pkthdr *h, Pack SET_PKT_LEN(p, h->caplen); } +#ifdef HAVE_PF_RING_FLOW_OFFLOAD +/** + * \brief Pfring bypass callback function + * + * \param p a Packet to use information from to trigger bypass + * \return 1 if bypass is successful, 0 if not + */ +static int PfringBypassCallback(Packet *p) +{ + hw_filtering_rule r; + + /* Only bypass TCP and UDP */ + if (!(PKT_IS_TCP(p) || PKT_IS_UDP(p))) { + return 0; + } + + /* Bypassing tunneled packets is currently not supported */ + if (IS_TUNNEL_PKT(p)) { + return 0; + } + + r.rule_family_type = generic_flow_id_rule; + r.rule_family.flow_id_rule.action = flow_drop_rule; + r.rule_family.flow_id_rule.thread = 0; + r.rule_family.flow_id_rule.flow_id = p->pfring_v.flow_id; + + SCLogDebug("Bypass set for flow ID = %u", p->pfring_v.flow_id); + + if (pfring_add_hw_rule(p->pfring_v.ptv->pd, &r) < 0) { + return 0; + } + + return 1; +} +#endif + /** * \brief Recieves packets from an interface via libpfring. * @@ -353,6 +399,14 @@ TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot) * reset it here */ PACKET_PROFILING_RESTART(p); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + if (ptv->flags & PFRING_FLAGS_BYPASS) { + p->pfring_v.flow_id = hdr.extended_hdr.pkt_hash; /* pkt hash contains the flow id in this configuration */ + p->pfring_v.ptv = ptv; + p->BypassPacketsFlow = PfringBypassCallback; + } +#endif + /* Check for Zero-copy mode */ if (ptv->flags & PFRING_FLAGS_ZERO_COPY) { PacketSetData(p, pkt_buffer, hdr.caplen); @@ -493,6 +547,13 @@ TmEcode ReceivePfringThreadInit(ThreadVars *tv, const void *initdata, void **dat } } +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + if (pfconf->flags & PFRING_CONF_FLAGS_BYPASS) { + opflag |= PF_RING_FLOW_OFFLOAD | PF_RING_FLOW_OFFLOAD_NOUPDATES; + ptv->flags |= PFRING_FLAGS_BYPASS; + } +#endif + ptv->pd = pfring_open(ptv->interface, (uint32_t)default_packet_size, opflag); if (ptv->pd == NULL) { SCLogError(SC_ERR_PF_RING_OPEN,"Failed to open %s: pfring_open error." @@ -561,6 +622,10 @@ TmEcode ReceivePfringThreadInit(ThreadVars *tv, const void *initdata, void **dat ptv->tv); ptv->capture_kernel_drops = StatsRegisterCounter("capture.kernel_drops", ptv->tv); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + ptv->capture_bypassed = StatsRegisterCounter("capture.bypassed", + ptv->tv); +#endif /* A bit strange to have this here but we only have vlan information * during reading so we need to know if we want to keep vlan during @@ -613,6 +678,13 @@ void ReceivePfringThreadExitStats(ThreadVars *tv, void *data) StatsGetLocalCounterValue(tv, ptv->capture_kernel_packets), StatsGetLocalCounterValue(tv, ptv->capture_kernel_drops)); SCLogPerf("(%s) Packets %" PRIu64 ", bytes %" PRIu64 "", tv->name, ptv->pkts, ptv->bytes); +#ifdef HAVE_PF_RING_FLOW_OFFLOAD + if (ptv->flags & PFRING_FLAGS_BYPASS) { + SCLogPerf("(%s) Bypass: Packets %" PRIu64 "", + tv->name, + StatsGetLocalCounterValue(tv, ptv->capture_bypassed)); + } +#endif } /** diff --git a/src/source-pfring.h b/src/source-pfring.h index 9871f458f6..dc4535556d 100644 --- a/src/source-pfring.h +++ b/src/source-pfring.h @@ -31,9 +31,11 @@ #include #endif -typedef enum { - PFRING_CONF_FLAGS_CLUSTER = 0x1 -} PfringIfaceConfigFlags; +typedef struct PfringThreadVars_ PfringThreadVars; + +/* PfringIfaceConfig flags */ +#define PFRING_CONF_FLAGS_CLUSTER (1 << 0) +#define PFRING_CONF_FLAGS_BYPASS (1 << 1) typedef struct PfringIfaceConfig_ { @@ -55,6 +57,16 @@ typedef struct PfringIfaceConfig_ void (*DerefFunc)(void *); } PfringIfaceConfig; +/** + * \brief per packet Pfring vars + * + * This structure is used to pass packet metadata in callbacks. + */ +typedef struct PfringPacketVars_ +{ + PfringThreadVars *ptv; + uint32_t flow_id; +} PfringPacketVars; void TmModuleReceivePfringRegister (void); diff --git a/src/util-device.h b/src/util-device.h index 5c4e76c09f..3dd6345671 100644 --- a/src/util-device.h +++ b/src/util-device.h @@ -43,6 +43,7 @@ typedef struct LiveDevice_ { int ignore_checksum; SC_ATOMIC_DECLARE(uint64_t, pkts); SC_ATOMIC_DECLARE(uint64_t, drop); + SC_ATOMIC_DECLARE(uint64_t, bypassed); SC_ATOMIC_DECLARE(uint64_t, invalid_checksums); TAILQ_ENTRY(LiveDevice_) next; diff --git a/src/util-error.c b/src/util-error.c index c8ad0cf42a..1e4075cd83 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -342,6 +342,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_WARN_EVENT_DROPPED); CASE_CODE (SC_ERR_NO_REDIS_ASYNC); CASE_CODE (SC_ERR_REDIS_CONFIG); + CASE_CODE (SC_ERR_BYPASS_NOT_SUPPORTED); } return "UNKNOWN_ERROR"; diff --git a/src/util-error.h b/src/util-error.h index 94f67d14b2..cc73272a1f 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -331,7 +331,8 @@ typedef enum { SC_WARN_LOG_CF_TOO_MANY_NODES, SC_WARN_EVENT_DROPPED, SC_ERR_NO_REDIS_ASYNC, - SC_ERR_REDIS_CONFIG + SC_ERR_REDIS_CONFIG, + SC_ERR_BYPASS_NOT_SUPPORTED } SCError; const char *SCErrorToString(SCError); diff --git a/suricata.yaml.in b/suricata.yaml.in index c7fc6bd16e..04055abf05 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1620,6 +1620,12 @@ pfring: cluster-type: cluster_flow # bpf filter for this interface #bpf-filter: tcp + + # If bypass is set then the PF_RING hw bypass is activated, when supported + # by the interface in use. Suricata will instruct the interface to bypass + # all future packets for a flow that need to be bypassed. + #bypass: yes + # Choose checksum verification mode for the interface. At the moment # of the capture, some packets may be with an invalid checksum due to # offloading to the network card of the checksum computation.