1 rss 的作用
rss 是網卡提供的分流機制。用來將報表分流到不同的收包隊列,以提高收包性能。
引用 Intel 82599 10 GbE Controller Datasheet 其中的 Section 7.1.2.8.1, RSS Hash Function
一節。
- The receive packet is parsed into the header fields used by the hash operation (such as IP addresses, TCP port, etc.)
- A hash calculation is performed.
The 82599 supports a single hash function, as defined by MSFT RSS.
The 82599 therefore does not indicate to the device driver which hash function is used.
The 32-bit result is fed into the packet receive descriptor.- The seven LSBs of the hash result are used as an index into a 128-entry redirection table.
Each entry provides a 4-bit RSS output index.When RSS is enabled, the 82599 provides software with the following information as:
- Required by Microsoft (MSFT) RSS
- Provided for device driver assist:
- * A Dword result of the MSFT RSS hash function, to be used by the stack for flow classification, is written into the receive packet descriptor (required by MSFT RSS).
- * A 4-bit RSS Type field conveys the hash function used for the specific packet (required by MSFT RSS).
Enabling rules:
- RSS is enabled in the MRQC register.
- RSS enabling cannot be done dynamically while it must be preceded by a software reset.
- RSS status field in the descriptor write-back is enabled when the RXCSUM.PCSD bit is set (fragment checksum is disabled).
RSS is therefore mutually exclusive with UDP fragmentation checksum offload.- Support for RSS is not provided when legacy receive descriptor format is used.
Disabling rules:
- Disabling RSS on the fly is not allowed, and the 82599 must be reset after RSS is disabled.
- When RSS is disabled, packets are assigned an RSS output index = zero.
When multiple request queues are enabled in RSS mode, un-decodable packets are assigned an RSS output index = zero.
The 32-bit tag (normally a result of the hash function) equals zero.
注意:
- 沒有開啓 rss:
-
- 則所有報文只會從一個硬件隊列來收包。
- 開啓 rss 後:
-
- rss 會解釋報文的 l3 層信息:ip 地址。甚至 l4 層信息:tcp/udp 端口。
-
-
- 報文會經過 hash function 計算出一個 uint32_t 的
rss hash
。填充到 struct rte_mbuf 的 hash.rss字段中。
- 報文會經過 hash function 計算出一個 uint32_t 的
-
-
-
- rss hash 的 低7位 會映射到 4位長 的
RSS output index
。
- rss hash 的 低7位 會映射到 4位長 的
-
-
- 無法解釋的 報文,
rss hash
和RSS output index
設置爲 0。
- 無法解釋的 報文,
2 DPDK rss 的 配置
21 用於 DPDK rss 的 配置 的數據結構
struct rte_eth_rss_conf 用於配置網卡的 rss。
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 | rss_key 數組。如果 爲 NULL,留給網卡設置 rss_key。 |
rss_key_len | rss_key 數組的字節數。 |
rss_hf | 需要對報文的分析的元組類型。常用的組合有 l3: ETH_RSS_IP , l3+l4: ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP 。 |
2.2 常見 rss 的配置
static struct rte_eth_conf eth_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 /* 通過 l3 tuple 計算 rss hash */
| ETH_RSS_UDP /* + 通過 UDP tuple 計算 rss hash */
| ETH_RSS_TCP /* + 通過 TCP tuple 計算 rss hash */
},
/* ... */
}
/* ... */
};
問題:
- 將 rss_key 字段設置爲 NULL, 留給 網卡設置 rss key。不是一個好主意。
- 因爲不同的驅動,rss key 有可能設置爲對稱,也有可能設置爲非對稱的。所以不同驅動網卡的分流效果,將無法預測。
比如網卡82599ES 使用的是 ixgbe 的驅動。
在 ixgbe_rss_configure() 中,會使用 對稱的 rss key 作爲默認值。
ixgbe 的驅動 用於 rss 配置的代碼:
/* filepath:
* dpdk/drivers/net/ixgbe/ixgbe_rxtx.c
*/
/* ixgbe 的驅動 使用了 對稱的 rss key 作爲默認值。 */
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 void
ixgbe_rss_configure(struct rte_eth_dev *dev)
{
/* ... */
if (rss_conf.rss_key == NULL)
rss_conf.rss_key = rss_intel_key; /* <-- Default hash key */
ixgbe_hw_rss_hash_set(hw, &rss_conf);
}
應用筆記:
- 當使用發包機來測試的時候,如果發包機產出的報文的元組信息過於有規律,而網卡使用了對稱的 rss_key,將會很大可能分流到相同的硬件收包隊列,從而影響性能。
- 特別當進行 2544 吞吐量測試的時候,這個問題更加需要注意。
- 一旦發現分流效果不好。則可以 使用非對稱的 rss_key,並且將 rss_hf 設置爲
ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP
。
2.3 顯示指定 rss key
爲了避免驅動使用了不合適的 rss_key。所以最好還是自己顯示指定。
下面的代碼,就提供了 對稱 和 非對稱 rss key 以供選擇。
rss_hash_key 的數值可以參考論文Scalable TCP Session Monitoring withSymmetric Receive-side Scaling。
/* 使用 宏來選擇 默認的 rss 配置 */
#define RSS_HASH_KEY_DEFAULT rss_hash_key_symmetric
#define RSS_HASH_FUNCTION_DEFAULT (ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP)
/* symmetric_rss_key */
static uint8_t rss_hash_key_symmetric[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
};
/* asymmetric_rss_key */
static uint8_t rss_hash_key_asymmetric[40] =
{ 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2
, 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0
, 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4
, 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c
, 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa
};
/* rss config */
static struct rte_eth_conf eth_conf = {
.rxmode = {
.mq_mode = ETH_MQ_RX_RSS /* ETH_MQ_RX_RSS */
},
.rx_adv_conf = {
.rss_conf = {
.rss_key = RSS_HASH_KEY_DEFAULT, /* <-- 顯示指定 rss_key */
.rss_key_len = RTE_DIM(RSS_HASH_KEY_DEFAULT), /* rss_key 數組的字節數 */
.rss_hf = RSS_HASH_FUNCTION_DEFAULT /* <-- 顯示指定 rss hash function */
},
/* ... */
}
/* ... */
};
/* 靜態的 rss 配置方法 */
int error_code;
error_code = rte_eth_dev_configure(port_id
, rx_queue_count
, tx_queue_count
, ð_conf /* <-- 傳入上面定義的 eth_conf */
);
2.4 動態的 rss 配置方法
除了上面靜態的 rss 配置方法。也可以通過 rte_eth_dev_rss_hash_update() 來動態配置。
下面的例子,就是一個 rte_eth_dev_rss_hash_update() 的包裹函數。
static int
port_set_rss_config(uint_8 port_id
, uint64_t hash_function
, uint8_t *hash_key
, uint8_t hash_key_len
)
{
struct rte_eth_rss_conf rss_conf;
int error_code;
rss_conf.rss_key = hash_key;
rss_conf.rss_key_len = hash_key_len;
rss_conf.rss_hf = hash_function;
error_code = rte_eth_dev_rss_hash_update(port_id, &rss_conf);
if (0 != error_code) {
goto fail;
}
return 0;
/* error handle */
fail:
switch (error_code) {
case -ENODEV:
printf("port index %d invalid\n", port_id);
break;
case -ENOTSUP:
printf("operation not supported by device\n");
break;
default:
printf("operation failed - error_code=%d\n", error_code);
break;
}
return -1;
}
3 如何計算 rss hash
3.1 算法
rss hash 的算法可以參考 Intel 82599 10 GbE Controller Datasheet 其中的 Section 7.1.2.8.1, RSS Hash Function
一節。
算法的僞代碼表示如下:
ComputeHash(input[], N)
For hash-input input[] of length N bytes (8N bits) and a random secret key K of 320 bits
Result = 0;
For each bit b in input[] {
if (b == 1) then Result ^= (left-most 32 bits of K);
shift K left 1 bit position;
}
return Result;
注意:
- 輸入參數 input[],表示 元組信息,是以小端對齊的。
- 輸入參數 N,表示 元組信息 的字節數。
- K,就是 rss key。以 uint8_t [] 數組形式表示:K[0] K[1] K[2] … K[k-1]。總共 k 個字節。
- K[0] 是最左端的 字節。K[0] 的 MSB(最高位)是最左端的比特位。
- K[k-1] 是最右端的 字節。K[k-1] 的 LSB(最低位)是最右端的比特位。
例子:
項目 | 數值(十進制) | uint8_t [] 數組形式表示 (小端對齊十六進制) |
---|---|---|
src_ipv4 | 66.9.149.187 | {0xbb, 0x95, 0x09, 0x42} |
dst_ipv4 | 161.142.100.80 | {0x50, 0x64, 0x8e, 0xa1} |
sport | 2794 | {0xea, 0x0a} |
dport | 1766 | {0xe6, 0x06} |
- 如果 只考慮 ipv4 的二元組信息,則輸入參數的數值爲:
輸入參數 | 數值 |
---|---|
input[] | {0xbb, 0x95, 0x09, 0x42, 0x50, 0x64, 0x8e, 0xa1} |
N | 8 |
- 如果 考慮 ipv4 和 port 的四元組信息,則輸入參數的數值爲:
輸入參數 | 數值 |
---|---|
input[] | {0xbb, 0x95, 0x09, 0x42, 0x50, 0x64, 0x8e, 0xa1, 0xea, 0x0a, 0xe6, 0x06} |
N | 12 |
3.2 DPDK 的 rss hash 實現函數
應用筆記:
- 在實際的應用中。有的時候需要使用 軟件方法,來計算出與網卡硬件一致的
rss hash
值。
DPDK rss hash 實現函數 有兩個,分別是 rte_softrss() 和 rte_softrss_be()。
它們都定義在 dpdk/lib/librte_hash/rte_thash.h
中。
注意:
- rte_softrss() 和 rte_softrss_be() 都可以對相同的
input_tuple
計算出相同的 rss hash。 - rte_softrss_be() 在運算的時候,對
rss_key
少執行了 rte_cpu_to_be_32() 大小端轉換。所以相比 rte_softrss() 會高效點。 - rte_softrss_be() 所需要 rss_key 是需要 經過 rte_convert_rss_key() 來預先做 大小端轉換的。
DPDK rte_softrss() 的源代碼:
/**
* Generic implementation. Can be used with original rss_key
* @param input_tuple
* Pointer to input tuple
* @param input_len
* Length of input_tuple in 4-bytes chunks
* @param rss_key
* Pointer to RSS hash key.
* @return
* Calculated hash value.
*/
static inline uint32_t
rte_softrss(uint32_t *input_tuple, uint32_t input_len,
const uint8_t *rss_key)
{
uint32_t i, j, ret = 0;
for (j = 0; j < input_len; j++) {
for (i = 0; i < 32; i++) {
if (input_tuple[j] & (1 << (31 - i))) {
ret ^= rte_cpu_to_be_32(((const uint32_t *)rss_key)[j]) << i |
(uint32_t)((uint64_t)(rte_cpu_to_be_32(((const uint32_t *)rss_key)[j + 1])) >>
(32 - i));
}
}
}
return ret;
}
DPDK rte_softrss_be() 的源代碼:
/**
* Prepare special converted key to use with rte_softrss_be()
* @param orig
* pointer to original RSS key
* @param targ
* pointer to target RSS key
* @param len
* RSS key length
*/
static inline void
rte_convert_rss_key(const uint32_t *orig, uint32_t *targ, int len)
{
int i;
for (i = 0; i < (len >> 2); i++)
targ[i] = rte_be_to_cpu_32(orig[i]);
}
/**
* Optimized implementation.
* If you want the calculated hash value matches NIC RSS value
* you have to use special converted key with rte_convert_rss_key() fn.
* @param input_tuple
* Pointer to input tuple
* @param input_len
* Length of input_tuple in 4-bytes chunks
* @param *rss_key
* Pointer to RSS hash key.
* @return
* Calculated hash value.
*/
static inline uint32_t
rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
const uint8_t *rss_key)
{
uint32_t i, j, ret = 0;
for (j = 0; j < input_len; j++) {
for (i = 0; i < 32; i++) {
if (input_tuple[j] & (1 << (31 - i))) {
ret ^= ((const uint32_t *)rss_key)[j] << i |
(uint32_t)((uint64_t)(((const uint32_t *)rss_key)[j + 1]) >> (32 - i));
}
}
}
return ret;
}
另外,rte_thash.h 也定義的 struct rte_ipv4_tuple
和 struct rte_ipv6_tuple
用來放置報文的元組信息。
注意:
- struct rte_ipv4_tuple 所有字段都需要以小端對齊。
- struct rte_ipv6_tuple 所有字段都需要以小端對齊。
/**
* 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)
/**
* IPv4 tuple
* addresses and ports/sctp_tag have to be CPU byte order
*/
struct rte_ipv4_tuple {
uint32_t src_addr;
uint32_t dst_addr;
union {
struct {
uint16_t dport;
uint16_t sport;
};
uint32_t sctp_tag;
};
};
/**
* IPv6 tuple
* Addresses have to be filled by rte_thash_load_v6_addr()
* ports/sctp_tag have to be CPU byte order
*/
struct rte_ipv6_tuple {
uint8_t src_addr[16];
uint8_t dst_addr[16];
union {
struct {
uint16_t dport;
uint16_t sport;
};
uint32_t sctp_tag;
};
};
union rte_thash_tuple {
struct rte_ipv4_tuple v4;
struct rte_ipv6_tuple v6;
3.3 rss hash 的例子
以下的例子參考的是 dpdk/app/test/test_thash.c
。
union rte_thash_tuple tuple;
uint32_t rss_l3_original, rss_l3l4_original;
uint32_t rss_l3_converted, rss_l3l4_converted;
uint8_t default_rss_key[] = {
0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa,
};
uint8_t rss_key_be[RTE_DIM(default_rss_key)];
tuple.v4.src_addr = IPv4(66, 9, 149, 187);
tuple.v4.dst_addr = IPv4(161, 142, 100, 80);
tuple.v4.sport = 2794;
tuple.v4.dport = 1766;
/*Calculate hash with original key*/
rss_l3_original = rte_softrss((uint32_t *)&tuple,
RTE_THASH_V4_L3_LEN, default_rss_key);
rss_l3l4_original = rte_softrss((uint32_t *)&tuple,
RTE_THASH_V4_L4_LEN, default_rss_key);
assert(0x323e8fc2 == rss_l3_original);
assert(0x51ccc178 == rss_l3l4_original);
/*Calculate hash with converted key*/
rss_l3_converted = rte_softrss_be((uint32_t *)&tuple,
RTE_THASH_V4_L3_LEN, rss_key_be);
rss_l3l4_converted = rte_softrss_be((uint32_t *)&tuple,
RTE_THASH_V4_L4_LEN, rss_key_be);
assert(0x323e8fc2 == rss_l3_converted);
assert(0x51ccc178 == rss_l3l4_converted);
ref:
Intel 82599 10 GbE Controller Datasheet
Scalable TCP Session Monitoring withSymmetric Receive-side Scaling
DPDK 之 Symmetric Receive-side Scaling