以下是使用19.2版本的DPDK和最新版nDPI进行HTTP协议过滤的示例代码:
#include <stdio.h>
#include <string.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <ndpi_api.h>
#define BURST_SIZE 32
static struct ndpi_detection_module_struct *ndpi_struct = NULL;
static void packet_handler(struct rte_mbuf *pkt) {
struct ndpi_detection_results results;
int rc = 0;
// 获取数据包缓冲区地址
void *packet = rte_pktmbuf_mtod(pkt, void *);
// 进行协议识别
memset(&results, 0, sizeof(results));
ndpi_protocol detection = ndpi_detection_process_packet(ndpi_struct, packet, pkt->data_len, &results);
if (detection.app_protocol == NDPI_PROTOCOL_HTTP) {
printf("Detected HTTP traffic from flow %u.%u.%u.%u:%u to %u.%u.%u.%u:%u\n",
(pkt->hash.rss & 0xff000000) >> 24, (pkt->hash.rss & 0x00ff0000) >> 16,
(pkt->hash.rss & 0x0000ff00) >> 8, pkt->hash.rss & 0x000000ff,
rte_be_to_cpu_16(pkt->port),
(pkt->hash.fdir.hi & 0xffff0000) >> 16, pkt->hash.fdir.hi & 0x0000ffff,
(pkt->hash.fdir.lo & 0xffff0000) >> 16, pkt->hash.fdir.lo & 0x0000ffff,
rte_be_to_cpu_16(pkt->hash.fdir.hi));
}
}
int main(int argc, char *argv[]) {
int ret;
// 初始化DPDK环境
ret = rte_eal_init(argc, argv);
if (ret < 0) {
rte_exit(EXIT_FAILURE, "rte_eal_init failed\n");
}
// 获取网口数量
uint16_t nb_ports = rte_eth_dev_count_avail();
if (nb_ports == 0) {
rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
}
// 配置DPDK网口
for (int portid = 0; portid < nb_ports; portid++) {
ret = rte_eth_dev_configure(portid, 1, 1, NULL);
if (ret < 0) {
rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n", ret, portid);
}
struct rte_eth_conf port_conf;
memset(&port_conf, 0, sizeof(port_conf));
port_conf.rxmode.max_rx_pkt_len = RTE_ETHER_MAX_LEN;
port_conf.rxmode.mq_mode = ETH_MQ_RX_RSS;
port_conf.rx_adv_conf.rss_conf.rss_key = NULL;
port_conf.rx_adv_conf.rss_conf.rss_hf |= ETH_RSS_PROTO_MASK;
ret = rte_eth_dev_configure(portid, 1, 1, &port_conf);
if (ret < 0) {
rte_exit(EXIT_FAILURE,"Cannot configure device: err=%d,port=%u\n",ret,(unsigned)portid);
}
// 启动DPDK网口接收流量
const uint16_t rx_rings = 1, tx_rings = 1;
const uint16_t rx_ring_size = 1024, tx_ring_size = 1024;
struct rte_eth_rxconf rx_conf;
memset(&rx_conf, 0, sizeof(rx_conf));
rx_conf.rx_thresh.pthresh = 8;
rx_conf.rx_thresh.hthresh = 8;
rx_conf.rx_thresh.wthresh = 4;
for (int qid = 0; qid < rx_rings; qid++) {
ret = rte_eth_rx_queue_setup(portid, qid, rx_ring_size, rte_eth_dev_socket_id(portid), &rx_conf,
rte_pktmbuf_pool_create("RX_POOL", nb_ports * rx_ring_size,
RTE_CACHE_LINE_SIZE, 0,
RTE_MBUF_DEFAULT_BUF_SIZE,
rte_socket_id()));
if (ret < 0) {
rte_exit(EXIT_FAILURE,"Cannot configure RX queue: err=%d,q=%u\n",ret,(unsigned)qid);
}
}
struct rte_eth_txconf tx_conf;
memset(&tx_conf, 0, sizeof(tx_conf));
tx_conf.txq_flags |= ETH_TXQ_FLAGS_NOOFFLOADS;
for (int qid = 0; qid < tx_rings; qid++) {
ret = rte_eth_tx_queue_setup(portid, qid, tx_ring_size,rte_eth_dev_socket_id(portid), &tx_conf);
if (ret < 0) {
rte_exit(EXIT_FAILURE,"Cannot configure TX queue: err=%d,q=%u\n",ret,(unsigned)qid);
}
}
ret = rte_eth_dev_start(portid);
if (ret < 0) {
rte_exit(EXIT_FAILURE,"Cannot start device: err=%d,port=%u\n", ret,(unsigned)portid);
}
rte_eth_promiscuous_enable(portid);
}
// 初始化nDPI
ndpi_struct = ndpi_init_detection_module();
if (ndpi_struct == NULL) {
rte_exit(EXIT_FAILURE, "Failed to initialize nDPI\n");
}
// 加载nDPI协议识别规则
int num_signatures = ndpi_load_protocols_file(ndpi_struct, "/path/to/ndpi/protocols.txt");
if (num_signatures <= 0) {
rte_exit(EXIT_FAILURE, "Failed to load nDPI protocol signatures\n");
}
printf("Loaded %d nDPI protocol signatures\n", num_signatures);
// 开始接收数据包并进行处理
struct rte_mbuf *pkts_burst[BURST_SIZE];
while (1) {
for (int portid = 0; portid < nb_ports; portid++) {
const uint16_t nb_rx = rte_eth_rx_burst(portid, 0, pkts_burst, BURST_SIZE);
for (int i = 0; i < nb_rx; i++) {
packet_handler(pkts_burst[i]);
rte_pktmbuf_free(pkts_burst[i]);
}
}
}
return 0;
}
在上述代码中,我们使用ndpi_api.h
头文件提供的函数来实现对HTTP协议的过滤。具体步骤如下:
在程序启动时初始化nDPI模块,可以使用
ndpi_init_detection_module()
函数。加载nDPI协议识别规则,可以通过调用
ndpi_load_protocols_file()
函数来加载协议规则文件。在示例代码中,我们将协议规则文件存放在/path/to/ndpi/protocols.txt
路径下。在数据包处理函数中调用
ndpi_detection_process_packet()
函数进行协议识别。如果检测到HTTP流量,则打印出相应的信息。
需要注意的是,在使用nDPI进行协议识别时,需要预留足够的时间来处理每个数据包。因此,在接收数据包时可以使用DPDK提供的批量接收功能,一次性接收多个数据包,减少处理延迟。