dpdk-packet_producer-自制发包器

dpdk-packet_producer-自制发包器

  • 需求
  • 代码
  • 主要流程
    • 主函数的流程
    • 主线程函数的流程
  • 结果
    • 虚拟机Terminal显示
    • 宿主机Wireshark抓包

需求

本代码利用dpdk实现一个发包器,每2秒发送64个COLOR_GET数据包。
由于dpdk直接通过物理层发送数据包,需要写代码发数据帧格式如下:
dpdk-packet_producer-自制发包器_第1张图片
其中rte_ether_hdr和rte_ipv4_hdr在dpdk的lib中均有封装的struct,其结构如下:

struct rte_ether_hdr {
     
	struct rte_ether_addr d_addr; /**< Destination address. */
	struct rte_ether_addr s_addr; /**< Source address. */
	uint16_t ether_type;      /**< Frame type. */
} __attribute__((aligned(2)));

struct rte_ipv4_hdr {
     
	uint8_t  version_ihl;		/**< version and header length */
	uint8_t  type_of_service;	/**< type of service */
	rte_be16_t total_length;	/**< length of packet */
	rte_be16_t packet_id;		/**< packet ID */
	rte_be16_t fragment_offset;	/**< fragmentation offset */
	uint8_t  time_to_live;		/**< time to live */
	uint8_t  next_proto_id;		/**< protocol ID */
	rte_be16_t hdr_checksum;	/**< header checksum */
	rte_be32_t src_addr;		/**< source address */
	rte_be32_t dst_addr;		/**< destination address */
} __attribute__((__packed__));

COLOR是ICN体系结构的一种网络,Customer端如果需要获取数据,需要发送get包进行请求,其包格式如下:
dpdk-packet_producer-自制发包器_第2张图片
结构体定义如下:

#define CONTENTLEN 4
#define PUBKEYLEN 4
struct CoLoR_get
{
     
    uint8_t version_type;   //版本4位,类型4位 1
    uint8_t ttl;        //生存时间 1
    uint16_t total_len; //总长度 2
    uint16_t port_src;  //源端口号 2
    uint16_t port_dst;  //目的端口号 2
    uint16_t minmal_PID_CP; //pid改变的周期 2
    uint8_t PIDs;       //PID的数目 1
    uint8_t Offest_RES;     //位运算取Offset 1
    uint32_t offset;    //偏移量 4
    uint32_t length;    //偏移长度 4
    uint16_t content_len;   //公钥长度 2
    uint16_t mtu;       //最大传输单元 2
    uint16_t publickey_len; //公钥长度 2
    uint16_t checksum;  //检验和 2
    uint8_t n_sid[16];  //NID part of an SID,长度为16字节 16*8=128   16
    uint8_t l_sid[20];  //SID的长度为20字节 20*8 = 160  20
    uint8_t nid[16];    //NID的长度为16字节 16
    uint8_t content[CONTENTLEN];    // Content characteristics 4
    uint8_t publickey[PUBKEYLEN];   //公钥 4
}__attribute__((__packed__));
typedef struct CoLoR_get CoLoR_get_t;

代码

  1. 头文件及宏定义
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2015 Intel Corporation
 * Author: Lianpeng
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define RX_RING_SIZE 1024  
#define TX_RING_SIZE 1024

#define NUM_MBUFS 8191
#define MBUF_CACHE_SIZE 250
#define BURST_SIZE 32

#define TXONLY_DEF_PACKET_LEN 64
//IP_SRC_ADDR表示虚拟机对应网卡的IP地址(192.168.73.3)
//IP_DST_ADDR表示宿主机对应的IP地址(192.168.73.1)
//在VirtualBox中网卡enp0s8采用Host-Only的连接模式
//这种模式下宿主机和虚拟机都能ping通

#define IP_SRC_ADDR ((192U << 24) | (168 << 16) | (73 << 8) | 3)
#define IP_DST_ADDR ((192U << 24) | (168 << 16) | (73 << 8) | 1)

