suricata之pfring收包模式源码分析

#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 

/** 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

你可能感兴趣的:(suricata概述及源码分析,suricata)