《suricata中DPDK收发包源码分析1》中分析了整体的DPDK收发包框架代码,今天我们继续来深入了解一下一些细节方面的问题。
目录
Q1:收发包线程模式在代码中是怎样确定的?
Q2: DPDK库的初始化rte_eal_init在哪里调用的?
Q3: 对网卡进行DPDK相关接口的配置操作的代码在哪里?
Q4: DPDK mbfu和suricata Packet是怎么转换的?
Q5: 网卡的RSS对哈希是在哪里配置的?
Q1:收发包线程模式在代码中是怎样确定的?
前面的文章《suricata中DPDK收发包线程模型和配置说明》中说的收包线程模型如下,那么这个收包模型在代码中是怎么确定的呢?
前面讲的DPDK的网口配置文件时,每个网口会指定收包线程数,我们指定的是2,即每个网口有两个收包线程,在RunModeSetLiveCaptureWorkers函数中会获取网卡总数,遍历所有网卡,然后遍历所有网卡配置的线程个数,所以总共启动的收包线程数就是网卡数*每个网卡配置的收包线程数,那怎么确定第一个收包线程只收取第一个网卡的0号队列的报文,第二个收包线程只收取第一个网卡的1号队列的报文,以及第三个线程,第四个线程以此类推呢?
前面讲到,DPDK调用rte_eth_rx_burst进行循环收包的函数是ReceiveDPDKLoop,rte_eth_rx_burst函数的前两个参数是port_id和queue_id,所以要控制每个线程收哪个网卡哪个队列的报文控制这两个参数就行了,这里的port_id和queue_id来自于DPDK的线程变量DPDKThreadVars,每个收包线程都有一个这样的变量:
typedef struct DPDKThreadVars_ {
/* counters */
uint64_t pkts;
ThreadVars *tv;
TmSlot *slot;
LiveDevice *livedev;
ChecksumValidationMode checksum_mode;
/* references to packet and drop counters */
uint16_t capture_dpdk_packets;
uint16_t capture_dpdk_rx_errs;
uint16_t capture_dpdk_imissed;
uint16_t capture_dpdk_rx_no_mbufs;
uint16_t capture_dpdk_ierrors;
uint16_t capture_dpdk_tx_errs;
unsigned int flags;
int threads;
/* for IPS */
DpdkCopyModeEnum copy_mode;
uint16_t out_port_id; //发送网口id
/* Entry in the peers_list */
uint64_t bytes;
uint64_t accepted;
uint64_t dropped;
uint16_t port_id; //收包网口id
uint16_t queue_id; //网口队列id
struct rte_mempool *pkt_mempool;
struct rte_mbuf *received_mbufs[BURST_SIZE];
} DPDKThreadVars;
DPDKThreadVars变量的值来自于slot->slot_data,slot->slot_data是TmThreadsSlotPktAcqLoop函数中:
for (slot = s; slot != NULL; slot = slot->slot_next) {
if (slot->SlotThreadInit != NULL) {
void *slot_data = NULL;
r = slot->SlotThreadInit(tv, slot->slot_initdata, &slot_data); //ReceiveDPDKThreadInit 或 DecodeDPDKThreadInit
if (r != TM_ECODE_OK) {
if (r == TM_ECODE_DONE) {
EngineDone();
TmThreadsSetFlag(tv, THV_CLOSED | THV_INIT_DONE | THV_RUNNING_DONE);
goto error;
} else {
TmThreadsSetFlag(tv, THV_CLOSED | THV_RUNNING_DONE);
goto error;
}
}
(void)SC_ATOMIC_SET(slot->slot_data, slot_data); //局部变量slot_data复制给slot->slot_data
}
局部变量slot_data是slot->SlotThreadInit(指向ReceiveDPDKThreadInit函数时)中将slot->slot_initdata(也就是DPDKIfaceConfig)的内容赋值给它的,那slot->slot_initdata(也就是DPDKIfaceConfig)又是哪里来的呢?其实slot->slot_initdata是TmSlotSetFuncAppend函数中将第三个参数data赋值给它的,这个data就是在RunModeSetLiveCaptureWorkersForDevice函数中注册ReceiveDPDK时通过TmSlotSetFuncAppend(tv, tm_module, aconf)传递的这个aconf变量,而conf变量就是ParseDpdkConfigAndConfigureDevice函数根据pcie地址读取到的网口的信息,所有绕了一大圈,才把配置里面的port_id和queue_id给到了DPDK线程变量DPDKThreadVars,用来决定每个线程收哪个网卡哪个队列的报文。
另外,发送报文都是调用Packet变量的p->ReleasePacket函数指针,也就是DPDKReleasePacket函数,通过DPDK的rte_eth_tx_burst接口进行发送的。
Q2: DPDK库的初始化rte_eal_init在哪里调用的?
rte_eal_init是在InitEal函数中调用的,调用栈如下:
SuricataMain
-->RunModeDispatch
-->RunModeIdsDpdkWorkers
-->InitEal
-->rte_eal_init
rte_eal_init传入的参数很简单,就是配置中的proc-type,指定的是Primary进程,其实suricata文档中说明的是DPDK收包的话就应该是Primary进程。
Q3: 对网卡进行DPDK相关接口的配置操作的代码在哪里?
对网卡进行配置操作的函数调用栈如下:
SuricataMain
-->RunModeDispatch
-->RunModeIdsDpdkWorkers
-->RunModeSetLiveCaptureWorkers
-->ParseDpdkConfigAndConfigureDevice
-->DeviceConfigure
-->DeviceInitPortConf(设置网卡RSS)
-->rte_eth_dev_configure(配置网卡队列等参数)
-->rte_eth_dev_adjust_nb_rx_tx_desc(配置网卡收包文件描述符)
-->rte_eth_dev_set_mtu(设置MTU)
-->DeviceConfigureQueues(按网口申请收包mbuf内存池)
-->rte_eth_rx_queue_setup(配置网口收包队列)
-->rte_eth_tx_queue_setup(配置网口发包队列)
需要注意的是启动网卡的接口rte_eth_dev_start,是在ReceiveDPDKThreadInit函数,也就是在收包线程入口TmThreadsSlotPktAcqLoop中调用slot->SlotThreadInit时进行的,由于多个一个网口只用启动一次,但是一个网口对应多个收包线程,所以加了一个判断,只在每个网口最后一个收包线程时启动网卡,部分代码如下:
// the last thread starts the device
if (queue_id == dpdk_config->threads - 1) {
retval = rte_eth_dev_start(ptv->port_id);
if (retval < 0) {
SCLogError("Error (%s) during device startup of %s", rte_strerror(-retval),
dpdk_config->iface);
goto fail;
}
Q4: DPDK mbfu和suricata Packet是怎么转换的?
在ReceiveDPDKLoop函数中,通过rte_eth_rx_burst收包的每一个mbuf与Packet对应,部分代码如下:
//DPDK开始收包
nb_rx = rte_eth_rx_burst(ptv->port_id, ptv->queue_id, ptv->received_mbufs, BURST_SIZE);
if (unlikely(nb_rx == 0)) {
continue;
}
ptv->pkts += (uint64_t)nb_rx;
for (uint16_t i = 0; i < nb_rx; i++) {
p = PacketGetFromQueueOrAlloc(); //Packet与dpdk mbuf一一对应
if (unlikely(p == NULL)) {
continue;
}
PKT_SET_SRC(p, PKT_SRC_WIRE);
p->datalink = LINKTYPE_ETHERNET;
if (ptv->checksum_mode == CHECKSUM_VALIDATION_DISABLE) {
p->flags |= PKT_IGNORE_CHECKSUM;
}
p->ts = DPDKSetTimevalReal(&machine_start_time);
p->dpdk_v.mbuf = ptv->received_mbufs[i]; //记录mbuf,后面用来发送报文时指定mbuf
p->ReleasePacket = DPDKReleasePacket; //释放mbuf或者发送报文
p->dpdk_v.copy_mode = ptv->copy_mode; //转发模式 DpdkCopyModeEnum
p->dpdk_v.out_port_id = ptv->out_port_id; //出网口
p->dpdk_v.out_queue_id = ptv->queue_id; //出网口的队列
p->livedev = ptv->livedev; //出网口信息指针
if (ptv->checksum_mode == CHECKSUM_VALIDATION_DISABLE) {
p->flags |= PKT_IGNORE_CHECKSUM;
} else if (ptv->checksum_mode == CHECKSUM_VALIDATION_OFFLOAD) {
uint64_t ol_flags = ptv->received_mbufs[i]->ol_flags;
if ((ol_flags & RTE_MBUF_F_RX_IP_CKSUM_MASK) == RTE_MBUF_F_RX_IP_CKSUM_GOOD &&
(ol_flags & RTE_MBUF_F_RX_L4_CKSUM_MASK) == RTE_MBUF_F_RX_L4_CKSUM_GOOD) {
SCLogDebug("HW detected GOOD IP and L4 chsum, ignoring validation");
p->flags |= PKT_IGNORE_CHECKSUM;
} else {
if ((ol_flags & RTE_MBUF_F_RX_IP_CKSUM_MASK) == RTE_MBUF_F_RX_IP_CKSUM_BAD) {
SCLogDebug("HW detected BAD IP checksum");
// chsum recalc will not be triggered but rule keyword check will be
p->level3_comp_csum = 0;
}
if ((ol_flags & RTE_MBUF_F_RX_L4_CKSUM_MASK) == RTE_MBUF_F_RX_L4_CKSUM_BAD) {
SCLogDebug("HW detected BAD L4 chsum");
p->level4_comp_csum = 0;
}
}
}
//构造Packet结构体
PacketSetData(p, rte_pktmbuf_mtod(p->dpdk_v.mbuf, uint8_t *),
rte_pktmbuf_pkt_len(p->dpdk_v.mbuf)); //Packet中记录了包的首地址和长度
if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
TmqhOutputPacketpool(ptv->tv, p);
DPDKFreeMbufArray(ptv->received_mbufs, nb_rx - i - 1, i + 1);
SCReturnInt(EXIT_FAILURE);
}
}
Q5: 网卡的RSS对哈希是在哪里配置的?
是在DeviceInitPortConf函数中,部分代码如下:
port_conf->rx_adv_conf.rss_conf = (struct rte_eth_rss_conf){
.rss_key = rss_hkey,
.rss_key_len = RSS_HKEY_LEN,
.rss_hf = iconf->rss_hf,
};
其中rss_hkey是40个字节的数组,设置的是对称哈希,可以将相同五元组的报文都放入同一个网卡队列中:
// General purpose RSS key for symmetric bidirectional flow distribution
uint8_t rss_hkey[] = { 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D,
0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D,
0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A };
这个RSS最终是调用DPDK的网卡配置函数rte_eth_dev_configure来进行配置的。
好了,关于suricata中DPDK进行收发包的全部实现逻辑就讲到这里了。有问题的朋友,可以进网络技术开发交流群提问(先加我微信,备注加群)。喜欢文章内容的朋友,记得加个关注哦~~