这里我们把遇到的坑都列一下,主要针对OVS-DPDK和OVS的区别处遇到的坑,OVS版本2.7.0,DPDK16.11.1版本。
hugepage配置问题
首先需要说明的是因为OVS-DPDK使用了hugepage,所以如果VM要和OVS-DPDK进行通信的话,也就必须使用hugepage。hugepage配置基本上有两种方案,2M和1G的,下面针对两个分别说一下:
- 1G的需要在grub中指定默认为1G的hugepage,并且不支持创建小于1G内存的VM
- 2M的hugepage,如果需要较大的内存,需要注意内存块映射的限制值,这块接下来会着重说一下,因为正好遇到了坑
我的环境内存为16Gx16=256G,此时我使用的是2M的hugepage,但是我发现只要nr_hugepages配置大了,OVS-DPDK启动就会失败,报错如下:
EAL: Failed to remap 2 MB pages
PANIC in rte_eal_init():
Cannot init memory
一下以nr_hugepages=40960为例进行分析为什么会失败。经过上面的报错我们直奔主题,去看代码
ovs-vswitchd.c的main-->bridge_run-->dpdk_init-->dpdk_init__-->rte_eal_init
会调用DPDK的eal.c的rte_eal_memory_init-->rte_eal_hugepage_init
int rte_eal_hugepage_init(void)
{
//此处循环是针对不同的page size的,即2M和1G,我们没有配置1G,只有2M的
for (i = 0; i < (int)internal_config.num_hugepage_sizes; i ++){
//此处是将所有的hugepage的内存文件进行映射
//因为我们配置了40960个,所以需要全部映射
//下面会对该函数进行分析
pages_new = map_all_hugepages(&tmp_hp[hp_offset], hpi, 1);
//重新映射,这里主要是为了尽量将内存物理地址连续的
//也都映射为连续的虚拟地址
if (map_all_hugepages(&tmp_hp[hp_offset], hpi, 0) !=
hpi->num_pages[0]) {
RTE_LOG(ERR, EAL, "Failed to remap %u MB pages\n",
(unsigned)(hpi->hugepage_sz / 0x100000));
goto fail;
}
//解除第一次的映射
if (unmap_all_hugepages_orig(&tmp_hp[hp_offset], hpi) < 0)
goto fail;
}
}
static unsigned map_all_hugepages(struct hugepage_file *hugepg_tbl,
struct hugepage_info *hpi, int orig)
{
size_t vma_len = 0;
for (i = 0; i < hpi->num_pages[0]; i++) {
uint64_t hugepage_sz = hpi->hugepage_sz;
//第一次映射都需要创建该结构
//第二次映射此标识为0,忽略
if (orig) {
hugepg_tbl[i].file_id = i;
hugepg_tbl[i].size = hugepage_sz;
eal_get_hugefile_path(hugepg_tbl[i].filepath,
sizeof(hugepg_tbl[i].filepath), hpi->hugedir,
hugepg_tbl[i].file_id);
hugepg_tbl[i].filepath[sizeof(hugepg_tbl[i].filepath) - 1] = '\0';
}
//第一次或者是当vma_len映射完毕后重新查看需要映射的物理地址的连续性
else if (vma_len == 0) {
unsigned j, num_pages;
//检测物理地址是否连续
for (j = i+1; j < hpi->num_pages[0] ; j++) {
if (hugepg_tbl[j].physaddr !=
hugepg_tbl[j-1].physaddr + hugepage_sz)
break;
}
num_pages = j - i;
vma_len = num_pages * hugepage_sz;
//根据物理地址的连续性找到匹配的虚拟地址的连续长度
vma_addr = get_virtual_area(&vma_len, hpi->hugepage_sz);
if (vma_addr == NULL)
vma_len = hugepage_sz;
}
//直接open hugepage
fd = open(hugepg_tbl[i].filepath, O_CREAT | O_RDWR, 0600);
//进行内存地址的映射
//经过debug发现是第二次调用函数映射的时候出错
//并且出错位置就在此处,errno为22
virtaddr = mmap(vma_addr, hugepage_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, fd, 0);
if (virtaddr == MAP_FAILED) {
RTE_LOG(DEBUG, EAL, "%s(): mmap failed: %s\n", __func__,
strerror(errno));
close(fd);
return i;
}
}
}
经过以上代码能够确认,虚拟地址够用,物理地址也存在,但是映射失败,并且每次运行崩溃的时机基本一致,都和循环次数有关,第二次的23000+的映射时报错,所以怀疑和系统的限制有关,经过群基友的提示,和进程的虚拟内存map的配置相关,即查看每个进程的map限制
$ cat /proc/sys/vm/max_map_count
65530
经过查看此值为65530,而我们第一次映射为40960,第二次映射时累加就会超过65530,所以会报错。这也就能解释为什么第二次映射时都是到固定的位置报错。而查看内核我们看到该全局变量sysctl_max_map_count是32位的,所以可以支持很大,我们配置大小为2048000之后,再运行OVS-DPDK就没有问题了。
sysctl -w vm.max_map_count=2048000
OVS-DPDK的conntrack不支持分片报文
问题描述
在VM中发送大于MTU的UDP报文,会发现网络不通。经过查看流表发现报文过conntrack的时候被标记为+inv标志,并且丢弃了。
问题代码分析
通过查看代码(路径为pmd_thread_main-->dp_netdev_process_rxq_port-->dp_netdev_input-->dp_netdev_input__-->dp_execute_cb-->conntrack_execute
)
int
conntrack_execute(struct conntrack *ct, struct dp_packet_batch *pkt_batch,
ovs_be16 dl_type, bool commit, uint16_t zone,
const uint32_t *setmark,
const struct ovs_key_ct_labels *setlabel,
const char *helper)
{
struct dp_packet **pkts = pkt_batch->packets;
size_t cnt = pkt_batch->count;
struct conn_lookup_ctx ctxs[KEY_ARRAY_SIZE];
for (i = 0; i < cnt; i++) {
//此处会解析报文头,然后计算hash值
//解析过程中,如果是分片报文,则返回false
//出场关于可以看到false时,报文设置为CS_INVALID
//所以问题出现在这里,分片的报文期望也能找到conntrack
if (!conn_key_extract(ct, pkts[i], dl_type, &ctxs[i], zone)) {
write_ct_md(pkts[i], CS_INVALID, zone, 0, OVS_U128_ZERO);
continue;
}
...
}
...
return 0;
}
conntrack_execute-->conn_key_extract-->extract_l3_ipv4
static inline bool
extract_l3_ipv4(struct conn_key *key, const void *data, size_t size,
const char **new_data, bool validate_checksum)
{
//分片报文,返回值即为false,不管是首个还是之后的分片报文
if (IP_IS_FRAGMENT(ip->ip_frag_off)) {
return false;
}
}
解决方案
方案1
学习kernel的方案,在conntrack匹配之前首先对报文进行重组,如果我们现在做了重组之后的好处是,报文只查一次,然后发报文之前可能还需要再分片,比较麻烦。
方案2
分片的报文不进行重组,只是分片的后续报文也需要匹配conntrack,我们采取的方案是分片首包除了需要创建之前的连接维护,还需要根据源、目的IP和ID进行hash值计算,根据将该hash和首包的conntrack信息进行映射,目前经过思考觉得该方案其实也挺消耗资源,如果每个报文都有分片,每个报文的分片都需要和conntrack建立映射,这样映射的建立和销毁也很频繁。
OVS-DPDK的NAT patch不支持冲突解决
问题描述
配置完NAT之后,两个vm同时ping一个IP,然后流表做SNAT,IP替换为宿主机的IP,发现偶尔会出现vm1的ping没有任何回复报文,而vm2 ping一次收到两个回复报文,怀疑是因为NAT没有做冲突查看和解决,当两个ping的ICMP层的ID值一致的时候,NAT的完出去的报文,收到回复报文的时候,没有办法区分两个UNNAT,所以都会给后面的那个vm做UNNAT。
OVS的NAT代码分析
__netif_receive_skb-->__netif_receive_skb_core-->netdev_frame_hook-->netdev_port_receive-->ovs_vport_receive-->ovs_dp_process_packet-->ovs_execute_actions-->do_execute_actions-->ovs_ct_execute
首先调用handle_fragments
进行报文重组,根据commit操作调用ovs_ct_commit
进行conntrack创建,不是commnit则调用ovs_ct_lookup
查找conntrack,这里我们先不管,ovs_ct_commit
和ovs_ct_lookup
都会调用__ovs_ct_lookup
,前者是查找有没有冲突,后者是查找conntrack,我们主要关注这个函数。当查找到conntrack之后会判定conntrack是否有NAT信息,如果有的话调用ovs_ct_nat
进行NAT操作。
ovs_ct_nat
- untracked状态时以及没有执行commit的不进行NAT
- 查看NAT类型,比如SNAT、DNAT、或者是reply报文的NAT,对源、目的需要做一个转换
- 调用函数
ovs_ct_nat_execute
进行具体的NAT操作 - 最后NAT完成之后需要调用
ovs_nat_update_key
更新flow key
ovs_ct_nat_execute
- 根据不同的NAT类型,选择不同的hook点,SNAT选择LOCAL_IN,DNAT选择LOCAL_OUT,这里是NAT时需要的一个参数
- 如果是reply报文,则需要调用ICMP的函数
nf_nat_icmp_reply_translation
进行翻译,使他看起来和之前的报文一样匹配conntrack,ipv6的是也调用类似的函数 - 如果是新建的conntrack信息,则调用
nf_nat_initialized
初始化,调用nf_nat_setup_info
进行安装NAT信息 - 调用
nf_nat_packet
进行报文的NAT操作
nf_nat_setup_info
- 调用
nf_ct_nat_ext_add
给conntrack创建NAT信息 - 调用
get_unique_tuple
从NAT规则的范围中获取一个可用元组
get_unique_tuple
- 如果源IP或者L4信息已经映射了,同样的映射符合范围内的一个唯一的元组,就使用他,主要是L3的
in_range
和L4的in_range
函数处理,如果没有接着往下看 - 在给定的范围内选择最少使用的IP或者L4信息
- 根据L4的
in_range
进行验证并确定是唯一的元组nf_nat_used_tuple
,然后就可以了,如果没有接着往下看 - 调用L4的
unique_tuple
进行唯一性的处理
以上提到了好多次的L3和L4的一些函数指针,表示不同的协议有不同的操作,L3主要是包括ipv4和ipv6,L4支持的协议包括dccp、gre、icmp、icmpv6、sctp、tcp、udp、udplite、unkown。
icmp_unique_tuple
- 如果指定了ICMP的ID或者范围,则进行循环选择并确定唯一性
- 没有指定的话则随意选择
dccp_unique_tuple
,sctp_unique_tuple
,tcp_unique_tuple
,udp_unique_tuple
,udplite_unique_tuple
都是调用的如下函数nf_nat_l4proto_unique_tuple
nf_nat_l4proto_unique_tuple
- 如果没有指定范围,DNAT时目的端口不能改变,SNAT时源端口可以改变
- 端口的变化范围有几个限制,端口是512以内的映射范围是1-512,端口是512-1024的映射范围是600-1024,1024以上的映射范围就是1024以上
- 如果指定了端口的变化范围,那就按照指定的来
- 如果是
NF_NAT_RANGE_PROTO_RANDOM
模式的话,调用L3的secure_port
,根据源目的IP和需要修改的端口计算一个hash值。 - 如果是
NF_NAT_RANGE_PROTO_RANDOM_FULLY
模式的话,直接计算随机数 - 根据得到的值根据范围取余,再加上最小值就得到的端口,然后判定是否已用,用了的话加1再判定。
nf_nat_packet
- 首先查找3层协议和4层协议
- 调用3层协议的
manip_pkt
函数指针进行报文的操作,参数包括4层协议
nf_nat_ipv4_manip_pkt
- 报文偏移到L3
- 调用L4的
manip_pkt
函数指针进行报文操作 - 重新计算csum
- 修改源或者目的IP
icmp_manip_pkt
- 报文偏移到L4
- 重新计算csum
- 修改ICMP的ID
udp_manip_pkt
- 报文偏移到L4
- 重新计算csum
- 修改UDP的源或者目的端口
tcp_manip_pkt
- 报文偏移到L4
- 重新计算csum
- 修改TCP的源或者目的端口
分片报文不能通过带五元组的精细流表
问题描述
问题同时存在与OVS和OVS-DPDK中,当vm中发出的报文是带分片的,但是我们的转发流表比较精细的话(比如UDP,流表匹配五元组),那么报文不能正常转发,当流表只是3元组,不包括源、目的端口的时候可以正常转发。
vhotuser重连的问题
问题描述见链接
新建虚拟机首包容易丢失的问题
问题描述
这个问题不只是DPDK版本的有,OVS如果是物理网口加到桥上,然后IP配置到桥上的场景中也可能会出现。必现场景是需要tunnel封装后需要发送的HOST的arp信息不存在时,报文就会丢失。这里说的arp信息是指ovs存储的arp信息,查看方式为ovs-appctl tnl/arp/show
,我们只需要执行ovs-appctl tnl/arp/flush
后,即可复现。
问题代码分析
handle_packet_upcall-->dp_netdev_upcall-->upcall_cb-->upcall_xlate-->xlate_actions-->do_xlate_actions-->xlate_output_action-->compose_output_action-->compose_output_action__-->build_tunnel_send
是创建封装流表的时候,会调用tnl_neigh_lookup
查询arp信息,如果查询不到则调用tnl_send_arp_request
发送arp请求,然后返回。如果查找到了则会创建datapath流表。所以最开始的一个报文,没有创建datapath流表,这个问题不是很紧急,所以暂时还没有解决,基本思路就是先创建一个flood的datapath流表,他的hard_timeout很小,转发完报文就销毁,后续有了arp之后,报文就不会出现问题。
ovs-dpdk不能解封装geneve报文的问题
当有多个ovn-controller的时候,其中一个ovn-controller服务关掉之后,任何节点的ovs-dpdk都不能正确解封装报文,经过查看发现关闭ovn-controller服务的时候,ovs-vsctl show
命令能看到和该宿主机的tunnel口删掉了,而删掉这个tunnel口的时候,发现监听端口也被删除了,这个通过命令ovs-appctl tnl/ports/show -v
可以查看,现在需要查看代码,为什么会删除一个tunnel口就会删除监听,理论上应该是没有任何tunnel口之后才会删除监听。
port_construct-->tnl_port_add-->tnl_port_add__-->tnl_port_map_insert
只有第一次才会添加,后续的就不再操作了。而通过map_insert_ipdev__-->map_insert
看到引用计数只有一次,无论添加了多少个tunnel口。
port_destruct-->tnl_port_del-->tnl_port_del__-->tnl_port_map_delete
直接减引用计数,然后删除tunnel口。
由以上我们可以看出,只要删除一个tunnel口,那么监听端口就删掉了,会影响解封装,导致网络异常。我们需要做的就是引用计数的处理,这个问题我暂时提交了一个patch。
To Be Continued ...
7 条评论
你的文章让我感受到了无尽的欢乐,谢谢分享。 https://www.4006400989.com/qyvideo/54236.html
你的文章让我感受到了无尽的欢乐,谢谢分享。 https://www.4006400989.com/qyvideo/17191.html
畅享无限,长期稳定176传奇私服,火爆招募热血玩家!:https://501h.com/lianji/2024-08-09/26757.html
不错不错,我喜欢看 www.jiwenlaw.com
不错不错,我喜欢看 https://www.237fa.com/
看的我热血沸腾啊
博主的每一篇文章都透漏着专业,能否加个好友跟博主学习学习?