最简单的openvswitch转发过程内核代码级实现

笔者写这篇文章出于好奇,对namespace和openvswitch如何工作在内核协议栈中的好奇,也出于对openvswitch了解的渴望,因此这篇文章仅仅是开端,后续会有更多文章揭秘openvswitch具体工作过程,不啰嗦了,下面步入正题。

这篇文章是基于两个namespace经过openvswitch之间进行通信的场景,分析寻找该通信底层发生的事情;所以该篇文章会涉及namespace协议栈处理和openvswitch通信过程两大部分;不过后续文章书写部分并没有按照这个书写逻辑;后文是根据笔者具体实践步骤书写的,这两部分内容会蕴含其中,具体请看后文。

最简单的环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
           br0
+--------------------------------------+
+--+ +--+
+---+ | tap1 tap2| +---+
| +--+ +--+ |
| | | |
| +--------------------------------------+ |
| |
| |
| |
| |
+------------------+ +-------------------+
| tap1 | | tap2 |
|192.168.1.101/24 | | 192.168.1.102/24 |
| | | |
| | | |
| | | |
| namespace ns1 | | namespace ns1 |
| | | |
+------------------+ +-------------------+

组网实现脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ip netns add ns1
ip netns add ns2

ovs-vsctl add-br br0
ovs-vsctl add-port br0 tap1 -- set Interface tap1 type=internal

ip link set tap1 netns ns1
ip netns exec ns1 ip link set dev tap1 up

ovs-vsctl add-port br0 tap2 -- set Interface tap2 type=internal
ip link set tap2 netns ns2
ip netns exec ns2 ip link set dev tap2 up

ip netns exec ns1 ip addr add 192.168.1.101/24 dev tap1
ip netns exec ns2 ip addr add 192.168.1.102/24 dev tap2

ip netns exec ns1 ip link set lo up
ip netns exec ns2 ip link set lo up

连通性测试

1
2
3
4
[root@localhost ~]# ip netns exec ns1  ping 192.168.1.102 -c 100
PING 192.168.1.102 (192.168.1.102) 56(84) bytes of data.
64 bytes from 192.168.1.102: icmp_seq=1 ttl=64 time=0.172 ms
64 bytes from 192.168.1.102: icmp_seq=2 ttl=64 time=0.051 ms

内核处理过程

在执行该过程中,在我的Centos7.2环境装了systemtap,同时安装了适用systemtap所需要的对应内核的调试和开发数据包,如下

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# uname -a
Linux localhost.localdomain 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 13:29:22 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
[root@localhost ~]#
[root@localhost ~]# rpm -qa|grep kern
kernel-debuginfo-common-x86_64-3.10.0-514.26.2.el7.x86_64
kernel-3.10.0-514.26.2.el7.x86_64
kernel-debuginfo-3.10.0-514.26.2.el7.x86_64
kernel-tools-libs-3.10.0-514.el7.x86_64
kernel-3.10.0-514.el7.x86_64
kernel-devel-3.10.0-514.26.2.el7.x86_64
kernel-tools-3.10.0-514.el7.x86_64
kernel-headers-3.10.0-514.26.2.el7.x86_64

本节使用systemtap探究第一节中描述场景下通信的内核数据包转发流程;当然在使用此方法时,笔者已经知道数据包大概流程,所以才知道追踪哪个函数,因此后文抓取协议栈的调用栈时候,请不要疑惑为什么抓取此函数

跟踪内核的stp脚本

追踪调用栈的脚本如下

1
2
3
4
5
6
7
8
[root@localhost ~]# cat bt.stp
probe kernel.function(@1){
print("----------------START-------------------------\n")
printf("In process [%s]\n", execname())
print_regs()
print_backtrace()
print("----------------END-------------------------\n")
}

调用方法如下

1
2
stap --all-modules  bt.stp enqueue_to_backlog
stap --all-modules bt.stp icmp_rcv |grep -30 "ping"

根据调用栈结果得出如下的流量转发路径

cc.png

数据包在ns1 中处理过程

  • ns1 ping ns2地址首先运行用户态程序ping,ping会通过socket最终调用kernel ip_push_pending_frames将该数据包引入到ns1命名空间的协议栈
  • 在ns1中因为该数据包是属于本机向外发出的流量因此最终会经过ip_output流程,最终根据虚拟口tap1 调用dev_hard_start_xmit将该数据包发出
  • 被发出的数据包因为该tap1口连接到openvswitch br0上,最终会经过dev_hard_start_xmit 调用internal_dev_xmit将该数据包注入openvswitch收数据包流程中
  • 该数据包注入到openvswitch流程后会调用ovs_vport_receive来处理

数据包在openvswitch中处理过程

  • ovs_vport_receive收到该数据包后,就相当于进入了openvswitch的datapath流程
  • 在ovs_vport_receive会根据当前数据包的流量特征查找对应的flow,如果无法查找到会upcall到用户态进行流表查询流程
  • 因为当前测试的流表是normal转发,所以查询结束后,会调用ovs_execute_actions执行具体动作,当前的测试场景下会调用do_output.isra.31处理数据包,因为该动作是要发往tap2,所以调用ovs_vport_send最终发给tap2口
  • 因为tap2属于openvswitch的内部口,所以会调用internal_dev_recv将该数据包发给tap2处理
  • 目前发给tap2处理后,该过程不是直接将数据包送给ns2的协议栈收报入口进行处理,而是将数据包经过netif_rx和netif_rx_internal将数据包enqueue_to_backlog挂入到CPU的队列中
  • 后续ns2软中断时候会从该队列中读取消耗该数据包

数据包在ns2中处理过程

  • ns2协议栈会经过软中断处理do_softirq后,最终调用process_backlog处理cpu队列上的数据包
  • process_backlog会调用协议栈的__netif_receive_skb来消耗数据包
  • 该数据包会经过ip层处理ip_rcv,因为是发给本机的数据包所以调用ip_local_deliver最终交给icmp_rcv进行处理回复

总结

经过以上流程的详细描述,可以知道openvswitch是负责连接两个ns转发的路径,而ns1和ns2协议栈是独立的,如果想要通信需要借助openvswith这样的虚拟交换机;

且经过虚拟交换机将数据包注入ns2过程并不是直接调用ns2的收包处理函数,而是将数据包挂到CPU队列中,待ns2协议栈中断触发时候自己去处理消耗,其实这也间接的说明了其数据包在大模块间通信时候采用的生产者和消费者模型

而不是直接调用ns2的收包处理函数去处理,其实这样是有好处的,它的好处是将数据包提供模块和数据包消耗模块分开,留给数据包消耗模块更大自主空间去处理消耗该数据包。

当然笔者也想到一个问题,当ns2软中断非常忙时候,无法及时消耗数据包,会出现tap口tx drop计数;当出现该问题时当然你可以修改cpu放置的数据包队列大小来稍微更改效果;但是笔者认为更多的经历应该放在为什么ns2处理数据慢?笔者相信通过找到该问题原因,drop丢包问题也会得到解决。