#ifndef __SOURCE_PFRING_H__
#define __SOURCE_PFRING_H__
#define PFRING_IFACE_NAME_LENGTH 48
typedef struct PfringThreadVars_ PfringThreadVars;
/* PfringIfaceConfig flags */
#define PFRING_CONF_FLAGS_CLUSTER (1 << 0)
#define PFRING_CONF_FLAGS_BYPASS (1 << 1)
// 用于在PF_RING初始化过程中配置接口相关的参数,并在需要时管理其内存和资源的释放。
typedef struct PfringIfaceConfig_
{
uint32_t flags; // 标志位
int cluster_id; // 集群ID
unsigned int ctype; // 集群类型
char iface[PFRING_IFACE_NAME_LENGTH]; // 接口名
int threads; // 线程数量
char *bpf_filter; // BPF过滤器
ChecksumValidationMode checksum_mode; // 校验和验证模式
SC_ATOMIC_DECLARE(unsigned int, ref); // 原子引用计数
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);
void TmModuleDecodePfringRegister (void);
int PfringConfGetThreads(void);
void PfringLoadConfig(void);
/* We don't have to use an enum that sucks in our code */
#define CLUSTER_FLOW 0
#define CLUSTER_ROUND_ROBIN 1
#define CLUSTER_FLOW_5_TUPLE 4
#endif /* __SOURCE_PFRING_H__ */
#include "suricata-common.h"
#include "suricata.h"
#include "conf.h"
#include "decode.h"
#include "packet-queue.h"
#include "threads.h"
#include "threadvars.h"
#include "tm-queuehandlers.h"
#include "tm-threads.h"
#include "source-pfring.h"
#include "util-debug.h"
#include "util-checksum.h"
#include "util-privs.h"
#include "util-device.h"
#include "util-host-info.h"
#include "runmodes.h"
#include "util-profiling.h"
TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot);
TmEcode PfringBreakLoop(ThreadVars *tv, void *data);
TmEcode ReceivePfringThreadInit(ThreadVars *, const void *, void **);
void ReceivePfringThreadExitStats(ThreadVars *, void *);
TmEcode ReceivePfringThreadDeinit(ThreadVars *, void *);
TmEcode DecodePfringThreadInit(ThreadVars *, const void *, void **);
TmEcode DecodePfring(ThreadVars *, Packet *, void *);
TmEcode DecodePfringThreadDeinit(ThreadVars *tv, void *data);
extern int max_pending_packets;
#ifndef HAVE_PFRING
/*Handle cases where we don't have PF_RING support built-in*/
TmEcode NoPfringSupportExit(ThreadVars *, const void *, void **);
void TmModuleReceivePfringRegister (void)
{
tmm_modules[TMM_RECEIVEPFRING].name = "ReceivePfring";
tmm_modules[TMM_RECEIVEPFRING].ThreadInit = NoPfringSupportExit;
tmm_modules[TMM_RECEIVEPFRING].Func = NULL;
tmm_modules[TMM_RECEIVEPFRING].ThreadExitPrintStats = NULL;
tmm_modules[TMM_RECEIVEPFRING].ThreadDeinit = NULL;
tmm_modules[TMM_RECEIVEPFRING].cap_flags = SC_CAP_NET_ADMIN | SC_CAP_NET_RAW |
SC_CAP_NET_BIND_SERVICE | SC_CAP_NET_BROADCAST;
tmm_modules[TMM_RECEIVEPFRING].flags = TM_FLAG_RECEIVE_TM;
}
void TmModuleDecodePfringRegister (void)
{
tmm_modules[TMM_DECODEPFRING].name = "DecodePfring";
tmm_modules[TMM_DECODEPFRING].ThreadInit = NoPfringSupportExit;
tmm_modules[TMM_DECODEPFRING].Func = NULL;
tmm_modules[TMM_DECODEPFRING].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODEPFRING].ThreadDeinit = NULL;
tmm_modules[TMM_DECODEPFRING].cap_flags = 0;
tmm_modules[TMM_DECODEPFRING].flags = TM_FLAG_DECODE_TM;
}
/**
* \brief this function prints an error message and exits.
* \param tv pointer to ThreadVars
* \param initdata pointer to the interface passed from the user
* \param data pointer gets populated with PfringThreadVars
*/
TmEcode NoPfringSupportExit(ThreadVars *tv, const void *initdata, void **data)
{
SCLogError(SC_ERR_NO_PF_RING,"Error creating thread %s: you do not have support for pfring "
"enabled please recompile with --enable-pfring", tv->name);
exit(EXIT_FAILURE);
}
#else /* implied we do have PF_RING support */
#include <pfring.h>
/** protect pfring_set_bpf_filter, as it is not thread safe */
static SCMutex pfring_bpf_set_filter_lock = SCMUTEX_INITIALIZER;
/* XXX replace with user configurable options */
#define LIBPFRING_PROMISC 1
#define LIBPFRING_REENTRANT 0
#define LIBPFRING_WAIT_FOR_INCOMING 1
/* PfringThreadVars flags */
#define PFRING_FLAGS_ZERO_COPY (1 << 0)
#define PFRING_FLAGS_BYPASS (1 << 1)
/**
* \brief Structure to hold thread specific variables.
*/
// 用于存储与PF_RING线程相关的变量和信息。
struct PfringThreadVars_
{
pfring *pd; // PF_RING库的线程句柄
uint64_t bytes; // 捕获的字节数
uint64_t pkts; // 捕获的数据包数
uint16_t capture_kernel_packets; // 内核捕获的数据包数
uint16_t capture_kernel_drops; // 内核丢弃的数据包数
uint16_t capture_bypassed; // 绕过的数据包数
uint32_t flags; // 标志位
ThreadVars *tv; // 线程变量
TmSlot *slot; // 槽变量
int vlan_in_ext_header; // VLAN信息是否在扩展头中
int threads; // 线程数量
cluster_type ctype; // 集群类型
uint8_t cluster_id; // 集群ID
char *interface; // 接口名
LiveDevice *livedev; // PF_RING库使用的Live设备,指计算机系统中的网络接口,例如网卡。
char *bpf_filter; // BPF过滤器
ChecksumValidationMode checksum_mode; // 校验和验证模式
bool vlan_hdr_warned; // 是否发出了关于VLAN头的警告
};
/**
* \brief Registration Function for RecievePfring.
* \todo Unit tests are needed for this module.
*/
void TmModuleReceivePfringRegister (void)
{
tmm_modules[TMM_RECEIVEPFRING].name = "ReceivePfring";
tmm_modules[TMM_RECEIVEPFRING].ThreadInit = ReceivePfringThreadInit;
tmm_modules[TMM_RECEIVEPFRING].Func = NULL;
tmm_modules[TMM_RECEIVEPFRING].PktAcqLoop = ReceivePfringLoop;
tmm_modules[TMM_RECEIVEPFRING].PktAcqBreakLoop = PfringBreakLoop;
tmm_modules[TMM_RECEIVEPFRING].ThreadExitPrintStats = ReceivePfringThreadExitStats;
tmm_modules[TMM_RECEIVEPFRING].ThreadDeinit = ReceivePfringThreadDeinit;
tmm_modules[TMM_RECEIVEPFRING].flags = TM_FLAG_RECEIVE_TM;
}
/**
* \brief Registration Function for DecodePfring.
* \todo Unit tests are needed for this module.
*/
void TmModuleDecodePfringRegister (void)
{
tmm_modules[TMM_DECODEPFRING].name = "DecodePfring";
tmm_modules[TMM_DECODEPFRING].ThreadInit = DecodePfringThreadInit;
tmm_modules[TMM_DECODEPFRING].Func = DecodePfring;
tmm_modules[TMM_DECODEPFRING].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODEPFRING].ThreadDeinit = DecodePfringThreadDeinit;
tmm_modules[TMM_DECODEPFRING].flags = TM_FLAG_DECODE_TM;
}
static inline void PfringDumpCounters(PfringThreadVars *ptv)
{
pfring_stat pfring_s;
if (likely((pfring_stats(ptv->pd, &pfring_s) >= 0))) {
/* pfring counter is per socket and is not cleared after read.
* So to get the number of packet on the interface we can add
* the newly seen packets and drops for this thread and add it
* 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);
SC_ATOMIC_ADD(ptv->livedev->pkts, pfring_s.recv - th_pkts);
SC_ATOMIC_ADD(ptv->livedev->drop, pfring_s.drop - th_drops);
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
if (ptv->flags & PFRING_FLAGS_BYPASS) {
uint64_t th_bypassed = StatsGetLocalCounterValue(ptv->tv, ptv->capture_bypassed);
SC_ATOMIC_ADD(ptv->livedev->bypassed, pfring_s.shunt - th_bypassed);
StatsSetUI64(ptv->tv, ptv->capture_bypassed, pfring_s.shunt);
}
#endif
}
}
/**
* \brief Pfring Packet Process function.
*
* This function fills in our packet structure from libpfring.
* From here the packets are picked up by the DecodePfring thread.
*
* \param user pointer to PfringThreadVars
* \param h pointer to pfring packet header
* \param p pointer to the current packet
*/
//这段代码的目的是将从 PF_RING 接收到的数据包的各种信息(如长度、时间戳、校验和等)提取并设置到 Suricata 的 Packet 对象中,以便后续的处理和分析。
static inline void PfringProcessPacket(void *user, struct pfring_pkthdr *h, Packet *p)
{
PfringThreadVars *ptv = (PfringThreadVars *)user;
// 将接收的数据包的长度(caplen 表示捕获的数据包长度)累加到 PfringThreadVars 结构体中的 bytes 变量中,用于统计字节数。
ptv->bytes += h->caplen;
// 将接收的数据包的数量累加到 PfringThreadVars 结构体中的 pkts 变量中,用于统计数据包数量。
ptv->pkts++;
p->livedev = ptv->livedev;
/* PF_RING may fail to set timestamp */
if (h->ts.tv_sec == 0) {
gettimeofday((struct timeval *)&h->ts, NULL);
}
p->ts.tv_sec = h->ts.tv_sec;
p->ts.tv_usec = h->ts.tv_usec;
/* PF_RING all packets are marked as a link type of ethernet
* so that is what we do here. */
// 将数据包的数据链路类型(datalink)设置为以太网类型,即 LINKTYPE_ETHERNET。
p->datalink = LINKTYPE_ETHERNET;
/* In the past, we needed this vlan handling in cases
* where the vlan header was stripped from the raw packet.
* With modern (at least >= 6) versions of PF_RING, the
* 'copy_data_to_ring' function (kernel/pf_ring.c) makes
* sure that if the hardware stripped the vlan header,
* it is put back by PF_RING.
*
* PF_RING should put it back in all cases, but as a extra
* precaution keep the check here. If the vlan header is
* part of the raw packet, the vlan_offset will be set.
* So if it is not set, use the parsed info from PF_RING's
* extended header.
*/
// 检查是否需要处理 VLAN 头信息。
//如果 PF_RING 的扩展头中未包含 VLAN 头信息(vlan_offset 为0)但实际数据中存在 VLAN ID,
// 则将 VLAN 信息提取出来并在数据包对象中设置。
if (ptv->vlan_in_ext_header &&
h->extended_hdr.parsed_pkt.offset.vlan_offset == 0 &&
h->extended_hdr.parsed_pkt.vlan_id)
{
p->vlan_id[0] = h->extended_hdr.parsed_pkt.vlan_id & 0x0fff;
p->vlan_idx = 1;
if (!ptv->vlan_hdr_warned) {
SCLogWarning(SC_ERR_PF_RING_VLAN, "no VLAN header in the raw "
"packet. See #2355.");
ptv->vlan_hdr_warned = true;
}
}
// 根据配置的校验模式,对数据包的校验和进行处理。
// 校验模式可以是 CHECKSUM_VALIDATION_RXONLY、CHECKSUM_VALIDATION_DISABLE、CHECKSUM_VALIDATION_AUTO 等。
// 根据不同的模式,可能会标记数据包的 PKT_IGNORE_CHECKSUM 标志以忽略校验和校验。
switch (ptv->checksum_mode) {
case CHECKSUM_VALIDATION_RXONLY:
if (h->extended_hdr.rx_direction == 0) {
p->flags |= PKT_IGNORE_CHECKSUM;
}
break;
case CHECKSUM_VALIDATION_DISABLE:
p->flags |= PKT_IGNORE_CHECKSUM;
break;
case CHECKSUM_VALIDATION_AUTO:
if (ChecksumAutoModeCheck(ptv->pkts,
SC_ATOMIC_GET(ptv->livedev->pkts),
SC_ATOMIC_GET(ptv->livedev->invalid_checksums))) {
ptv->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
p->flags |= PKT_IGNORE_CHECKSUM;
}
break;
default:
break;
}
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)
{
// 在网络数据包处理中,硬件过滤规则(Hardware Filtering Rule)是一种定义在网络硬件上的条件,
// 用于决定哪些数据包可以被接收、处理或转发。
// 这些规则允许网络管理员配置硬件设备,
// 以根据特定的条件对数据包进行筛选、分类和处理,从而实现数据包级别的控制和管理。
// 硬件过滤规则通常涉及以下信息:
// 1. **匹配条件(Matching Criteria)**:这是规则要匹配的数据包属性,
// 如源地址、目标地址、协议、端口等。根据这些条件,硬件设备将决定是否对数据包进行操作。
// 2. **操作(Actions)**:根据匹配条件,规则可以执行不同的操作。
// 这可能包括接收数据包、丢弃数据包、转发数据包到指定接口等。
// 3. **优先级(Priority)**:如果多个规则匹配同一个数据包,优先级可以帮助确定哪个规则应该被应用。
// 硬件过滤规则通常由网络设备的操作系统或驱动程序配置和管理。
// 它们可以用于在数据包接收和发送过程中实现高效的流量控制、安全策略、负载均衡等。
// 在某些情况下,库或框架(如 PF_RING)可能提供接口来让用户或应用程序添加、修改和管理硬件过滤规则。
// 这样,您就可以在程序中定义规则,以便对网络流量进行更精细的控制和管理。
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);
// 通常情况下,pfring_add_hw_rule 函数的作用可能是向硬件规则表中添加规则,
// 以便在硬件上进行数据包过滤或处理。这可以用于实现流量控制、过滤、重定向等功能。
// 然而,由于我无法直接访问您的代码库或特定版本的 PF_RING 文档,因此无法提供具体的函数定义或用法说明。
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.
*
* This function recieves packets from an interface and passes
* the packet on to the pfring callback function.
*
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into PfringThreadVars for ptv
* \param slot slot containing task information
* \retval TM_ECODE_OK on success
* \retval TM_ECODE_FAILED on failure
*/
// 用于在循环中接收PF_RING中的数据包,将数据包传递给下游的解码线程。
TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot)
{
SCEnter();
PfringThreadVars *ptv = (PfringThreadVars *)data;
Packet *p = NULL;
struct pfring_pkthdr hdr;
TmSlot *s = (TmSlot *)slot;
time_t last_dump = 0;
u_int buffer_size;
u_char *pkt_buffer;
ptv->slot = s->slot_next;
/* we have to enable the ring here as we need to do it after all
* the threads have called pfring_set_cluster(). */
/* 在所有线程调用 pfring_set_cluster() 之后,我们必须在这里启用环 */
int rc = pfring_enable_ring(ptv->pd);
if (rc != 0) {
SCLogError(SC_ERR_PF_RING_OPEN, "pfring_enable_ring failed returned %d ", rc);
SCReturnInt(TM_ECODE_FAILED);
}
// 这部分代码包含了一个循环,用于持续地接收和处理网络数据包。
// 在循环中,首先检查是否需要停止(通过检查suricata_ctl_flags标志位)。
// 然后,它确保数据包池中至少有一个空闲的数据包,以避免在线速率下频繁分配数据包。
// 然后,通过pfring_recv从pfring环接收数据包,并存储在pkt_buffer中。
while(1) {
if (suricata_ctl_flags & SURICATA_STOP) {
SCReturnInt(TM_ECODE_OK);
}
/* make sure we have at least one packet in the packet pool, to prevent
* us from alloc'ing packets at line rate */
/* 确保我们的数据包池中至少有一个数据包,以防止在线速率下分配数据包 */
PacketPoolWait();
p = PacketGetFromQueueOrAlloc();
if (p == NULL) {
SCReturnInt(TM_ECODE_FAILED);
}
PKT_SET_SRC(p, PKT_SRC_WIRE);
/* Some flavours of PF_RING may fail to set timestamp - see PF-RING-enabled libpcap code*/
/* 有些PF_RING的变种可能会在设置时间戳时失败 - 参见启用了PF-RING的libpcap代码 */
hdr.ts.tv_sec = hdr.ts.tv_usec = 0;
/* Check for Zero-copy mode */
/* 检查是否使用了零拷贝模式 */
if (ptv->flags & PFRING_FLAGS_ZERO_COPY) {
buffer_size = 0;
pkt_buffer = NULL;
} else {
buffer_size = GET_PKT_DIRECT_MAX_SIZE(p);
pkt_buffer = GET_PKT_DIRECT_DATA(p);
}
// 从pfring环接收数据包,并存储在pkt_buffer中。
int r = pfring_recv(ptv->pd, &pkt_buffer,
buffer_size,
&hdr,
LIBPFRING_WAIT_FOR_INCOMING);
if (likely(r == 1)) {
/* 在阻塞的 pfring_recv 调用之前启动了分析,所以在这里重置 */
/* profiling started before blocking pfring_recv call, so
* reset it here */
PACKET_PROFILING_RESTART(p);
#ifdef HAVE_PF_RING_FLOW_OFFLOAD
if (ptv->flags & PFRING_FLAGS_BYPASS) {
/* pkt hash contains the flow id in this configuration */
/* pkt hash 包含在此配置中的流 ID */
p->pfring_v.flow_id = hdr.extended_hdr.pkt_hash;
p->pfring_v.ptv = ptv;
p->BypassPacketsFlow = PfringBypassCallback;
}
#endif
/* Check for Zero-copy mode */
/* 检查是否使用了零拷贝模式 */
// 接下来,代码处理接收到的数据包。如果使用了零拷贝模式,数据包的内容不会被拷贝,而是直接使用pkt_buffer。
if (ptv->flags & PFRING_FLAGS_ZERO_COPY) {
PacketSetData(p, pkt_buffer, hdr.caplen);
}
PfringProcessPacket(ptv, &hdr, p);
if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
SCReturnInt(TM_ECODE_FAILED);
}
/* Trigger one dump of stats every second */
/* 每秒触发一次统计信息的输出 */
if (p->ts.tv_sec != last_dump) {
PfringDumpCounters(ptv);
last_dump = p->ts.tv_sec;
}
} else if (unlikely(r == 0)) {
if (suricata_ctl_flags & SURICATA_STOP) {
SCReturnInt(TM_ECODE_OK);
}
/* pfring didn't use the packet yet */
/* pfring 尚未使用该数据包 */
TmThreadsCaptureHandleTimeout(tv, p);
} else {
SCLogError(SC_ERR_PF_RING_RECV,"pfring_recv error %" PRId32 "", r);
TmqhOutputPacketpool(ptv->tv, p);
SCReturnInt(TM_ECODE_FAILED);
}
StatsSyncCountersIfSignalled(tv);
}
return TM_ECODE_OK;
}
/**
* \brief Stop function for ReceivePfringLoop.
*
* This function forces ReceivePfringLoop to stop the
* execution, exiting the packet capture loop.
*
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into PfringThreadVars for ptv
* \retval TM_ECODE_OK on success
* \retval TM_ECODE_FAILED on failure
*/
TmEcode PfringBreakLoop(ThreadVars *tv, void *data)
{
PfringThreadVars *ptv = (PfringThreadVars *)data;
/* Safety check */
if (ptv->pd == NULL) {
return TM_ECODE_FAILED;
}
pfring_breakloop(ptv->pd);
return TM_ECODE_OK;
}
/**
* \brief Init function for RecievePfring.
*
* This is a setup function for recieving packets
* via libpfring.
*
* \param tv pointer to ThreadVars
* \param initdata pointer to the interface passed from the user
* \param data pointer gets populated with PfringThreadVars
* \todo add a config option for setting cluster id
* \todo Create a general pfring setup function.
* \retval TM_ECODE_OK on success
* \retval TM_ECODE_FAILED on error
*/
// 进行PF_RING的初始化和配置,创建相关的线程和数据结构。
TmEcode ReceivePfringThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
int rc;
u_int32_t version = 0;
PfringIfaceConfig *pfconf = (PfringIfaceConfig *) initdata;
unsigned int opflag;
char const *active_runmode = RunmodeGetActive();
if (pfconf == NULL)
return TM_ECODE_FAILED;
// 在这里,为PfringThreadVars结构分配了内存,并将指针ptv赋值给它。
// 如果内存分配失败(ptv为NULL),则调用pfconf的DerefFunc函数来释放资源,并返回一个错误代码。
// 然后,将ptv的内存清零。
PfringThreadVars *ptv = SCMalloc(sizeof(PfringThreadVars));
if (unlikely(ptv == NULL)) {
pfconf->DerefFunc(pfconf);
return TM_ECODE_FAILED;
}
memset(ptv, 0, sizeof(PfringThreadVars));
ptv->tv = tv;
ptv->threads = 1;
ptv->interface = SCStrdup(pfconf->iface);
if (unlikely(ptv->interface == NULL)) {
SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate device string");
SCFree(ptv);
SCReturnInt(TM_ECODE_FAILED);
}
ptv->livedev = LiveGetDevice(pfconf->iface);
if (ptv->livedev == NULL) {
SCLogError(SC_ERR_INVALID_VALUE, "Unable to find Live device");
SCFree(ptv);
SCReturnInt(TM_ECODE_FAILED);
}
/* enable zero-copy mode for workers runmode */
// 这段代码首先检查是否处于"workers"运行模式,并且如果是的话,
// 将线程变量的flags标志位设置为PFRING_FLAGS_ZERO_COPY,表示启用零拷贝模式。
// 然后使用日志函数SCLogPerf记录启用零拷贝模式的信息。
if (active_runmode && strcmp("workers", active_runmode) == 0) {
ptv->flags |= PFRING_FLAGS_ZERO_COPY;
SCLogPerf("Enabling zero-copy for %s", ptv->interface);
}
// 将线程变量的checksum_mode成员设置为从pfconf(PfringIfaceConfig结构体)中获取的校验和验证模式。
ptv->checksum_mode = pfconf->checksum_mode;
// 初始化变量opflag,并设置其初始值为PF_RING_PROMISC,表示开启混杂模式。
opflag = PF_RING_PROMISC;
/* if we have a recent kernel, we need to use parsed_pkt to get VLAN info */
// 如果ptv->vlan_in_ext_header为真,即表示VLAN信息在扩展头中,
// 则将opflag设置为opflag | PF_RING_LONG_HEADER,表示启用长头部模式。
if (ptv->vlan_in_ext_header) {
opflag |= PF_RING_LONG_HEADER;
}
// 如果校验和验证模式为CHECKSUM_VALIDATION_RXONLY,则进行以下操作:
// 如果接口名称以"dna"开头,输出警告信息,表示不能在DNA接口上使用rxonly校验,
// 然后将校验模式设置为CHECKSUM_VALIDATION_AUTO。
// 否则,将opflag设置为opflag | PF_RING_LONG_HEADER,即启用长头部模式。
if (ptv->checksum_mode == CHECKSUM_VALIDATION_RXONLY) {
if (strncmp(ptv->interface, "dna", 3) == 0) {
SCLogWarning(SC_ERR_INVALID_VALUE,
"Can't use rxonly checksum-checks on DNA interface,"
" resetting to auto");
ptv->checksum_mode = CHECKSUM_VALIDATION_AUTO;
} else {
opflag |= PF_RING_LONG_HEADER;
}
}
// 如果编译时支持PF_RING的流量卸载特性,且pfconf->flags中设置了PFRING_CONF_FLAGS_BYPASS标志,执行以下操作:
// 将opflag设置为opflag | PF_RING_FLOW_OFFLOAD | PF_RING_FLOW_OFFLOAD_NOUPDATES,表示启用流量卸载和不更新模式。
// 将线程变量的flags标志位设置为PFRING_FLAGS_BYPASS。
#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
// 打开PF_RING设备,传入接口名称、默认数据包大小以及之前设置的opflag,返回一个pfring句柄。
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."
" Check if %s exists and pf_ring module is loaded.",
ptv->interface,
ptv->interface);
pfconf->DerefFunc(pfconf);
SCFree(ptv);
return TM_ECODE_FAILED;
}
// 设置pfring设备的应用程序名称,即标识当前应用的名称。
pfring_set_application_name(ptv->pd, (char *)PROG_NAME);
// 获取pfring设备的版本信息,将其存储在version变量中。
pfring_version(ptv->pd, &version);
/* We only set cluster info if the number of pfring threads is greater than 1 */
// 对线程变量ptv进行一系列配置:
// 设置线程数和集群ID。
// 如果线程数为1且接口名称以"dna"开头,输出日志表示检测到DNA接口,不将线程添加到集群中。
// 如果接口名称以"zc"开头,输出日志表示检测到ZC接口,同样不将线程添加到集群中。
// 否则,根据配置设置集群类型和ID,如果设置失败且集群设置为强制,则返回初始化失败。
ptv->threads = pfconf->threads;
ptv->cluster_id = pfconf->cluster_id;
if ((ptv->threads == 1) && (strncmp(ptv->interface, "dna", 3) == 0)) {
SCLogInfo("DNA interface detected, not adding thread to cluster");
} else if (strncmp(ptv->interface, "zc", 2) == 0) {
SCLogInfo("ZC interface detected, not adding thread to cluster");
} else {
ptv->ctype = (cluster_type)pfconf->ctype;
rc = pfring_set_cluster(ptv->pd, ptv->cluster_id, ptv->ctype);
if (rc != 0) {
SCLogError(SC_ERR_PF_RING_SET_CLUSTER_FAILED, "pfring_set_cluster "
"returned %d for cluster-id: %d", rc, ptv->cluster_id);
if (rc != PF_RING_ERROR_NOT_SUPPORTED || (pfconf->flags & PFRING_CONF_FLAGS_CLUSTER)) {
/* cluster is mandatory as explicitly specified in the configuration */
pfconf->DerefFunc(pfconf);
return TM_ECODE_FAILED;
}
}
}
// 打印日志,描述使用的PF_RING版本、接口和集群ID。根据线程数决定是否添加了集群线程。
if (ptv->threads > 1) {
SCLogPerf("(%s) Using PF_RING v.%d.%d.%d, interface %s, cluster-id %d",
tv->name, (version & 0xFFFF0000) >> 16, (version & 0x0000FF00) >> 8,
version & 0x000000FF, ptv->interface, ptv->cluster_id);
} else {
SCLogPerf("(%s) Using PF_RING v.%d.%d.%d, interface %s, cluster-id %d, single-pfring-thread",
tv->name, (version & 0xFFFF0000) >> 16, (version & 0x0000FF00) >> 8,
version & 0x000000FF, ptv->interface, ptv->cluster_id);
}
// 如果存在BPF过滤器,将其复制到线程变量中,然后设置BPF过滤器,若设置失败则返回初始化失败。
if (pfconf->bpf_filter) {
ptv->bpf_filter = SCStrdup(pfconf->bpf_filter);
if (unlikely(ptv->bpf_filter == NULL)) {
SCLogError(SC_ERR_MEM_ALLOC, "Set PF_RING bpf filter failed.");
} else {
SCMutexLock(&pfring_bpf_set_filter_lock);
rc = pfring_set_bpf_filter(ptv->pd, ptv->bpf_filter);
SCMutexUnlock(&pfring_bpf_set_filter_lock);
if (rc < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Failed to compile BPF \"%s\"",
ptv->bpf_filter);
return TM_ECODE_FAILED;
}
}
}
// 注册统计计数器,记录捕获的数据包数、丢弃的数据包数,以及可能的绕过数据包数。
ptv->capture_kernel_packets = StatsRegisterCounter("capture.kernel_packets",
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
/* If kernel is older than 3.0, VLAN is not stripped so we don't
* get the info from packt extended header but we will use a standard
* parsing */
// 根据内核版本判断是否启用VLAN扩展头,并设置vlan_in_ext_header的值。
ptv->vlan_in_ext_header = 1;
if (! SCKernelVersionIsAtLeast(3, 0)) {
ptv->vlan_in_ext_header = 0;
}
/* If VLAN tags are not in the extended header, set cluster type to 5-tuple
* or in case of a ZC interface, do nothing */
// 如果VLAN标签不在扩展头中,且集群类型为CLUSTER_FLOW,
// 且接口不是ZC接口,则设置集群类型为CLUSTER_FLOW_5_TUPLE,并进行错误检查。
// ZC 接口:"ZC" 可能指的是 "Zero Copy"
// 在网络数据包处理中,"CLUSTER_FLOW_5_TUPLE" 是一种集群类型,
// 用于指定数据包在进行负载均衡和集群处理时的分组方式。
// 这种集群类型基于数据包的五元组信息进行分类和分组,
// 以便将具有相似特征的数据包路由到同一处理单元或线程进行处理。
// 五元组是指一个数据包的五个基本属性,包括:
// 1. 源IP地址
// 2. 目标IP地址
// 3. 源端口号
// 4. 目标端口号
// 5. 协议类型(如TCP、UDP、ICMP等)
// 当集群类型设置为 "CLUSTER_FLOW_5_TUPLE" 时,数据包会根据上述五元组信息进行分类,
// 相同五元组的数据包将被分配到同一集群或处理单元,从而实现负载均衡和并行处理。
// 这种集群类型通常适用于需要对网络流量进行分流和处理的场景,
// 以确保相同会话或连接的数据包被发送到同一处理单元,从而提高处理效率和性能。
if ((! ptv->vlan_in_ext_header) && ptv->ctype == CLUSTER_FLOW &&
strncmp(ptv->interface, "zc", 2) != 0) {
SCLogPerf("VLAN not in extended header, setting cluster type to CLUSTER_FLOW_5_TUPLE");
rc = pfring_set_cluster(ptv->pd, ptv->cluster_id, CLUSTER_FLOW_5_TUPLE);
if (rc != 0) {
SCLogError(SC_ERR_PF_RING_SET_CLUSTER_FAILED, "pfring_set_cluster "
"returned %d for cluster-id: %d", rc, ptv->cluster_id);
pfconf->DerefFunc(pfconf);
return TM_ECODE_FAILED;
}
}
//将ptv的值赋给data指针,使用pfconf的DerefFunc释放资源,并返回一个OK的错误代码。
*data = (void *)ptv;
pfconf->DerefFunc(pfconf);
return TM_ECODE_OK;
}
/**
* \brief This function prints stats to the screen at exit.
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into PfringThreadVars for ptv
*/
// 用于在线程退出时打印统计信息,包括接收到的数据包数量、丢弃的数据包数量等。
void ReceivePfringThreadExitStats(ThreadVars *tv, void *data)
{
PfringThreadVars *ptv = (PfringThreadVars *)data;
PfringDumpCounters(ptv);
SCLogPerf("(%s) Kernel: Packets %" PRIu64 ", dropped %" PRIu64 "",
tv->name,
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
}
/**
* \brief DeInit function closes pd at exit.
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into PfringThreadVars for ptvi
* \retval TM_ECODE_OK is always returned
*/
// 用于在线程退出时进行清理工作,关闭PF_RING等资源。
TmEcode ReceivePfringThreadDeinit(ThreadVars *tv, void *data)
{
PfringThreadVars *ptv = (PfringThreadVars *)data;
if (ptv->interface)
SCFree(ptv->interface);
pfring_remove_from_cluster(ptv->pd);
if (ptv->bpf_filter) {
pfring_remove_bpf_filter(ptv->pd);
SCFree(ptv->bpf_filter);
}
pfring_close(ptv->pd);
return TM_ECODE_OK;
}
/**
* \brief This function passes off to link type decoders.
*
* DecodePfring decodes raw packets from PF_RING. Inside of libpcap version of
* PF_RING all packets are marked as a link type of ethernet so that is what we do here.
*
* \param tv pointer to ThreadVars
* \param p pointer to the current packet
* \param data pointer that gets cast into PfringThreadVars for ptv
*
* \todo Verify that PF_RING only deals with ethernet traffic
*
* \warning This function bypasses the pkt buf and len macro's
*
* \retval TM_ECODE_OK is always returned
*/
// 用于解码接收到的数据包,将其进行解析并交给相应的解码器进行处理。
TmEcode DecodePfring(ThreadVars *tv, Packet *p, void *data)
{
DecodeThreadVars *dtv = (DecodeThreadVars *)data;
BUG_ON(PKT_IS_PSEUDOPKT(p));
/* update counters */
DecodeUpdatePacketCounters(tv, dtv, p);
/* If suri has set vlan during reading, we increase vlan counter */
if (p->vlan_idx) {
StatsIncr(tv, dtv->counter_vlan);
}
DecodeEthernet(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
PacketDecodeFinalize(tv, dtv, p);
return TM_ECODE_OK;
}
/**
* \brief This an Init function for DecodePfring
*
* \param tv pointer to ThreadVars
* \param initdata pointer to initilization data.
* \param data pointer that gets cast into PfringThreadVars for ptv
* \retval TM_ECODE_OK is returned on success
* \retval TM_ECODE_FAILED is returned on error
*/
TmEcode DecodePfringThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
DecodeThreadVars *dtv = NULL;
dtv = DecodeThreadVarsAlloc(tv);
if (dtv == NULL)
SCReturnInt(TM_ECODE_FAILED);
DecodeRegisterPerfCounters(dtv, tv);
*data = (void *)dtv;
return TM_ECODE_OK;
}
TmEcode DecodePfringThreadDeinit(ThreadVars *tv, void *data)
{
if (data != NULL)
DecodeThreadVarsFree(tv, data);
SCReturnInt(TM_ECODE_OK);
}
#endif /* HAVE_PFRING */
两个文件分别是:source-pfring.h和source-pfring.c