官方文档查看地址:
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);
}