【DPDK】dpdk样例源码解析之五:dpdk-rss

本篇文章介绍DPDK-RSS相关的功能,RSS是网卡提供的分流机制,简单讲就是一个HASH值,如果使用DPDK收包,开启RSS后,会根据配置项将数据包分流到不同的收包队列,用来是实现负载均衡。

通过DPDK-L3FWD样例,添加打印参数进行详细说明,大致分为以下流程:

1、DPDK如何开启RSS(开启和关闭位置,以及开启关闭影响参数);

2、如何查看网卡所支持的RSS选项;

3、DPDK如何配置不同的RSS选项(包括RSS对称算法,根据IP/UDP/TCP进行分流等)获取不同的RSS值;

4、如何通过DPDK携带的API接口获取RSS值(通过将IP五元组信息传入到API中获取到RSS值);

操作系统版本CentOS 8.4

DPDK版本dpdk-20.11.3

1、开启RSS

1.1、RSS开关

借助DPDK-L3FWD样例,在收包API调用之后,打印每个struct rte_mbuf,因为DPDK如果启用RSS时,struct rte_mbuf *m结构中有一个参数会附上数值,那就是rss,如下图所示:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第1张图片

通过打印m->hash.rss即可获取对应数据包的RSS

DPDK初始化网卡端口是会传入port_conf结构体,包含的有RSS相关内容,DPDK-L3FWD样例中,初始化网卡端口API如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第2张图片

查看port_conf结构:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第3张图片

结构体中各个参数解释如下:

static struct rte_eth_conf port_conf = {
	.rxmode = {	// rx收包相关
		.mq_mode = ETH_MQ_RX_RSS, 	//启用RSS, 注释掉该项则关闭RSS
		.max_rx_pkt_len = RTE_ETHER_MAX_LEN,
		.split_hdr_size = 0,
		.offloads = DEV_RX_OFFLOAD_CHECKSUM,
	},
	.rx_adv_conf = {
		.rss_conf = {		// RSS配置相关
			.rss_key = NULL,		// 如果不为空,则配置对称算法KEY
			.rss_hf = ETH_RSS_IP,	//根据IP进行hash
		},
	},
	.txmode = {	// tx发包相关
		.mq_mode = ETH_MQ_TX_NONE,
	},
};

DPDK-L3FWD默认是启用RSS的。

1.2、RSS打印

添加当接收到数据包时,打印该数据包的RSS值代码如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第4张图片

编译生成可执行文件,启动DPDK-L3FWD程序,对端重放数据包,数据包内如如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第5张图片

通过打印双向的数据包,查看双向获取到的RSS值是否一致:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第6张图片

这里可以发现,获取到的RSS值不同。后面会介绍为什么,以及怎么配置可以使获取到的RSS值相同

1.3、RSS关闭

DPDK-L3FWD关闭RSS,代码改动如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第7张图片

重现编译运行样例程序,对端重发同样的ICMP数据包,结果如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第8张图片

可以看到,获取到的RSS和预期的一样全为0。至此,DPDKRSS开关位置以及简单的配置已经介绍完毕,后面章节会详细一点介绍RSS相关的参数以及如何灵活配置达到自己的需求。

2、配置RSS

2.1、RSS结构体

struct rte_eth_conf结构体用于存放网卡RSS相关参数,其它详细信息可参考源代码中注释,写的很清楚,这里只介绍RSS相关:

mq_mode = ETH_MQ_RX_RSS用来标识开启RSS,开启RSS后,可以根据rss_conf结构参数进行HASH计算,包含3个参数,信息如下:

/**
 * A structure used to configure the Receive Side Scaling (RSS) feature
 * of an Ethernet port.
 * If not NULL, the *rss_key* pointer of the *rss_conf* structure points
 * to an array holding the RSS key to use for hashing specific header
 * fields of received packets. The length of this array should be indicated
 * by *rss_key_len* below. Otherwise, a default random hash key is used by
 * the device driver.
 *
 * The *rss_key_len* field of the *rss_conf* structure indicates the length
 * in bytes of the array pointed by *rss_key*. To be compatible, this length
 * will be checked in i40e only. Others assume 40 bytes to be used as before.
 *
 * The *rss_hf* field of the *rss_conf* structure indicates the different
 * types of IPv4/IPv6 packets to which the RSS hashing must be applied.
 * Supplying an *rss_hf* equal to zero disables the RSS feature.
 */
struct rte_eth_rss_conf {
	uint8_t *rss_key;    /**< If not NULL, 40-byte hash key. */
	uint8_t rss_key_len; /**< hash key length in bytes. */
	uint64_t rss_hf;     /**< Hash functions to apply - see below. */
};
字段 描述
rss_key 哈希key,如果为空,则使用网卡rss_key,如果不为空,则为40字节的数组用来作为has key
rss_key_len 哈希key长度,rss_key数组字节数,为空则填写0,不为空则填写40
rss_hf 哈希函数,用来标识根据IP进行哈希,还是根据IP+PORT进行哈希,还是根据IP+PORT+PROTOCOL进行哈希