//IP数据包必填部分
#define IP_DEFTTL  64   /* from RFC 1340. */
#define IP_VERSION 0x40
#define IP_HDRLEN  0x05 /* default IP header length == five 32-bits words. */
#define IP_VHL_DEF (IP_VERSION | IP_HDRLEN)

//COLOR_get包对应端口的设置
#define COLOR_SRC_PORT 9191
#define COLOR_DEST_PORT 9191

#define PKG_GEN_COUNT 64

static struct rte_ipv4_hdr pkt_ip_hdr;
static struct CoLoR_get pkt_colorget_hdr;
static struct rte_ether_hdr pkt_eth_hdr;

static const struct rte_eth_conf port_conf_default = {
     
	.rxmode = {
     
		.max_rx_pkt_len = RTE_ETHER_MAX_LEN,
	},
};
  1. 函数声明
static void setup_pkt_color_ip_headers(struct rte_ipv4_hdr *ip_hdr, struct CoLoR_get *color_hdr);
//设置COLOR_GET和IP数据报报头
static inline void  show_mac(const char *info,struct rte_ether_addr *addr );
//显示addr指向的MAC地址
static inline void  display_rx_mac(struct rte_mbuf **bufs, uint16_t nb_mbufs);
//显示获取到的数据包的SRC和DEST MAC地址
static inline int port_init(uint16_t port, struct rte_mempool *mbuf_pool);
//初始化网口
static void copy_buf_to_pkt_segs(void* buf, unsigned len, struct rte_mbuf *pkt, unsigned offset);
//如果mbuf分配的第一个片段长度不够,需要对mbuf的下一个结点进行赋值
static inline void copy_buf_to_pkt(void* buf, unsigned len, struct rte_mbuf *pkt, unsigned offset);
//先判断是否可以直接赋给第一个片段,如果不够则需要调用copy_buf_to_pkt_segs
static inline void  pkt_prepare(struct rte_mbuf  *pkt);
//为生成的packet_mbuf进行数据填充
static __attribute__((noreturn)) void lcore_main(struct rte_mempool *mbuf_pool);
//主线程函数
int main(int argc, char *argv[]);
//主函数
  1. 首先将全局变量中的get包、IP数据包包头进行数据填充
static void
setup_pkt_color_ip_headers(struct rte_ipv4_hdr *ip_hdr,
			 struct CoLoR_get *color_hdr)
{
     
	uint16_t *ptr16;
	uint32_t ip_cksum;
	uint16_t pkt_len;

	/*
	 * Initialize COLOR header.
	 * get packet size, version_type, ttl, port_src, port_dst, check_sum
	 */
	pkt_len = sizeof(struct CoLoR_get);
	color_hdr->length = rte_cpu_to_be_16(sizeof(struct CoLoR_get));
	color_hdr->version_type = rte_cpu_to_be_16(1);
	color_hdr->ttl=rte_cpu_to_be_16(88);//ttl
	color_hdr->port_src=rte_cpu_to_be_16(COLOR_SRC_PORT);
	color_hdr->port_dst=rte_cpu_to_be_16(COLOR_DEST_PORT);
	//懒得计算color的checksum

	/*
	 * Initialize IP header.
	 * 
	 */
	pkt_len = (uint16_t) (pkt_len + sizeof(struct rte_ipv4_hdr));
	ip_hdr->version_ihl   = IP_VHL_DEF;
	ip_hdr->type_of_service   = 0;
	ip_hdr->fragment_offset = 0;
	ip_hdr->time_to_live   = IP_DEFTTL;
	ip_hdr->next_proto_id = IPPROTO_IP;//选择报头0
	ip_hdr->packet_id = 0;
	ip_hdr->total_length   = rte_cpu_to_be_16(pkt_len);
	ip_hdr->src_addr = rte_cpu_to_be_32(IP_SRC_ADDR);
	ip_hdr->dst_addr = rte_cpu_to_be_32(IP_DST_ADDR);

