openvswitch vxlan 源码分析
ovs对于vxlan的支持依赖datapath的类型,对于kernel space datapath来说,创建vxlan端口后,在将此端口添加到datapath时,会调用kernel自身提供的vxlan.ko模块创建出vxlan_sys_port来,ovs只需要将流表action指向vxlan_sys_port即可,vxlan报文的封装,封装后路由查找和邻居查找都由vxlan模块/kernel来实现;而对于userspace datapath来说,vxlan报文的封装,封装后路由查找和邻居查找都由ovs本身实现。
实验
下面分别实验两种datapath下的vxlan。
kernel space vxlan
拓扑图如下所示
对应的命令如下
//在vm1上操作,创建一个bridge,添加一个vxlan端口
ovs-vsctl add-br br0
ovs-vsctl add-port br0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.10.10.2 options:key=flow options:dst_port=8472
ifconfig br0 1.1.1.1/24
ifconfig ens8 10.10.10.1/24
//在vm2上操作
ovs-vsctl add-br br0
ovs-vsctl add-port br0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.10.10.1 options:key=flow options:dst_port=8472
ifconfig br0 1.1.1.2/24
ifconfig ens8 10.10.10.2/24
命令执行成功后,查看基本情况
//创建vlxna端口后会监听端口8472,用来接收vxlan报文
root@master:~# netstat -nap | grep 8472
udp 0 0 0.0.0.0:8472 0.0.0.0:* -
udp6 0 0 :::8472 :::* -
//查看vxlan端口驱动类型为vxlan
root@master:~# ethtool -i vxlan_sys_8472
driver: vxlan
version: 0.1
firmware-version:
expansion-rom-version:
bus-info:
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no
//查看ovs当前配置
root@master:~# ovs-vsctl show
1e633d2b-7a9e-44ba-9c16-89e12912c2d6
Bridge "br0"
Port "br0"
Interface "br0"
type: internal
Port "vxlan1"
Interface "vxlan1"
type: vxlan
options: {dst_port="8472", key=flow, remote_ip="10.10.10.2"}
//查看datapath端口
root@master:~# ovs-appctl dpctl/show
system@ovs-system:
lookups: hit:147 missed:23 lost:0
flows: 4
masks: hit:457 total:5 hit/pkt:2.69
port 0: ovs-system (internal)
port 1: br0 (internal)
port 2: vxlan_sys_8472 (vxlan: packet_type=ptap)
在vm1上ping 1.1.1.2,可以ping通,查看流表及抓包情况
root@master:~# ping 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=4.58 ms
64 bytes from 1.1.1.2: icmp_seq=2 ttl=64 time=0.643 ms
^C
--- 1.1.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.643/2.610/4.578/1.967 ms
//datapath流表信息,有4条流表,两条arp相关的,两条icmp相关的,从1口收到的报文转发给2口,从2口收到的报文转发给1口
root@master:~# ovs-appctl dpctl/dump-flows
recirc_id(0),tunnel(tun_id=0x0,src=10.10.10.2,dst=10.10.10.1,flags(-df-csum+key)),in_port(2),eth(src=d6:5a:9e:97:39:4f,dst=b6:7b:1a:1e:79:44),eth_type(0x0806), packets:1, bytes:42, used:8.528s, actions:1
recirc_id(0),in_port(1),eth(src=b6:7b:1a:1e:79:44,dst=d6:5a:9e:97:39:4f),eth_type(0x0800),ipv4(tos=0/0x3,frag=no), packets:13, bytes:1274, used:0.384s, actions:set(tunnel(tun_id=0x0,dst=10.10.10.2,ttl=64,tp_dst=8472,flags(df|key))),2
recirc_id(0),in_port(1),eth(src=b6:7b:1a:1e:79:44,dst=d6:5a:9e:97:39:4f),eth_type(0x0806), packets:0, bytes:0, used:never, actions:set(tunnel(tun_id=0x0,dst=10.10.10.2,ttl=64,tp_dst=8472,flags(df|key))),2
recirc_id(0),tunnel(tun_id=0x0,src=10.10.10.2,dst=10.10.10.1,flags(-df-csum+key)),in_port(2),eth(src=d6:5a:9e:97:39:4f,dst=b6:7b:1a:1e:79:44),eth_type(0x0800),ipv4(frag=no), packets:13, bytes:1274, used:0.384s, actions:1
//在vxlan端口上抓包,此时只能抓到封装前的报文
root@master:~# tcpdump -vne -i vxlan_sys_8472
tcpdump: listening on vxlan_sys_8472, link-type EN10MB (Ethernet), capture size 262144 bytes
23:02:27.662008 0e:ed:bb:33:06:40 > 96:e5:e8:08:63:44, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 23800, offset 0, flags [DF], proto ICMP (1), length 84)
1.1.1.2 > 1.1.1.1: ICMP echo request, id 9663, seq 89, length 64
23:02:27.662095 96:e5:e8:08:63:44 > 0e:ed:bb:33:06:40, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 64219, offset 0, flags [none], proto ICMP (1), length 84)
1.1.1.1 > 1.1.1.2: ICMP echo reply, id 9663, seq 89, length 64
//在最终出端口上抓包,可以抓到封装vxlan后的报文
root@master:~# tcpdump -vne -i ens8
tcpdump: listening on ens8, link-type EN10MB (Ethernet), capture size 262144 bytes
23:36:05.294029 52:54:00:9f:e8:0e > 52:54:00:9e:98:20, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 36259, offset 0, flags [DF], proto UDP (17), length 134)
10.10.10.2.49240 > 10.10.10.1.8472: OTV, flags [I] (0x08), overlay 0, instance 0
0e:ed:bb:33:06:40 > 96:e5:e8:08:63:44, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 11222, offset 0, flags [DF], proto ICMP (1), length 84)
1.1.1.2 > 1.1.1.1: ICMP echo request, id 9663, seq 2061, length 64
23:36:05.294094 52:54:00:9e:98:20 > 52:54:00:9f:e8:0e, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 55108, offset 0, flags [DF], proto UDP (17), length 134)
10.10.10.1.51916 > 10.10.10.2.8472: OTV, flags [I] (0x08), overlay 0, instance 0
96:e5:e8:08:63:44 > 0e:ed:bb:33:06:40, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 57149, offset 0, flags [none], proto ICMP (1), length 84)
1.1.1.1 > 1.1.1.2: ICMP echo reply, id 9663, seq 2061, length 64
ping流程如下
a. vm1上ping 1.1.1.2时,首先查找arp表,找不到后,会发送arp请求报文。arp请求报文从br0发出后,网桥br0收到,查找datapath流表,因为是首包肯定查不到,然后将收包上送用户态的slow-path处理。
b. 在slow-path查找openflow流表,action为 normal,需要查找fdb表。
root@master:~# ovs-ofctl dump-flows br0
cookie=0x0, duration=62611.119s, table=0, n_packets=21004, n_bytes=1141452, priority=0 actions=NORMAL
因为目的mac为广播,所以需要广播到网桥br0上其他所有端口,目前只有端口vxlan_sys_8472。
c. 上一步查找流表结果是将报文从vxlan_sys_8472发出,因为vxlan_sys_8472为vxlan端口,所以会将vxlan相关配置一同下发到datapath流表
root@master:~# ovs-appctl dpctl/dump-flows
recirc_id(0),in_port(1),eth(src=b6:7b:1a:1e:79:44,dst=d6:5a:9e:97:39:4f),eth_type(0x0806), packets:0, bytes:0, used:never, actions:set(tunnel(tun_id=0x0,dst=10.10.10.2,ttl=64,tp_dst=8472,flags(df|key))),2
d. 除了下发流表到datapath外,还会将上送的首包下发到datapath,根据最新的流表执行。即将首包发给vxlan_sys_8472处理,同时携带vxlan相关配置(比如remote_ip,dst_port, tunnle id等信息)
e. 报文到达vxlan_sys_8472后根据vxlan配置,封装成vxlan报文,根据外层目的ip查找路由表可知,需要从ens8网卡发出
root@master:~# ip r
default via 192.168.122.1 dev ens3 proto static
1.1.1.0/24 dev br0 proto kernel scope link src 1.1.1.1
10.10.10.0/24 dev ens8 proto kernel scope link src 10.10.10.1
f. 封装后的vxlan报文经过底层网络转发到vm2,根据目的ip发现需要本机处理,经过协议栈处理后,相当于解封装vxlan报文,再根据目的端口号8472查找对应的socket可知,需要将udp的payload,即内层arp请求报文发给vm2上vxlan端口处理。
g. 因为vxlan端口在网桥上,也需要查找vm2上datapath流表,当然也会失败,将arp请求报文上送vm2上slow-path处理,广播报文发给网桥上其他端口,目前只有br0,最后将流表信息下发到datapath,并且将arp请求报文下发到datapath,最终发给端口br0。
h. 上一步在slow-path收到arp请求报文时,也会学到对端的mac,如下
root@node1:~# ovs-appctl fdb/show br0
port VLAN MAC Age
1 0 b6:7b:1a:1e:79:44 1
LOCAL 0 d6:5a:9e:97:39:4f 1
vm2回复arp响应报文时,目的mac为单播地址。查找datapath流表失败,上送slow-path,查找openflow流表,查找fdb表成功找到出端口1。注意fdb表的出端口对应的是openflow端口,可通过ovs-ofctl show br0查看,不要和datapath端口号混淆了。
i. 将流表信息下发到datapath,并将报文发给vxlan端口,封装vxlan报文,查找路由表,发送出去。
j. vm1收到后,解封装vxlan报文,查找datapath流表失败,上送slow-path,查找fdb表成功找到出端口,同时学习到对端的mac。将流表下发到datapath,报文发给vm1上的br0。至此vm1学到了1.1.1.2的mac地址。
k. vm1上学到mac地址后发出ping报文,网桥br1收到ping包,查找datapath流表,因为是首包肯定查不到,上送用户态的slow-path处理,查找fdb表成功找到出端口,将流表下发到datapath,将报文发给vxlan端口,封装vxlan后发出。
l. 后续的流程都是类似的,不再赘述。后续的报文可以直接查找datapath流表进行转发即可。
userspace vxlan
拓扑图如下所示
注意:br1上通往外部的端口即可以是绑定到dpdk driver的(由pmd线程处理收发包),也可以是绑定到kernel driver的(由ovs的non-pmd线程(vswitchd主线程)处理它的收发包)。上图中的ens8端口是绑定在kernel driver的。
对应的命令如下
//以下命令在vm1上执行
ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev
ovs-vsctl add-port br0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.10.10.2 options:key=flow options:dst_port=8472
ifconfig br0 1.1.1.1/24
ovs-vsctl add-br br1 -- set bridge br1 datapath_type=netdev
ovs-vsctl add-port br1 ens8
ip link set dev br1 up
ip addr add dev br1 10.10.10.1/24
ip link set dev ens8 up
//以下命令在vm2上执行
ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev
ovs-vsctl add-port br0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.10.10.1 options:key=flow options:dst_port=8472
ifconfig br0 1.1.1.2/24
ovs-vsctl add-br br1 -- set bridge br1 datapath_type=netdev
ovs-vsctl add-port br1 ens8
ip link set dev br1 up
ip addr add dev br1 10.10.10.2/24
ip link set dev ens8 up
查看下ovs配置,因为userspace vxlan,所以此时在vm上通过ifconfig是看不到类似vxlan_sys_8472的vxlan端口的。
root@master:~# ovs-vsctl show
163a03bf-8b1b-4043-8d37-8b2287bf94fe
Bridge "br1"
Port "br1"
Interface "br1"
type: internal
Port "ens8"
Interface "ens8"
Bridge "br0"
Port "vxlan1"
Interface "vxlan1"
type: vxlan
options: {dst_port="8472", key=flow, remote_ip="10.10.10.2"}
Port "br0"
Interface "br0"
type: internal
//查看userspace datapath端口信息
root@master:~# ovs-appctl dpctl/show
netdev@ovs-netdev:
lookups: hit:27 missed:26 lost:0
flows: 1
port 0: ovs-netdev (tap)
port 1: br0 (tap)
port 2: vxlan_sys_8472 (vxlan: packet_type=ptap)
port 3: br1 (tap)
port 4: ens8
//查看路由信息,封装vxlan后,根据外层ip查找路由表,寻找出接口
root@master:~# ovs-appctl ovs/route/show
Route Table:
Cached: 1.1.1.1/32 dev br0 SRC 1.1.1.1
Cached: 10.10.10.1/32 dev br1 SRC 10.10.10.1
Cached: 127.0.0.1/32 dev lo SRC 127.0.0.1
Cached: 172.17.0.1/32 dev docker0 SRC 172.17.0.1
Cached: 192.168.122.20/32 dev ens3 SRC 192.168.122.20
Cached: ::1/128 dev lo SRC ::1
...
Cached: 1.1.1.0/24 dev br0 SRC 1.1.1.1
Cached: 10.10.10.0/24 dev br1 SRC 10.10.10.1
Cached: 192.168.122.0/24 dev ens3 SRC 192.168.122.20
Cached: 172.17.0.0/16 dev docker0 SRC 172.17.0.1
Cached: 127.0.0.0/8 dev lo SRC 127.0.0.1
Cached: 0.0.0.0/0 dev ens3 GW 192.168.122.1 SRC 192.168.122.20
Cached: fe80::/64 dev br1 SRC fe80::1031:66ff:fe3c:9547
在vm1上ping 1.1.1.2,可以ping通,查看流表及抓包情况
root@master:~# ping 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=1.85 ms
^C
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.848/1.848/1.848/0.000 ms
//datapath流表信息(先忽略arp流表),出方向流表只有一条,封装vxlan后,发给出端口即可,但是入方向需要两条,一条用于查找tunnel信息,解封装后将报文送到vxlan端口,第二条根据内层报文信息转到正确的端口
root@master:~# ovs-appctl dpctl/dump-flows
flow-dump from non-dpdk interfaces:
recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth(src=2e:ab:5f:92:6c:47,dst=12:31:66:3c:95:47),eth_type(0x0800),ipv4(dst=10.10.10.1,proto=17,frag=no),udp(dst=8472), packets:919, bytes:90148, used:0.409s, actions:tnl_pop(2)
recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=6a:91:a4:4f:78:45,dst=b2:42:ef:84:79:4b),eth_type(0x0800),ipv4(tos=0/0x3,frag=no), packets:267, bytes:26166, used:0.410s, actions:clone(tnl_push(tnl_port(2),header(size=50,type=4,eth(dst=52:54:00:9f:e8:0e,src=12:31:66:3c:95:47,dl_type=0x0800),ipv4(src=10.10.10.1,dst=10.10.10.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=8472,csum=0x0),vxlan(flags=0x8000000,vni=0x0)),out_port(3)),4)
tunnel(tun_id=0x0,src=10.10.10.2,dst=10.10.10.1,flags(-df-csum+key)),recirc_id(0),in_port(2),packet_type(ns=0,id=0),eth(src=b2:42:ef:84:79:4b,dst=6a:91:a4:4f:78:45),eth_type(0x0800),ipv4(dst=1.1.1.1,proto=1,frag=no), packets:100, bytes:9800, used:0.409s, actions:1
//neigh信息
root@master:~# ovs-appctl tnl/neigh/show
IP MAC Bridge
==========================================================================
1.1.1.1 6a:91:a4:4f:78:45 br0
1.1.1.2 b2:42:ef:84:79:4b br0
10.10.10.1 12:31:66:3c:95:47 br1
10.10.10.2 52:54:00:9f:e8:0e br1
ping流程如下(暂时忽略arp学习过程)
a. vm1上ping 1.1.1.2后,br0端口收到icmp报文,查找fast path失败,上送slow-path处理,广播到vxlan1端口。
b. 在vxlan1端口根据配置的remote_ip 10.10.10.2查找路由表,找到出接口br1和源ip。
Cached: 10.10.10.0/24 dev br1 SRC 10.10.10.1
再根据neigh表找到目的mac
10.10.10.2 52:54:00:9f:e8:0e br1
c. 此时外层mac,ip和udp端口号都已知,根据这些信息构造外层报文信息。然后假装报文从br1收到,查找br1上的openflow流表,action为normal,需要查找fdb表,未知报文flood到br1上所有端口,当前只有ens8。所以最后的流表信息如下,会将此流表下发到datapath。
recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=6a:91:a4:4f:78:45,dst=b2:42:ef:84:79:4b),eth_type(0x0800),ipv4(tos=0/0x3,frag=no), packets:267, bytes:26166, used:0.410s, actions:clone(tnl_push(tnl_port(2),header(size=50,type=4,eth(dst=52:54:00:9f:e8:0e,src=12:31:66:3c:95:47,dl_type=0x0800),ipv4(src=10.10.10.1,dst=10.10.10.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=8472,csum=0x0),vxlan(flags=0x8000000,vni=0x0)),out_port(3)),4)
d. 流表下发后,会将报文按照流表action封装成vxlan报文,从ens8发送出去。
e. vm2上的ens8收到vxlan报文后,查找fast path失败,上送slow-path,在函数terminate_native_tunnel中根据外层报文信息查找是否有匹配的tunnel,如果有将设置action为OVS_ACTION_ATTR_TUNNEL_POP,出端口为tunnel口。
f. 下发上面的流表到datapath,同时执行action处理报文。将外层封装去除,以出端口vxlan1为入端口,重新走slow-path,找到br0,将这次流表也下发到datapath。所以vxlan报文接收方向有两条流表。
g. 后续流程类型,不再赘述。
源码分析
创建vxlan端口流程
main -> bridge_run -> bridge_add_ports -> iface_create -> iface_create -> iface_do_create
在 iface_do_create函数中
iface_set_netdev_config 将vxlan配置更新到 dev->tnl_cfg
ofproto_port_add
port_add将vxlan端口添加到datapath中
update_port ->ofport_install -> port_construct保存tunnel port到全局变量
在函数type_run中,将ofport->is_tunnel 转换到 xport->is_tunnel。后面流表转发只会用到xport相关。
发送vxlan报文流程
a. slow-path
slow-path处理流程,如果找到或者flood到vxlan端口后,调用compose_output_action__
upcall_cb -> process_upcall -> upcall_xlate -> xlate_actions -> do_xlate_actions -> xlate_output_action -> xlate_normal -> xlate_normal_flood -> output_normal -> compose_output_action -> compose_output_action__
static void
compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port, const struct xlate_bond_recirc *xr, bool check_stp)
if (xport->is_tunnel) {
//userspace vxlan 这里主要设置is_native_tunnel为true,后面才会真正设置action
if (ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
xlate_report(ctx, OFT_DETAIL, "output to native tunnel");
is_native_tunnel = true;
} else {
//kernelspace vxlan 这里主要添加两个action set和tunnel
xlate_report(ctx, OFT_DETAIL, "output to kernel tunnel");
commit_odp_tunnel_action(flow, &ctx->base_flow, ctx->odp_actions);
nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
nl_msg_start_nested(a, OVS_KEY_ATTR_TUNNEL);
}
//userspace vxlan发送流程,调用build_tunnel_send根据remote_ip查找路由表,查找mac表,并且添加三个action:
//OVS_ACTION_ATTR_CLONE,OVS_ACTION_ATTR_TUNNEL_PUSH,OVS_ACTION_ATTR_OUTPUT
if (is_native_tunnel) {
/* Output to native tunnel port. */
build_tunnel_send(ctx, xport, flow, odp_port);
} else if (terminate_native_tunnel(ctx, ofp_port, flow, wc, &odp_tnl_port)) {
//这个判断是接收到vxlan报文的处理
//terminate_native_tunnel 根据外层flow查找tunnel信息,找到 odp 端口,即在datapath的端口号
/* Intercept packet to be received on native tunnel port. */
nl_msg_put_odp_port(ctx->odp_actions, OVS_ACTION_ATTR_TUNNEL_POP, odp_tnl_port);
} else {
//对于在kernelspace转发的vxlan来说,只需要把action的出端口设置为vxlan即可,不用ovs来封装vxlan报文
nl_msg_put_odp_port(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, out_port);
}
}
对于userspace vxlan来说,主要下发三个action: OVS_ACTION_ATTR_CLONE,OVS_ACTION_ATTR_TUNNEL_PUSH,OVS_ACTION_ATTR_OUTPUT。其中OVS_ACTION_ATTR_TUNNEL_PUSH和OVS_ACTION_ATTR_OUTPUT嵌套在OVS_ACTION_ATTR_CLONE中。对应的流表如下
recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=6a:91:a4:4f:78:45,dst=b2:42:ef:84:79:4b),eth_type(0x0800),ipv4(tos=0/0x3,frag=no), packets:267, bytes:26166, used:0.410s, actions:clone(tnl_push(tnl_port(2),header(size=50,type=4,eth(dst=52:54:00:9f:e8:0e,src=12:31:66:3c:95:47,dl_type=0x0800),ipv4(src=10.10.10.1,dst=10.10.10.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=8472,csum=0x0),vxlan(flags=0x8000000,vni=0x0)),out_port(3)),4)
actions:clone() 对应OVS_ACTION_ATTR_CLONE。
tnl_push()对应OVS_ACTION_ATTR_TUNNEL_PUSH。括号中的tnl_port(2)表示端口2是vxlan口,header()中的内容包含外层源目的mac,源目的ip和源目的端口号和vxlan头部的flag和vni,out_port(3)表示vxlan报文的出端口,但实际上报文不会直接从此端口发出,而是以它为入端口,再次走slow-path查找实际的出端口。
最后的数字4对应出端口OVS_ACTION_ATTR_OUTPUT。
对于kernel space vxlan来说,也会下发三个action: OVS_ACTION_ATTR_SET,OVS_KEY_ATTR_TUNNEL,OVS_ACTION_ATTR_OUTPUT。其中OVS_KEY_ATTR_TUNNEL嵌套在OVS_ACTION_ATTR_SET中。对应的流表如下
recirc_id(0),in_port(1),eth(src=96:e5:e8:08:63:44,dst=0e:ed:bb:33:06:40),eth_type(0x0800),ipv4(tos=0/0x3,frag=no), packets:400, bytes:39200, used:0.476s, actions:set(tunnel(tun_id=0x0,dst=10.10.10.2,ttl=64,tp_dst=8472,flags(df|key))),2
actions:set() 对应OVS_ACTION_ATTR_SET。
tunnel()对应OVS_KEY_ATTR_TUNNEL。这个action主要保存封装vxlan需要的信息。
最后的数字2对应出端口 OVS_ACTION_ATTR_OUTPUT。这个action主要是将未封装的报文发送给vxlan口,由vxlan kernel模块来封装。
需要注意的是必须安装actions的顺序执行,否则vxlan封装失败。因为首先通过OVS_KEY_ATTR_TUNNEL设置remote_ip等信息到skb的私有数据中,再执行OVS_ACTION_ATTR_OUTPUT时,从skb的私有数据中取出remote_ip等信息才能完成封装。
b. fast path
slow-path下发流表后,后续报文都可以在datapath找到流表直接转发。
对于userspace vxlan流程如下
dp_netdev_execute_actions -> odp_execute_actions -> odp_execute_clone执行clone action,这个函数里又依次执行tunnel push和output action
OVS_ACTION_ATTR_TUNNEL_PUSH -> push_tnl_action
OVS_ACTION_ATTR_OUTPUT -> netdev_send
对于kernel space vxlan流程如下
ovs_vport_receive -> ovs_dp_process_packet -> ovs_execute_actions
OVS_ACTION_ATTR_SET -> ovs_skb_dst_set(skb, (struct dst_entry *)tun->tun_dst); 保存tunnel信息到skb
OVS_ACTION_ATTR_OUTPUT -> vxlan_xmit 转发给vxlan模块处理
接收vxlan报文流程
slow-path接收vxlan报文流程和发送报文流程前面函数调用都相同,只不过在函数compose_output_action__中区分开来。
compose_output_action__
//xport为出端口(flood到所有端口),一般不会是tunnel端口
if (xport->is_tunnel) {
}
if (is_native_tunnel) {
} else if (terminate_native_tunnel(ctx, ofp_port, flow, wc, &odp_tnl_port)) {
//terminate_native_tunnel 根据外层flow查找tunnel信息,找到 odp 端口,即在datapath的端口号。添加出端口为 odp_tnl_port。
/* Intercept packet to be received on native tunnel port. */
nl_msg_put_odp_port(ctx->odp_actions, OVS_ACTION_ATTR_TUNNEL_POP, odp_tnl_port);
} else {
}
这一步会下发一条流表
recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth(src=2e:ab:5f:92:6c:47,dst=12:31:66:3c:95:47),eth_type(0x0800),ipv4(dst=10.10.10.1,proto=17,frag=no),udp(dst=8472), packets:919, bytes:90148, used:0.409s, actions:tnl_pop(2)
接着执行这条流表(以userspace vxlan为例)
dp_netdev_execute_actions -> odp_execute_actions -> dp_execute_cb -> OVS_ACTION_ATTR_TUNNEL_POP
struct dp_packet_batch *orig_packets_ = packets_;
odp_port_t portno = nl_attr_get_odp_port(a);
struct tx_port *p;
p = pmd_tnl_port_cache_lookup(pmd, portno);
if (p) {
//去掉外层头
netdev_pop_header(p->port->netdev, packets_);
struct dp_packet *packet;
DP_PACKET_BATCH_FOR_EACH (packet, packets_) {
//重新设置 in_port 为 portno,portno为vxlan端口号
packet->md.in_port.odp_port = portno;
}
(*depth)++;
//以vxlan端口为入端口,报文只有内层,重新走slow-path流程,添加第二条流表
dp_netdev_recirculate(pmd, packets_);
dp_netdev_input__(pmd, packets, true, 0);
(*depth)--;
}
第二条流表如下
tunnel(tun_id=0x0,src=10.10.10.2,dst=10.10.10.1,flags(-df-csum+key)),recirc_id(0),in_port(2),packet_type(ns=0,id=0),eth(src=b2:42:ef:84:79:4b,dst=6a:91:a4:4f:78:45),eth_type(0x0800),ipv4(dst=1.1.1.1,proto=1,frag=no), packets:100, bytes:9800, used:0.409s, actions:1
fast path就是按照datapath流表执行,不再赘述。