2.2、RSS参数解释

常见的rss配置如下:

static struct rte_eth_conf port_conf = {
	.rxmode = {
		.mq_mode = ETH_MQ_RX_RSS,	// 启用RSS
        /* ... */
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = NULL,		// 使用网卡默认rss_key, 一般都不能做到对称
			.rss_key_len = 0,		// rss_key数组长度
			.rss_hf = ETH_RSS_IP	// 哈希函数,根据三层IP进行哈希
            		| ETH_RSS_UDP	// 根据四层UDP协议进行哈希
                    | ETH_RSS_TCP,	// 根据四层TCP协议进行哈希
		},
	},
	/* ... */
};

其中rss_hf配置常用的三项参数,具体如下:

#define ETH_RSS_IP ( \
	ETH_RSS_IPV4 | \
	ETH_RSS_FRAG_IPV4 | \
	ETH_RSS_NONFRAG_IPV4_OTHER | \
	ETH_RSS_IPV6 | \
	ETH_RSS_FRAG_IPV6 | \
	ETH_RSS_NONFRAG_IPV6_OTHER | \
	ETH_RSS_IPV6_EX)
	
#define ETH_RSS_UDP ( \
	ETH_RSS_NONFRAG_IPV4_UDP | \
	ETH_RSS_NONFRAG_IPV6_UDP | \
	ETH_RSS_IPV6_UDP_EX)

#define ETH_RSS_TCP ( \
	ETH_RSS_NONFRAG_IPV4_TCP | \
	ETH_RSS_NONFRAG_IPV6_TCP | \
	ETH_RSS_IPV6_TCP_EX)

解释如下:按照上面配置,实现机制为,如果数据包四层传输层不是UDP或者TCP则按照三层网络层IP地址进行hash,对于源地址和目的地址相同的数据包获取到的RSS值相同,如果四层传输层是UDP或者TCP,怎按照IP+PROTOCOL进行哈希。不仅需要源地址和目的地址相同,还需要四层传输层相同。

2.3、RSS配置

可以看出,将RSSrss_key字段设置为NULL,会出现上下行数据包获取到的rss值不一样的问题,如何解决这个问题,就需要将rss_key这个字段设置一下,其中Intel 82599ES网卡使用ixgbe驱动,可以添加如下配置代码完成:

static uint8_t rss_intel_key[40] = {
	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, 
};

static struct rte_eth_conf port_conf = {
	.rxmode = {
		.mq_mode = ETH_MQ_RX_RSS,
		.max_rx_pkt_len = RTE_ETHER_MAX_LEN,
		.split_hdr_size = 0,
		.offloads = DEV_RX_OFFLOAD_CHECKSUM,
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = rss_intel_key,
			.rss_key_len = 40,
			.rss_hf = ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP,
		},
	},
	.txmode = {
		.mq_mode = ETH_MQ_TX_NONE,
	},
};

重新编译代码,运行可执行程序,对端打包之后得到如下结果:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第9张图片

可以看到,这时得到的上下行数据包的rss值相同了。

3、生成RSS

由于rss这个值在网卡硬件接收阶段已经生成了,一次如果我们想要计算这个值,可以通过调用DPDK代码中API接口来实现,这样就可以通过软件方法得到和硬件一致的rss值了。

具体实现代码如下:

/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2016 Intel Corporation
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "rss.h"

static volatile bool force_quit;

static void
signal_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM) {
		printf("\n\nSignal %d received, preparing to exit...\n",
				signum);
		force_quit = true;
	}
}

