官方文檔查看地址:
http://doc.dpdk.org/guides/sample_app_ug/l3_forward.html?highlight=lpm
PDF下載地址:
https://www.intel.com/content/www/us/en/embedded/technology/packet-processing/dpdk/dpdk-sample-applications-user-guide.html
本篇難度係數:
翻譯:☆☆☆☆☆
理解:★★☆☆☆
20. L3轉發示例應用程序
L3轉發應用程序是使用DPDK進行包處理的一個簡單示例。應用程序執行L3轉發。
20.1 概述
該應用程序演示瞭如何在DPDK中使用散列和LPM庫來實現數據包轉發。初始化和運行時路徑與L2轉發示例應用程序(在實際和虛擬化環境中)非常相似。與L2 Forwarding示例應用程序的主要區別在於轉發決策是基於從輸入數據包讀取的信息做出的。
查找方法基於散列或基於LPM,並在運行時選擇。當所選查找方法基於散列時,使用散列對象來模擬流分類階段。散列對象用於與流表相關,以在運行時將每個輸入包映射到其流。
哈希查找鍵由DiffServ 5元組表示,該元組由從輸入包讀取的以下字段組成:源IP地址,目標IP地址,協議,源端口和目標端口。從標識的流表條目中讀取輸入包的輸出接口的ID。應用程序使用的流集靜態配置並在初始化時加載到散列中。當所選查找方法基於LPM時,LPM對象用於模擬IPv4數據包的轉發階段。LPM對象用作路由表,以在運行時標識每個輸入數據包的下一跳。
該LPM查找鍵是通過從輸入數據包讀出的目標IP地址字段所表示。輸入數據包的輸出接口的ID是LPM查找返回的下一跳。應用程序使用的一組LPM規則是靜態配置的,並在初始化時加載到LPM對象中。
在示例應用程序中,基於散列的轉發支持IPv4和IPv6。基於LPM的轉發僅支持IPv4。
20.2 編譯應用程序
要編譯示例應用程序,請參閱編譯示例應用程序(http://doc.dpdk.org/guides/sample_app_ug/compiling.html)。
該應用程序位於l3fwd
子目錄中。
20.3 運行應用程序
該應用程序有許多命令行選項:
./l3fwd [EAL options] -- -p PORTMASK
[-P]
[-E]
[-L]
--config(port,queue,lcore)[,(port,queue,lcore)]
[--eth-dest=X,MM:MM:MM:MM:MM:MM]
[--enable-jumbo [--max-pkt-len PKTLEN]]
[--no-numa]
[--hash-entry-num]
[--ipv6]
[--parse-ptype]
[--per-port-pool]
例如:
-p PORTMASK
: 要配置的端口的十六進制位掩碼-P
:可選,將所有端口設置爲混雜模式,以便無論數據包的以太網MAC目標地址如何都接收數據包。如果沒有此選項,則只接收以太網MAC目標地址設置爲端口以太網地址的數據包。-E
: 可選,啓用完全匹配。-L
: 可選,啓用最長前綴匹配。--config (port,queue,lcore)[,(port,queue,lcore)]
: 決定將端口的哪些隊列映射到哪些核心。--eth-dest=X,MM:MM:MM:MM:MM:MM
: 可選,端口X的以太網目標地址。--enable-jumbo
: 可選,啓用巨型幀( jumbo幀)。--max-pkt-len
: 可選,在啓用jumbo的前提下,最大包長度爲十進制(64-9600)。--no-numa
: 可選,禁用numa意識。--hash-entry-num
: 可選,指定要設置的十六進制的哈希條目號。--ipv6
: 可選,如果運行ipv6數據包則設置。--parse-ptype
:可選,設置爲使用軟件來分析數據包類型。如果沒有此選項,硬件將檢查數據包類型。--per-port-pool
:可選,設置爲每個端口使用獨立的緩衝池。如果沒有此選項,則單個緩衝池將用於所有端口。
例如,考慮具有8個物理核心的雙處理器插槽平臺,其中核心0-7和16-23出現在插槽0上,而核心8-15和24-31出現在插槽1上。
要在兩個端口之間啓用L3轉發,假設兩個端口位於同一個套接字中,使用兩個核心(核心1和核心2)(也在同一個套接字中),請使用以下命令:
./build/l3fwd -l 1,2 -n 4 -- -p 0x3 --config="(0,0,1),(1,0,2)"
在此命令中:
-l
選項啓用核心1,2-p
選項啓用端口0和1--config
選項在每個端口上啓用一個隊列,並將每個(端口,隊列)對映射到特定核心。下表顯示了此示例中的映射:
端口 | 隊列 | lcore | 描述 |
---|---|---|---|
0 | 0 | 1 | 將隊列0從端口0映射到lcore 1。 |
1 | 0 | 2 | 將隊列0從端口1映射到lcore 2。 |
有關運行應用程序和環境抽象層(EAL)選項的一般信息,請參閱“ DPDK入門指南”。
20.4 說明
以下部分提供了示例應用程序代碼的一些說明。如概述部分所述,初始化和運行時路徑與L2轉發示例應用程序(在實際和虛擬化環境中)非常相似。以下部分描述了L3轉發示例應用程序特有的方面。
20.4.1 哈希初始化
使用從全局數組中讀取的預配置條目創建和加載哈希對象,然後生成預期的5元組作爲密鑰以保持與實際流的一致,以便於在4M / 8M /上執行哈希性能測試16M流量。
注意
Hash初始化將設置ipv4和ipv6哈希表,並根據變量ipv6的值填充任一表。爲了支持具有高達8M單向流/ 16M雙向流的哈希性能測試,populate_ipv4_many_flow_into_table()函數將使用指定的哈希表條目號(默認爲4M)填充哈希表。
注意
可以在命令行中使用-ipv6指定全局變量ipv6的值。全局變量hash_entry_number的值,用於指定散列性能測試中所有已使用端口的總哈希條目號,可以在命令行中使用-hash-entry-num VALUE指定,默認值爲4。
#if (APP_LOOKUP_METHOD == APP_LOOKUP_EXACT_MATCH)
static void
setup_hash(int socketid)
{
// ...
if (hash_entry_number != HASH_ENTRY_NUMBER_DEFAULT) {
if (ipv6 == 0) {
/* populate the ipv4 hash */
populate_ipv4_many_flow_into_table(ipv4_l3fwd_lookup_struct[socketid], hash_entry_number);
} else {
/* populate the ipv6 hash */
populate_ipv6_many_flow_into_table( ipv6_l3fwd_lookup_struct[socketid], hash_entry_number);
}
} else
if (ipv6 == 0) {
/* populate the ipv4 hash */
populate_ipv4_few_flow_into_table(ipv4_l3fwd_lookup_struct[socketid]);
} else {
/* populate the ipv6 hash */
populate_ipv6_few_flow_into_table(ipv6_l3fwd_lookup_struct[socketid]);
}
}
}
#endif
20.4.2 LPM初始化
使用從全局數組中讀取的預配置條目,創建並加載LPM對象。
#if (APP_LOOKUP_METHOD == APP_LOOKUP_LPM)
static void
setup_lpm(int socketid)
{
unsigned i;
int ret;
char s[64];
/ *創建LPM表* /
snprintf(s, sizeof(s), "IPV4_L3FWD_LPM_%d", socketid);
ipv4_l3fwd_lookup_struct[socketid] = rte_lpm_create(s, socketid, IPV4_L3FWD_LPM_MAX_RULES, 0);
if (ipv4_l3fwd_lookup_struct[socketid] == NULL)
rte_exit(EXIT_FAILURE, "Unable to create the l3fwd LPM table"
" on socket %d\n", socketid);
/ *填充LPM表* /
for (i = 0; i < IPV4_L3FWD_NUM_ROUTES; i++) {
/ *跳過未使用的端口* /
if ((1 << ipv4_l3fwd_route_array[i].if_out & enabled_port_mask) == 0)
continue;
ret = rte_lpm_add(ipv4_l3fwd_lookup_struct[socketid], ipv4_l3fwd_route_array[i].ip,
ipv4_l3fwd_route_array[i].depth, ipv4_l3fwd_route_array[i].if_out);
if (ret < 0) {
rte_exit(EXIT_FAILURE, "Unable to add entry %u to the "
"l3fwd LPM table on socket %d\n", i, socketid);
}
printf("LPM: Adding route 0x%08x / %d (%d)\n",
(unsigned)ipv4_l3fwd_route_array[i].ip, ipv4_l3fwd_route_array[i].depth, ipv4_l3fwd_route_array[i].if_out);
}
}
#endif
20.4.3 基於散列查找的數據包轉發
對於每個輸入數據包,數據包轉發操作由IPv4數據包的l3fwd_simple_forward()或simple_ipv4_fwd_4pkts()函數或IPv6數據包的simple_ipv6_fwd_4pkts()函數完成。l3fwd_simple_forward()函數爲接收的任意數量的突發數據包提供IPv4和IPv6數據包轉發的基本功能,並且基於散列的查找的數據包轉發決策(即,數據包的輸出接口的標識)已通過get_ipv4_dst_port()或get_ipv6_dst_port()函數完成。get_ipv4_dst_port()函數如下所示:
static inline uint8_t
get_ipv4_dst_port(void *ipv4_hdr, uint16_t portid, lookup_struct_t *ipv4_l3fwd_lookup_struct)
{
int ret = 0;
union ipv4_5tuple_host key;
ipv4_hdr = (uint8_t *)ipv4_hdr + offsetof(struct ipv4_hdr, time_to_live);
m128i data = _mm_loadu_si128(( m128i*)(ipv4_hdr));
/ *獲取5元組:dst端口,src端口,dst IP地址,src IP地址和協議* /
key.xmm = _mm_and_si128(data, mask0);
/ *查找目的地端口* /
ret = rte_hash_lookup(ipv4_l3fwd_lookup_struct, (const void *)&key);
return (uint8_t)((ret < 0)? portid : ipv4_l3fwd_out_if[ret]);
}
get_ipv6_dst_port()函數類似於get_ipv4_dst_port()函數。
simple_ipv4_fwd_4pkts()和simple_ipv6_fwd_4pkts()函數針對連續4個有效的ipv4和ipv6數據包進行了優化,它們利用多緩衝區優化來提高轉發數據包的性能,並使用哈希表完全匹配。simple_ipv4_fwd_4pkts()的關鍵代碼片段如下所示:
static inline void
simple_ipv4_fwd_4pkts(struct rte_mbuf* m[4], uint16_t portid, struct lcore_conf *qconf)
{
// ...
data[0] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[0], unsigned char *) + sizeof(struct ether_hdr) + offsetof(struct ipv4_hdr, time_to_live)));
data[1] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[1], unsigned char *) + sizeof(struct ether_hdr) + offsetof(struct ipv4_hdr, time_to_live)));
data[2] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[2], unsigned char *) + sizeof(struct ether_hdr) + offsetof(struct ipv4_hdr, time_to_live)));
data[3] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[3], unsigned char *) + sizeof(struct ether_hdr) + offsetof(struct ipv4_hdr, time_to_live)));
key[0].xmm = _mm_and_si128(data[0], mask0);
key[1].xmm = _mm_and_si128(data[1], mask0);
key[2].xmm = _mm_and_si128(data[2], mask0);
key[3].xmm = _mm_and_si128(data[3], mask0);
const void *key_array[4] = {&key[0], &key[1], &key[2],&key[3]};
rte_hash_lookup_bulk(qconf->ipv4_lookup_struct, &key_array[0], 4, ret);
dst_port[0] = (ret[0] < 0)? portid:ipv4_l3fwd_out_if[ret[0]];
dst_port[1] = (ret[1] < 0)? portid:ipv4_l3fwd_out_if[ret[1]];
dst_port[2] = (ret[2] < 0)? portid:ipv4_l3fwd_out_if[ret[2]];
dst_port[3] = (ret[3] < 0)? portid:ipv4_l3fwd_out_if[ret[3]];
// ...
}
simple_ipv6_fwd_4pkts()函數類似於simple_ipv4_fwd_4pkts()函數。
已知問題:具有擴展的IP數據包或非TCP / UDP的IP數據包在此模式下無法正常工作。
20.4.4 基於LPM查找的數據包轉發
對於每個輸入數據包,數據包轉發操作由l3fwd_simple_forward()函數完成,但基於LPM的查找的數據包轉發決策(即數據包的輸出接口的標識)由get_ipv4_dst_port()函數完成如下:
static inline uint16_t
get_ipv4_dst_port(struct ipv4_hdr *ipv4_hdr, uint16_t portid, lookup_struct_t *ipv4_l3fwd_lookup_struct)
{
uint8_t next_hop;
return ((rte_lpm_lookup(ipv4_l3fwd_lookup_struct, rte_be_to_cpu_32(ipv4_hdr->dst_addr), &next_hop) == 0)? next_hop : portid);
}