	/*
	 * Compute IP header checksum.
	 */
	ptr16 = (unaligned_uint16_t*) ip_hdr;
	ip_cksum = 0;
	ip_cksum += ptr16[0]; ip_cksum += ptr16[1];
	ip_cksum += ptr16[2]; ip_cksum += ptr16[3];
	ip_cksum += ptr16[4];
	ip_cksum += ptr16[6]; ip_cksum += ptr16[7];
	ip_cksum += ptr16[8]; ip_cksum += ptr16[9];

	/*
	 * Reduce 32 bit checksum to 16 bits and complement it.
	 */
	ip_cksum = ((ip_cksum & 0xFFFF0000) >> 16) +
		(ip_cksum & 0x0000FFFF);
	if (ip_cksum > 65535)
		ip_cksum -= 65535;
	ip_cksum = (~ip_cksum) & 0x0000FFFF;
	if (ip_cksum == 0)
		ip_cksum = 0xFFFF;
	ip_hdr->hdr_checksum = (uint16_t) ip_cksum;
}
  1. 接受包的MAC地址打印
/*Function: Display INFORMATION and MAC address.
  * Input: info, rte_ether_addr *addr
  * Output: VOID
*/
static inline void 
show_mac(const char *info,struct rte_ether_addr *addr ){
     
		printf("%s %02" PRIx8 " %02" PRIx8 " %02" PRIx8
			   " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n",
			info,
			(addr->addr_bytes)[0], (addr->addr_bytes)[1],
			(addr->addr_bytes)[2], (addr->addr_bytes)[3],
			(addr->addr_bytes)[4], (addr->addr_bytes)[5]);
}


/*Function: Display SRC and DEST MAC address after receiving some packets
 * Input: bufs, nb_mbufs
 * Output: VOID
*/
static inline void 
display_rx_mac(struct rte_mbuf **bufs, uint16_t nb_mbufs){
     
	struct rte_ether_hdr *eth;
	struct rte_mbuf *m;
	int buf_count;

		for(buf_count=0;buf_count<nb_mbufs;buf_count++)
	{
     
		m=bufs[buf_count];
		
		eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
		
		show_mac("src_MAC :", &(eth->s_addr));
		show_mac("dst_MAC :", &(eth->d_addr));
	}
}
  1. 初始化网口配置
/*
 * Initializes a given port using global settings and with the RX buffers
 * coming from the mbuf_pool passed as a parameter.
 */
static inline int
port_init(uint16_t port, struct rte_mempool *mbuf_pool)
{
     
	struct rte_eth_conf port_conf = port_conf_default;
	const uint16_t rx_rings = 1, tx_rings = 1;
	uint16_t nb_rxd = RX_RING_SIZE;
	uint16_t nb_txd = TX_RING_SIZE;
	int retval;
	uint16_t q;
	struct rte_eth_dev_info dev_info;
	struct rte_eth_txconf txconf;

	if (!rte_eth_dev_is_valid_port(port))
		return -1;

	retval = rte_eth_dev_info_get(port, &dev_info);
	if (retval != 0) {
     
		printf("Error during getting device (port %u) info: %s\n",
				port, strerror(-retval));
		return retval;
	}

	if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE)
		port_conf.txmode.offloads |=
			DEV_TX_OFFLOAD_MBUF_FAST_FREE;

	/* Configure the Ethernet device. */
	retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
	if (retval != 0)
		return retval;

	retval = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd);
	if (retval != 0)
		return retval;

	/* Allocate and set up 1 RX queue per Ethernet port. */
	for (q = 0; q < rx_rings; q++) {
     
		retval = rte_eth_rx_queue_setup(port, q, nb_rxd,
				rte_eth_dev_socket_id(port), NULL, mbuf_pool);
		if (retval < 0)
			return retval;
	}

	txconf = dev_info.default_txconf;
	txconf.offloads = port_conf.txmode.offloads;
	/* Allocate and set up 1 TX queue per Ethernet port. */
	for (q = 0; q < tx_rings; q++) {
     
		retval = rte_eth_tx_queue_setup(port, q, nb_txd,
				rte_eth_dev_socket_id(port), &txconf);
		if (retval < 0)
			return retval;
	}

	/* Start the Ethernet port. */
	retval = rte_eth_dev_start(port);
	if (retval < 0)
		return retval;

	/* Display the port MAC address. */
	struct rte_ether_addr addr;
	retval = rte_eth_macaddr_get(port, &addr);
	if (retval != 0)
		return retval;

	/* Enable RX in promiscuous mode for the Ethernet device. */
	retval = rte_eth_promiscuous_enable(port);
	if (retval != 0)
		return retval;

	return 0;
}
  1. 将全局变量复制到mbuf中(分片复制/整体第一块)

