前面讲了vpp 软件Packet Flow Hash基础架构,其中在hash算法中有一个对称hash算法(handoff-eth-sym 1 Ethernet/IPv4/IPv6/MPLS headers Symmetric),对称hash算法的作用是将同一条流的两个方向的报文hash到一个cpu核心来处理。在软件逻辑中存在基于CPU核的L34会话业务及DPVS软件(dpvs中一般是使用网卡FDIR功能实现)中都有对称hash的应用,以提升网络处理性能。
在上家单位实现过基于vpp实现tcp,sctp报文的去重,保序、分片重组功能,以提供应用层完整的业务报文。就是利用网卡对称RSS实现网络性能优化,以减少vpp handoff功能的使用(一般会存在10%左右的性能开销)。可以参考以前文章:dpdk X710 symmetric RSS Vpp使用dpdk RSS功能部分报文不生效问题 。
下面以代码展开说明vpp 软件中对称hash处理逻辑。
1、普通IPv4报文,分别以Sip + Dip + protocol 做异或运算。
2、普通IPv6报文,分别以Sip + Dip + protocol 做异或运算。
3、mpls报文,最多支持五层mpls 嵌套,解析内层报文如果是ipv4或ipv6 ,以Sip + Dip + protocol 做异或运算,否则以内层mpls 标签数值做hash。
4、vlan或dot1ad报文,解析内层报文如果是ipv4或ipv6 ,以Sip + Dip + protocol 做异或运算,否则以 ethernet type做hash。
static inline u64
eth_get_sym_key (ethernet_header_t * h0)
{
u64 hash_key;
/*1、普通ipv4报文以sip + dip + protocl 计算hash key*/
if (PREDICT_TRUE (h0->type) == clib_host_to_net_u16 (ETHERNET_TYPE_IP4))
{
ip4_header_t *ip = (ip4_header_t *) (h0 + 1);
hash_key =(u64) (ip->src_address.as_u32 ^
ip->dst_address.as_u32 ^ ip->protocol);
}
/*2、普通ipv6报文以sip + dip + protocl 计算hash key*/
else if (h0->type == clib_host_to_net_u16 (ETHERNET_TYPE_IP6))
{
ip6_header_t *ip = (ip6_header_t *) (h0 + 1);
hash_key = (u64) (ip->src_address.as_u64[0] ^
ip->src_address.as_u64[1] ^
ip->dst_address.as_u64[0] ^
ip->dst_address.as_u64[1] ^ ip->protocol);
}
/*3、普通mpls报文计算hash key*/
else if (h0->type == clib_host_to_net_u16 (ETHERNET_TYPE_MPLS))
{
hash_key = mpls_get_key ((mpls_unicast_header_t *) (h0 + 1));
}
/*4、vlan或dot1ad报文计算hash key*/
else if (PREDICT_FALSE((h0->type == clib_host_to_net_u16 (ETHERNET_TYPE_VLAN))
|| (h0->type == clib_host_to_net_u16 (ETHERNET_TYPE_DOT1AD))))
{
ethernet_vlan_header_t *outer = (ethernet_vlan_header_t *) (h0 + 1);
outer = (outer->type == clib_host_to_net_u16 (ETHERNET_TYPE_VLAN)) ?outer + 1 : outer;
if (PREDICT_TRUE (outer->type) ==
clib_host_to_net_u16 (ETHERNET_TYPE_IP4))
{
ip4_header_t *ip = (ip4_header_t *) (outer + 1);
hash_key = (u64) (ip->src_address.as_u32 ^
ip->dst_address.as_u32 ^ ip->protocol);
}
else if (outer->type == clib_host_to_net_u16 (ETHERNET_TYPE_IP6))
{
ip6_header_t *ip = (ip6_header_t *) (outer + 1);
hash_key = (u64) (ip->src_address.as_u64[0] ^
ip->src_address.as_u64[1] ^
ip->dst_address.as_u64[0] ^
ip->dst_address.as_u64[1] ^ ip->protocol);
}
else if (outer->type == clib_host_to_net_u16 (ETHERNET_TYPE_MPLS))
{
hash_key = mpls_get_key ((mpls_unicast_header_t *) (outer + 1));
}
else
{
hash_key = outer->type;
}
}
else
{
hash_key = 0;
}
return hash_key;
}
通过分析代码,软件对称hash因子实现还是比较简单,不能做到hash均匀,vpp只是简单实现了此算法。vpp中flow hash使用的地方还是相当多的,也曾经因hash遇到过很多问题。
在vpp 19.01版本中ipsec使用vpp自带的ikev2协商,遇到过因硬件RSS算法(默认是L34层做hash key)导致协商失败的问题。大致原因是因为ikev2在协商过程中创建了以cpu核资源,通过spi查询到ipsec sa资源。在网卡开启多队列之后,ikev2第一阶段,端口号500,rss hash到worker1上,ikev2 第二阶段,因存在nat 端口号4500,rss hash 到worker2上面,导致查询不到sa资源,协商失败。解决方案也比较简单,就是修改网卡硬件rss key因子,以L3层为key做hash。
在vxlan报文封装过程中,计算外层UDP报文src port端口号,就是以内层报文来做hash key计算得到端口号。因资源池存在防火墙会话,内层报文数据量太大,导致防火墙会话满而丢包的问题。通过优化hash算法解决此问题。
static inline u32
vnet_l2_compute_flow_hash (vlib_buffer_t * b)
{
ethernet_header_t *eh = vlib_buffer_get_current (b);
u8 *l3h = (u8 *) eh + vnet_buffer (b)->l2.l2_len;
u16 ethertype = clib_net_to_host_u16 (*(u16 *) (l3h - 2));
/*1、ipv4 报文 以报文五元组做hash key*/
if (ethertype == ETHERNET_TYPE_IP4)
return ip4_compute_flow_hash ((ip4_header_t *) l3h, IP_FLOW_HASH_DEFAULT);
/*2、ipv6 报文 以报文五元组 + flowtable 做hash key*/
else if (ethertype == ETHERNET_TYPE_IP6)
return ip6_compute_flow_hash ((ip6_header_t *) l3h, IP_FLOW_HASH_DEFAULT);
else
{/*2、二层报文 以报文smac + dmac + ethernet type 做hash key*/
u32 a, b, c;
u32 *ap = (u32 *) & eh->dst_address[2];
u32 *bp = (u32 *) & eh->src_address[2];
a = *ap;
b = *bp;
c = ethertype;
hash_v3_mix32 (a, b, c);
hash_v3_finalize32 (a, b, c);
return c;
}
}
/** Default: 5-tuple + flowlabel without the "reverse" bit
* 以Sip+Dip+Sport + Dport + proctcol + flowlabel (ipv6报文 flowlable)*/
#define IP_FLOW_HASH_DEFAULT (0x9F)
另外在ip转发模块中,存在ECMP(Equal-Cost Multi-Path Routing等价多路径路由)中,也存在软件hash 选择的问题。
/** Default: 5-tuple + flowlabel without the "reverse" bit */
#define IP_FLOW_HASH_DEFAULT (0x9F)
#define foreach_flow_hash_bit_v1 \
_ (src, IP_FLOW_HASH_SRC_ADDR) \
_ (dst, IP_FLOW_HASH_DST_ADDR) \
_ (sport, IP_FLOW_HASH_SRC_PORT) \
_ (dport, IP_FLOW_HASH_DST_PORT) \
_ (proto, IP_FLOW_HASH_PROTO) \
_ (reverse, IP_FLOW_HASH_REVERSE_SRC_DST) \
_ (symmetric, IP_FLOW_HASH_SYMMETRIC)
#define foreach_flow_hash_bit \
_ (src, 0, IP_FLOW_HASH_SRC_ADDR) \
_ (dst, 1, IP_FLOW_HASH_DST_ADDR) \
_ (sport, 2, IP_FLOW_HASH_SRC_PORT) \
_ (dport, 3, IP_FLOW_HASH_DST_PORT) \
_ (proto, 4, IP_FLOW_HASH_PROTO) \
_ (reverse, 5, IP_FLOW_HASH_REVERSE_SRC_DST) \
_ (symmetric, 6, IP_FLOW_HASH_SYMMETRIC) \
_ (flowlabel, 7, IP_FLOW_HASH_FL) \
_ (gtpv1teid, 8, IP_FLOW_HASH_GTPV1_TEID)
可以通过set ip flow-hash 来设置hash key选择因子,此设置是基于vrf的。
set ip flow-hash table <table-id> [src] [dst] [sport] [dport] [proto] [reverse] [gtpv1teid]
至此我们了解了vpp中软件Packet Flow Hash的使用,以及遇到问题的一些解决思路。如果遇到类似的问题,欢迎留言讨论学习。
本文分享自 DPDK VPP源码分析 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!