static void get_tuple(void **tuple, char strsrcIpv4[], char strdstIpv4[], char strsrcIPV6[], char strdstIPV6[], 
	int *input_len) 
{
	//uint32_t input_len;
	//void *tuple;
	int ip_type = 0;
	struct rte_ipv4_tuple ipv4_tuple;
	struct rte_ipv6_tuple ipv6_tuple;
	struct rte_ipv4_hdr ipv4_hdr;
	struct rte_ipv6_hdr ipv6_hdr;
	memset(&ipv4_hdr, 0, sizeof(struct rte_ipv4_hdr));
	memset(&ipv6_hdr, 0, sizeof(struct rte_ipv6_hdr));
	memset(&ipv4_tuple, 0, sizeof(struct rte_ipv4_tuple));
	memset(&ipv6_tuple, 0, sizeof(struct rte_ipv6_tuple));

	inet_pton(AF_INET, strsrcIpv4, &ipv4_hdr.src_addr);
	inet_pton(AF_INET, strdstIpv4, &ipv4_hdr.dst_addr);

	inet_pton(AF_INET6, strsrcIPV6, &ipv6_hdr.src_addr);
	inet_pton(AF_INET6, strdstIPV6, &ipv6_hdr.dst_addr);

	//printf("src: %u, dst: %u\n", ipv4_hdr.src_addr, ipv4_hdr.dst_addr);

	ip_type = 4;
	
	if(ip_type == 4) {
		ipv4_tuple.src_addr = rte_be_to_cpu_32(ipv4_hdr.src_addr);
		ipv4_tuple.dst_addr = rte_be_to_cpu_32(ipv4_hdr.dst_addr);
		//*tuple = &ipv4_tuple;
		memcpy(*tuple, &ipv4_tuple, sizeof(ipv4_tuple));
		*input_len = RTE_THASH_V4_L3_LEN;
	} else if(ip_type == 6) {
		rte_thash_load_v6_addrs(&ipv6_hdr, (union rte_thash_tuple *)&ipv6_tuple);
		//*tuple = &ipv6_tuple;
		memcpy(*tuple, &ipv6_tuple, sizeof(ipv6_tuple));
		*input_len = RTE_THASH_V6_L3_LEN;
	} else {
		printf("IP type Error!\n");
	}
	//printf("input_len = %u\n", *input_len);
	return;
}


int
main(int argc, char **argv)
{
	(void)argc;
	(void)argv;
	
	uint32_t hash1, hash2;

	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGPIPE, signal_handler);
	
	char strsrcIpv4[16] = {"10.20.90.237"};
	char strdstIpv4[16] = {"192.168.10.70"};
	
    char strsrcIPV6[64] = {"2409:891e:80:230b::86"};
    char strdstIPV6[64] = {"2409:891e:80:230b:ecd7:fca4:2de9:a288"};

	// 1.rss_key初始化
	rss_init();

	// 2.从参数中获取五元组信息(ip类型和源/目的IP)
	void *tuple1;
	void *tuple2;
	int input_len1, input_len2;
	get_tuple(&tuple1, strsrcIpv4, strdstIpv4, strsrcIPV6, strdstIPV6, &input_len1);
	get_tuple(&tuple2, strdstIpv4, strsrcIpv4, strdstIPV6, strsrcIPV6, &input_len2);
	
	// 3.获取RSS值
	hash1 = rss_hash_data((uint32_t *)tuple1, input_len1);
	hash2 = rss_hash_data((uint32_t *)tuple2, input_len2);
	
	printf("hash1 = %u\n", hash1);
	printf("hash2 = %u\n", hash2);

	return 0;
}

编译完成之后,直接执行,结果如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第10张图片

可以看到,软件计算出来的rss数值和硬件获取到的一样。

3.1、代码逻辑

简单介绍一下软件生成rss代码逻辑:

主要涉及到三个步骤:

1、rss_key初始化

这一步需要和DPDK中设置的rss_key保持一致,初始化接口如下:

【DPDK】dpdk样例源码解析之五:dpdk-rss_第11张图片

2、从参数中获取五元组信息(ip类型和源/目的IP)

这一步主要为了得到五元组相关的信息,赋值完成之后调用rss_hash_data获取HASH值,即rss值,这里需关注几个参数:

参数1:IP地址,包括源/目的IP;

参数2:IP类型,IPV4或者IPV6

参数3:PORT值,包括源/目的PORT;(该测试样例中没有给PORT赋值)

参数4:input_len,这个值尤其重要,因为它决定了通过哪一层进行哈希,功能和rss_hf类似,具体赋值解释如下:

/**
 * length in dwords of input tuple to
 * calculate hash of ipv4 header only
 */
#define RTE_THASH_V4_L3_LEN	((sizeof(struct rte_ipv4_tuple) -	\
			sizeof(((struct rte_ipv4_tuple *)0)->sctp_tag)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv4 header +
 * transport header
 */
#define RTE_THASH_V4_L4_LEN	 ((sizeof(struct rte_ipv4_tuple)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv6 header only
 */
#define RTE_THASH_V6_L3_LEN	((sizeof(struct rte_ipv6_tuple) -       \
			sizeof(((struct rte_ipv6_tuple *)0)->sctp_tag)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv6 header +
 * transport header
 */
#define RTE_THASH_V6_L4_LEN	((sizeof(struct rte_ipv6_tuple)) / 4)

3、获取RSS

将第2步获取到的参数,传入到rss_hash_data接口,即可获取到对应的rss

4、总结

DPDK-L3FWD代码可参考文章,【DPDK】dpdk样例源码解析之三:dpdk-l3fwd_001

DPDK-RSS源代码下载链接

有问题欢迎在评论区探讨~

你可能感兴趣的:(【DPDK】,linux,dpdk,rss,c,dpdk-rss)