static void
copy_buf_to_pkt_segs(void* buf, unsigned len, struct rte_mbuf *pkt,
		     unsigned offset)
{
     
	struct rte_mbuf *seg;
	void *seg_buf;
	unsigned copy_len;

	seg = pkt;
	while (offset >= seg->data_len) {
     
		offset -= seg->data_len;
		seg = seg->next;
	}
	copy_len = seg->data_len - offset;
	seg_buf = rte_pktmbuf_mtod_offset(seg, char *, offset);
	while (len > copy_len) {
     
		rte_memcpy(seg_buf, buf, (size_t) copy_len);
		len -= copy_len;
		buf = ((char*) buf + copy_len);
		seg = seg->next;
		seg_buf = rte_pktmbuf_mtod(seg, char *);
	}
	rte_memcpy(seg_buf, buf, (size_t) len);
}

/*Function: Memory Copy from the source header to pkt by offset
 * Input: void* buf, unsigned len, struct rte_mbuf *pkt, unsigned offset
 * Output: VOID
 * */
static inline void
copy_buf_to_pkt(void* buf, unsigned len, struct rte_mbuf *pkt, unsigned offset)
{
     
	if (offset + len <= pkt->data_len) {
     
		rte_memcpy(rte_pktmbuf_mtod_offset(pkt, char *, offset),
			buf, (size_t) len);
		return;
	}
	copy_buf_to_pkt_segs(buf, len, pkt, offset);
	//In gereral, it's useless.So note it.
}
  1. 准备数据包,对新申请的mbuf进行填充
/*Funciton: PrePare a COLOR_get Packet
 * Input: struct rte_mbuf  *pkt
 * Output: VOID 
 */
static inline void 
pkt_prepare(struct rte_mbuf  *pkt){
     
	pkt->data_len = sizeof(struct rte_ether_hdr)  +  sizeof(struct rte_ipv4_hdr) + sizeof(struct CoLoR_get);
	//set basic info of pkg
	pkt->nb_segs = 1;//nb_segs;//
	pkt->pkt_len = pkt->data_len;
	pkt->ol_flags = PKT_TX_IPV4;//ol_flags;//
	pkt->vlan_tci = 0;//vlan_tci;//
	pkt->vlan_tci_outer = 0;//vlan_tci_outer;//
	pkt->l2_len = sizeof(struct rte_ether_hdr);
	pkt->l3_len = sizeof(struct rte_ipv4_hdr);
	pkt_eth_hdr.s_addr.addr_bytes[0] = 0x08;
	pkt_eth_hdr.s_addr.addr_bytes[1] = 0x00;
	pkt_eth_hdr.s_addr.addr_bytes[2] = 0x27;
	pkt_eth_hdr.s_addr.addr_bytes[3] = 0xea;
	pkt_eth_hdr.s_addr.addr_bytes[4] = 0x05;
	pkt_eth_hdr.s_addr.addr_bytes[5] = 0x4f;
	pkt_eth_hdr.d_addr.addr_bytes[0] = 0x0a;
	pkt_eth_hdr.d_addr.addr_bytes[1] = 0x00;
	pkt_eth_hdr.d_addr.addr_bytes[2] = 0x27;
	pkt_eth_hdr.d_addr.addr_bytes[3] = 0x00;
	pkt_eth_hdr.d_addr.addr_bytes[4] = 0x00;
	pkt_eth_hdr.d_addr.addr_bytes[5] = 0x14;
	//本来以为这里需要转换成网络字节序,但在调试过程中发现存在问题。
	//因此在Ethernet中无需调整MAC地址的字节序信息
	pkt_eth_hdr.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
	copy_buf_to_pkt(&pkt_eth_hdr, sizeof(pkt_eth_hdr), pkt, 0);
	copy_buf_to_pkt(&pkt_ip_hdr, sizeof(pkt_ip_hdr), pkt, sizeof(struct rte_ether_hdr));
	copy_buf_to_pkt(&pkt_colorget_hdr, sizeof(pkt_colorget_hdr), pkt,
						sizeof(struct rte_ether_hdr) +
						sizeof(struct rte_ipv4_hdr));
	//set ethernet flame header
}
  1. 主线程函数,收包发包逻辑
