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
拓扑图如下所示

image.png

对应的命令如下

//在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
拓扑图如下所示

image.png

注意: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流表执行,不再赘述。

你可能感兴趣的:(openvswitch vxlan 源码分析)