在Suricata中,用来封装数据包的结构体为Packet,核心字段如下:
| 字段 | 含义 |
|---|---|
| src/dst、sp/dp、proto | 五元组信息:源/目的地址,源/目的端口号,传输层协议(TCP/UDP/…)。 |
| flow | 数据包所属的流指针(类型为Flow_ *)。 |
| ip4h、ip6h | 网络层数据指针。 |
| tcph、udph、sctph、icmpv4/6h | 传输层数据指针。 |
| payload、payload_len | 应用层负载指针及长度。 |
| next、prev | 前一个/后一个数据包指针,用于组成双向链表。 |
PcapCallbackLoop函数中第一步就是完成对数据包的封装。其函数原型如下:
void PcapCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
其中,user为用户数据,即之前传入的PcapThreadVars,h为pcap包结构体头,pkt为包数据指针。
void PcapCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
{
SCEnter();
PcapThreadVars *ptv = (PcapThreadVars *)user;
Packet *p = PacketGetFromQueueOrAlloc();
/*调用PacketGetFromQueueOrAlloc获取一个Packet结构。该函数会首先尝试调用
PacketPoolGetPacket从packet pool中直接获取,如果失败(已经用完了),
那么就调用PacketGetFromAlloc新分配一个。
注:与pool中取出的数据包最终可以回收不同,这种使用malloc动态分配的数据包
最后需要free,因此为了区分会在其flags中打上标记PKT_ALLOC。*/
struct timeval current_time;
if (unlikely(p == NULL)) {
SCReturn;
}
PKT_SET_SRC(p, PKT_SRC_WIRE);
p->ts.tv_sec = h->ts.tv_sec;
p->ts.tv_usec = h->ts.tv_usec;
SCLogDebug("p->ts.tv_sec %"PRIuMAX"", (uintmax_t)p->ts.tv_sec);
p->datalink = ptv->datalink;
ptv->pkts++;
ptv->bytes += h->caplen;
(void) SC_ATOMIC_ADD(ptv->livedev->pkts, 1);
p->livedev = ptv->livedev;
/*填充Packet结构体的部分字段:数据包源(PKT_SRC_WIRE)、时间戳、
所属数据链路/设备*/
if (unlikely(PacketCopyData(p, pkt, h->caplen))) {
/*调用PacketCopyData复制数据包内容到pkt字段中,并设置pktlen为相应的长度。
注:为什么需要做复制这种开销大的操作呢?man pcap_dispatch给出了答案:The
struct pcap_pkthdr… are not guaranteed to be valid after the
callback routine returns; if the code needs them to be valid after
the callback, it must make a copy of them. 而由于Suricata的多线程和
异步特性,数据包在callback中会送入outq中等待后续线程继续处理,因此这里必须
进行复制。对于一些支持zero copy的抓包模式,如netmap,pfring等使用
PacketSetData直接将p->ext_pkt指向pkt*/
TmqhOutputPacketpool(ptv->tv, p);
SCReturn;
}
switch (ptv->checksum_mode) {
case CHECKSUM_VALIDATION_AUTO:
if (ptv->livedev->ignore_checksum) {
p->flags |= PKT_IGNORE_CHECKSUM;
} else if (ChecksumAutoModeCheck(ptv->pkts,
SC_ATOMIC_GET(ptv->livedev->pkts),
SC_ATOMIC_GET(ptv->livedev->invalid_checksums))) {
ptv->livedev->ignore_checksum = 1;
p->flags |= PKT_IGNORE_CHECKSUM;
}
break;
case CHECKSUM_VALIDATION_DISABLE:
p->flags |= PKT_IGNORE_CHECKSUM;
break;
default:
break;
}
/*校验和相关的处理。若checksum_mode为DISABLE,将会给包的flags打上
PKT_IGNORE_CHECKSUM标志,表示后续不再对其进行校验和验证。若
checksum_mode为AUTO,则调用ChecksumAutoModeCheck进行统计分析,
满足条件则后续该设备的数据包都会关闭校验和验证。目前是的关闭条件是:
1000个包中若有超过10%的包校验和不正确,则认为网卡开启了
checksum offloading,因而关闭检验和验证*/
if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
/*调用TmThreadsSlotProcessPkt让本线程中包含的其他slot中的模块对数据包进行后续处理。
若处理返回失败,则调用pcap_breakloop中断抓包。*/
pcap_breakloop(ptv->pcap_handle);
ptv->cb_result = TM_ECODE_FAILED;
}
/* Trigger one dump of stats every second */
TimeGet(¤t_time);
if (current_time.tv_sec != ptv->last_stats_dump) {
PcapDumpCounters(ptv);
ptv->last_stats_dump = current_time.tv_sec;
/*调用PcapDumpCounters设置抓包统计信息(stats counters)。
这个打印保证每秒只触发一次,机制是:获取当前时间,
只有当前秒数与上一次记录的秒数不同时才调用*/
}
SCReturn;
}

本文介绍了Suricata中数据包的封装过程,包括Packet结构体的定义与填充,以及PcapCallbackLoop函数如何处理捕获的数据包。还讨论了数据包复制的原因及其在多线程环境中的必要性。
62

被折叠的 条评论
为什么被折叠?



