• 【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,如下图所示:

    在这里插入图片描述

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

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

    在这里插入图片描述

    查看port_conf结构:

    在这里插入图片描述

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

    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,
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    DPDK-L3FWD默认是启用RSS的。

    1.2、RSS打印

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    1.3、RSS关闭

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

    在这里插入图片描述

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

    在这里插入图片描述

    可以看到,获取到的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. */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    字段描述
    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协议进行哈希
    		},
    	},
    	/* ... */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    其中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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    解释如下:按照上面配置,实现机制为,如果数据包四层传输层不是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,
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

    在这里插入图片描述

    可以看到,这时得到的上下行数据包的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140

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

    在这里插入图片描述

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

    3.1、代码逻辑

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

    主要涉及到三个步骤:

    1、rss_key初始化

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

    在这里插入图片描述

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3、获取RSS

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

    4、总结

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

    DPDK-RSS源代码下载链接

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

  • 相关阅读:
    Python安装BeautifulSoup及使用方法,利用BeautifulSoup解析html网页
    【技术解密】RabbitMQ消息积压不消费怎么办?小米给你最佳解决方案!
    NR 物理层编码 S2 - 线性码
    Python的对象与类
    私有化部署大模型:5个.Net开源项目
    【二叉树的遍历】
    xcode15出现大量Duplicate symbols
    SpringMVC基础概述
    数说方法论:针对个体消费者做批量定制化营销
    【shell】 1、bash语法超详细介绍
  • 原文地址:https://blog.csdn.net/weixin_42571882/article/details/128207119