/*
 * The lcore main. This is the main thread that does the work, reading from
 * an input port and writing to an output port.
 */
static __attribute__((noreturn)) void
lcore_main(struct rte_mempool *mbuf_pool)
{
     
	uint16_t port;
	uint16_t i;
	struct rte_mbuf *tx_bufs_pt[PKG_GEN_COUNT];
	/*
	 * Check that the port is on the same NUMA node as the polling thread
	 * for best performance.
	 */
	RTE_ETH_FOREACH_DEV(port)
		if (rte_eth_dev_socket_id(port) > 0 &&
				rte_eth_dev_socket_id(port) !=
						(int)rte_socket_id())
			printf("WARNING, port %u is on remote NUMA node to "
					"polling thread.\n\tPerformance will "
					"not be optimal.\n", port);

	printf("\nCore %u producing get packets. [Ctrl+C to quit]\n",
			rte_lcore_id());

	/* Run until the application is quit or killed. */
	for (;;) {
     
		/*
		  * Ensure that Only ONE Ethernet Device is working under DPDK module
		  * So FOREACH_DEV means the only one device is used and the device is supposed to send COLOR_get Packet per second
		 */
		RTE_ETH_FOREACH_DEV(port) {
     

			/* malloc some storage memory to initialize the Header:
			  * Ethernet |  IPV4  |  COLOR_GET
			  *     14          |   20     |   88 bytes == 122 bytes
			  * sizeof(struct ether_hdr)  |  sizeof(struct rte_ipv4_hdr) |  sizeof(struct CoLoR_get)
			*/
			struct rte_mbuf *rx_buf[BURST_SIZE];
			const uint16_t nb_rx = rte_eth_rx_burst(port, 0, rx_buf, BURST_SIZE);
			//对于网口port,将队列0的BURST_SIZE个包放入首地址rx_buf中
			//函数返回值是接收到的包的数量,数量应该<= BURST_SIZE

			//if get the pkg from other host
			if(nb_rx!=0)
			{
     
				printf("get packet from port \n");
				display_rx_mac(rx_buf,nb_rx);
				for(i=0; i<nb_rx; i++)
					rte_pktmbuf_free(rx_buf[i]);
				//把这些包的内存空间全部还给缓冲池
			}
			//decode the packet 
			for(int i=0;i<PKG_GEN_COUNT;i++){
     
				struct rte_mbuf  *pkt = (struct rte_mbuf *)rte_pktmbuf_alloc((struct rte_mempool *)mbuf_pool);
				if(pkt==NULL){
     
						rte_exit(EXIT_FAILURE, "Cannot alloc storage memory in  port %"PRIu16 "\n",port);
				}
				pkt_prepare(pkt);
				tx_bufs_pt[i] = pkt;

			}
			int nb_tx=rte_eth_tx_burst(port , 0, tx_bufs_pt, PKG_GEN_COUNT);
			printf("Successfully sent %d packets to MAC:0A:00:27:00:00:14\n",nb_tx);
			sleep(2);
			for(i=0;i<PKG_GEN_COUNT;i++)
			{
     
				if(unlikely(tx_bufs_pt[i]!=NULL))
					rte_pktmbuf_free(tx_bufs_pt[i]);
			}
		}
	}
}
  1. 主函数,初始化EAL环境、缓冲池等。
/*
 * The main function, which does initialization and calls the per-lcore
 * functions.
 */
int
main(int argc, char *argv[])
{
     
	struct rte_mempool *mbuf_pool;
	unsigned nb_ports;
	uint16_t portid;

	/* Initialize the Environment Abstraction Layer (EAL). */
	int ret = rte_eal_init(argc, argv);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

	argc -= ret;
	argv += ret;

	/* Check that there is an even number of ports to send/receive on. */
	nb_ports = rte_eth_dev_count_avail();
	if (nb_ports != 1)
		rte_exit(EXIT_FAILURE, "Error: It only need ONE Ethernet Device!\n");

	/* Creates a new mempool in memory to hold the mbufs. */
	mbuf_pool = rte_pktmbuf_pool_create("TX_POOL", NUM_MBUFS * nb_ports,
		MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

	if (mbuf_pool == NULL)
		rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

	/* Initialize all ports. */
	RTE_ETH_FOREACH_DEV(portid)
		if (port_init(portid, mbuf_pool) != 0)
			rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu16 "\n",
					portid);

	if (rte_lcore_count() > 1)
		printf("\nWARNING: Too many lcores enabled. Only 1 used.\n");

	setup_pkt_color_ip_headers(&pkt_ip_hdr,&pkt_colorget_hdr);
	/* Call lcore_main on the master core only. */
	lcore_main(mbuf_pool);

	return 0;
}

主要流程

主函数的流程

  1. 初始化EAL
  2. 初始化缓冲池
  3. 初始化网口
  4. 设置全局变量中的IP数据报报头、COLOR_GET数据报报头
  5. 调用主线程函数

主线程函数的流程

  1. loop
  2. 物理层收包,如有则输出SRC&DEST MAC地址,释放包的内存;
  3. 生成64个数据帧,将全局变量中的IP数据报报头和GET数据报报头通过内存复制到新申请的mbuf中;
  4. 发包,查看包是否已经完全发出,如果没有完全发出则将发出失败的包内存空间释放掉
  5. goto 1

结果

虚拟机Terminal显示

EAL: Detected 4 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: PCI device 0000:00:03.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100e net_e1000_em
EAL: PCI device 0000:00:08.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100e net_e1000_em

WARNING: Too many lcores enabled. Only 1 used.

Core 0 producing get packets. [Ctrl+C to quit]
Successfully sent 64 packets to MAC:0A:00:27:00:00:14
get packet from port 
src_MAC : 0a 00 27 00 00 14
dst_MAC : ff ff ff ff ff ff
Successfully sent 64 packets to MAC:0A:00:27:00:00:14
get packet from port 
src_MAC : 0a 00 27 00 00 14
dst_MAC : ff ff ff ff ff ff
Successfully sent 64 packets to MAC:0A:00:27:00:00:14
get packet from port 
src_MAC : 0a 00 27 00 00 14
dst_MAC : ff ff ff ff ff ff
src_MAC : 0a 00 27 00 00 14
dst_MAC : ff ff ff ff ff ff
src_MAC : 0a 00 27 00 00 14
dst_MAC : ff ff ff ff ff ff
# 由于宿主机一直在发布ARP通告查询192.168.73.3对应设备的MAC地址
# 所以收到该数据包

宿主机Wireshark抓包

dpdk-packet_producer-自制发包器_第3张图片

你可能感兴趣的:(dpdk,网络)