使用ovn-trace分析OVN 逻辑流表(Logical Flow) 在本篇文章中,我将解释什么是Logical Flow以及如何使用ovn-trace去更好地理解它们。同时,我也会用一些例子来解释,为什么使用Logical Flow这种抽象模型能让新特性的添加变得出乎意料的简单。

But First, OpenFlow Basics

在深入Logical Flow之前,对于OpenFlow有一个基本的了解是非常必要的。OpenFlow是一个用来对Open vSwitch的packet processing pipeline进行编辑的协议。它能够让你定义一系列伴有rule(flow)的table,每个rule中还包含了priority,match,和一系列的action。对于每个table,能够匹配的flow中优先级最高(数字越大,优先级越高)的将被执行。

首先,让我们来想象一个简单的virtual switch,它有着两个端口,port1和port2

使用ovn-trace分析OVN 逻辑流表(Logical Flow)
在本篇文章中,我将解释什么是Logical Flow以及如何使用ovn-trace去更好地理解它们。同时,我也会用一些例子来解释,为什么使用Logical Flow这种抽象模型能让新特性的添加变得出乎意料的简单。

我们可以用以下命令创建一个有着两个port的bridge:

  1.  
    $ ovs-vsctl add-br br-int
  2.  
    $ ovs-vsctl add-port br-int port1
  3.  
    $ ovs-vsctl add-port br-int port2
  4.  
     
  5.  
    $ ovs-vsctl show
  6.  
    3b1995d8-9683-45db-8929-36c62abdbd31
  7.  
    Bridge br-int
  8.  
    Port "port1"
  9.  
    Interface "port1"
  10.  
    Port br-int
  11.  
    Interface br-int
  12.  
    type: internal
  13.  
    Port "port2"
  14.  
    Interface "port2"

最简单的例子是创建一个table让所有从port1进入的包转发到port2,所有从port2进入的包转发到port1

  1.  
    Table Priority Match Actions
  2.  
    ----- -------- ---------- -------
  3.  
    0 0 in_port=1 output:2
  4.  
    0 0 in_port=2 output:1

我们可以用ovs-ofctl命令来编辑ovs中的pipeline:

  1.  
    $ ovs-ofctl del-flows br-int
  2.  
    $ ovs-ofctl add-flow br-int
  3.  
    $ ovs-ofctl add-flow br-int "table=0, priority=0, in_port=1, actions=output:2"
  4.  
    $ ovs-ofctl add-flow br-int "table=0, priority=0, in_port=2, actions=output:1"
  5.  
     
  6.  
    $ ovs-ofctl dump-flows br-int
  7.  
    NXST_FLOW reply (xid=0x4):
  8.  
    cookie=0x0, duration=9.679s, table=0, n_packets=0, n_bytes=0, idle_age=9, priority=0,in_port=1 actions=output:2
  9.  
    cookie=0x0, duration=2.287s, table=0, n_packets=0, n_bytes=0, idle_age=2, priority=0,in_port=2 actions=output:1

我们还可以对这个例子进行扩展,从而来演示不同priority的使用。现在我们只允许源mac地址为00:00:00:00:01的包进入port1以及只允许源mac地址为00:00:00:00:00:02的包进入port2(basic source port security)。我们接着使用table 0来实现port security,再用table 1决定包的走向。

  

(当然,这些都可以在一个table中完成,但是这样就不能演示table和priority的使用了,而这恰恰是这个例子的主要目的)

  1.  
    Table Priority Match Actions
  2.  
    ----- -------- ----------------------------------- ------------
  3.  
    0 10 in_port=1,dl_src=00:00:00:00:00:01 resubmit(,1)
  4.  
    0 10 in_port=2,dl_src=00:00:00:00:00:02 resubmit(,1)
  5.  
    0 0 drop
  6.  
    1 0 in_port=1 output:2
  7.  
    1 0 in_port=2 output:1

同样,我们使用ovs-ofctl编辑如下pipeline:

  1.  
    $ ovs-ofctl del-flows br-int
  2.  
    $ ovs-ofctl add-flow br-int "table=0, priority=10, in_port=1, dl_src=00:00:00:00:00:01, actions=resubmit(,1)"
  3.  
    $ ovs-ofctl add-flow br-int "table=0, priority=10, in_port=2, dl_src=00:00:00:00:00:02, actions=resubmit(,1)"
  4.  
    $ ovs-ofctl add-flow br-int "table=0, priority=0, actions=drop"
  5.  
    $ ovs-ofctl add-flow br-int "table=1, priority=0, in_port=1,actions=output:2"
  6.  
    $ ovs-ofctl add-flow br-int "table=1, priority=0, in_port=2,actions=output:1"
  7.  
     
  8.  
    $ ovs-ofctl dump-flows br-int
  9.  
    NXST_FLOW reply (xid=0x4):
  10.  
    cookie=0x0, duration=72.132s, table=0, n_packets=0, n_bytes=0, idle_age=72, priority=10,in_port=1,dl_src=00:00:00:00:00:01 actions=resubmit(,1)
  11.  
    cookie=0x0, duration=60.565s, table=0, n_packets=0, n_bytes=0, idle_age=60, priority=10,in_port=2,dl_src=00:00:00:00:00:02 actions=resubmit(,1)
  12.  
    cookie=0x0, duration=28.127s, table=0, n_packets=0, n_bytes=0, idle_age=28, priority=0 actions=drop
  13.  
    cookie=0x0, duration=13.887s, table=1, n_packets=0, n_bytes=0, idle_age=13, priority=0,in_port=1 actions=output:2
  14.  
    cookie=0x0, duration=4.023s, table=1, n_packets=0, n_bytes=0, idle_age=4, priority=0,in_port=2 actions=output:1

Open vSwitch还提供了一种能够追踪包从整个pipeline流通路径的机制。在这里,我们将追踪一个有着期望的mac地址的包从port1流入的过程。追踪程序的输出显示包被重新提交到了table 1并且之后从port 2输出了。输出的内容有点啰嗦,我们只要看"Rule"和"OpenFlow actions"这些列就可以知道哪些flow被执行了。

  1.  
    $ ovs-appctl ofproto/trace br-int in_port=1,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02 -generate
  2.  
    Bridge: br-int
  3.  
    Flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,dl_type=0x0000
  4.  
     
  5.  
    Rule: table=0 cookie=0 priority=10,in_port=1,dl_src=00:00:00:00:00:01
  6.  
    OpenFlow actions=resubmit(,1)
  7.  
     
  8.  
    Resubmitted flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,dl_type=0x0000
  9.  
    Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x0 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
  10.  
    Resubmitted odp: drop
  11.  
    Resubmitted megaflow: recirc_id=0,in_port=1,dl_src=00:00:00:00:00:01,dl_type=0x0000
  12.  
    Rule: table=1 cookie=0 priority=0,in_port=1
  13.  
    OpenFlow actions=output:2
  14.  
     
  15.  
    Final flow: unchanged
  16.  
    Megaflow: recirc_id=0,in_port=1,dl_src=00:00:00:00:00:01,dl_type=0x0000
  17.  
    Datapath actions: 3

OpenFlow可以用来构建更加复杂的pipeline。如果想知道更多细节,参见ovs-ofctl(8) man page。

  

OVN Logical Flows

上一节对OpenFlow进行了概述,展示了如何使用OpenFlow在单个switch里面构建packet processing pipelines。即使只在一台机器上对这些pipeline进行配置都让人觉得很麻烦,更不要说配置成千上万的机器了。但是只要有一个SDN controller,我们就能轻松地对许多switch进行配置,而这正是OVN在Open vSwtich这个项目中扮演的角色。OVN会完成所有OpenFlow的配置工作,用以实现你通过高层的接口配置的network topologies以及security policies。

那么OVN是怎么知道每台机器上需要的flow的呢?OVN解决这个问题的核心抽象模型就是Logical Flow。从概念上来说,Logical Flows和OpenFlow是类似的,它们都由table组成,table中包含flow,每个flow都有priority,match和action。两者最大的不同是,logical flow对整个网络的行为进行了详细地描述并且能扩展到任意数量的主机上。并且它把对于具体网络行为的定义和对于现实的物理设备的布局(有多少机器以及机器端口的分布)分离了开来。

OVN通过logical flow对网络进行编辑。之后,这些logical flow又会分发到每台机器上运行ovn-controller中。而ovn-controller知道根据当前的物理环境(本地端口在哪以及如何到达其他机器)将这些logical flow编译到OpenFlow中。

接着让我们用OVN来创建一个和上一节类似的例子。我们要创建的是一个带有两个logical ports的OVN logical switch。

  1.  
    $ ovn-nbctl ls-add sw0
  2.  
     
  3.  
    $ ovn-nbctl lsp-add sw0 sw0-port1
  4.  
    $ ovn-nbctl lsp-set-address sw0-port1 00:00:00:00:00:01
  5.  
    $ ovn-nbctl lsp-set-port-security sw0-port1 00:00:00:00:00:01
  6.  
     
  7.  
    $ ovn-nbctl lsp-add sw0 sw0-port2
  8.  
    $ ovn-nbctl lsp-set-addresses sw0-port2 00:00:00:00:00:02
  9.  
    $ ovn-nbctl lsp-set-port-security sw0-port2 00:00:00:00:00:02
  10.  
     
  11.  
    $ ovn-nbctl show sw0
  12.  
    switch 48d5f699-7ffe-4627-a369-2fc905e44b32 (sw0)
  13.  
    port sw0-port1
  14.  
    addresses: ["00:00:00:00:00:01"]
  15.  
    port sw0-port2
  16.  
    addresses: ["00:00:00:00:00:02"]

OVN定义了一个logical switch,sw0,使用了两个pipeline:一个ingress pipeline以及一个egress pipeline。当一个包流入网络时,生成该包的机器上的ingress pipeline将被执行。如果该包的目的地是同一台机器,那么egress pipeline也将被执行。

sw0-port1 在 sw0-port2 在相同主机上的场景:

使用ovn-trace分析OVN 逻辑流表(Logical Flow)
在本篇文章中,我将解释什么是Logical Flow以及如何使用ovn-trace去更好地理解它们。同时,我也会用一些例子来解释,为什么使用Logical Flow这种抽象模型能让新特性的添加变得出乎意料的简单。

如果该包的目的地在远程机器上,那么该包首先要经过一个tunnel进行传输,然后在远程机器上执行egress pipeline

sw0-port1 和 sw0-port2 在不同主机的场景:

使用ovn-trace分析OVN 逻辑流表(Logical Flow)
在本篇文章中,我将解释什么是Logical Flow以及如何使用ovn-trace去更好地理解它们。同时,我也会用一些例子来解释,为什么使用Logical Flow这种抽象模型能让新特性的添加变得出乎意料的简单。

你可以使用"ovn-sbctl lflow-list"命令来查看完整的logical flow。它看起来会和OpenFlow有些相似,但是还是有非常不一样的地方的:

  1. Ports是一个逻辑概念,它位于网络中的某处,而不是一个switch中的physical ports
  2. pipeline中的每一个table除了编号以外还多了一个名字。这个名字描述了pipeline在该阶段的作用
  3. 匹配更灵活。支持复杂的布尔运算,对于程序员应该是很熟悉的了
  4. OVN logical flows扩展的action远远超过了OpenFlow。我们可以用logical flow的语法控制一些高级的特性,例如DHCP。具体的match和syntax的语法可以参见ovn-sb(5)中关于Logical_Flow的文档

在本例中,pipeline中还保留了很多额外的stage,用于未被用到的特性,所以下面很多table的flow实际上没有做任何事情

  1.  
    $ ovn-sbctl lflow-list
  2.  
    Datapath: "sw0" (d7bf4a7b-e915-4502-8f9d-5995d33f5d10) Pipeline: ingress
  3.  
    table=0 (ls_in_port_sec_l2 ), priority=100 , match=(eth.src[40]), action=(drop;)
  4.  
    table=0 (ls_in_port_sec_l2 ), priority=100 , match=(vlan.present), action=(drop;)
  5.  
    table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "sw0-port1" && eth.src == {00:00:00:00:00:01}), action=(next;)
  6.  
    table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "sw0-port2" && eth.src == {00:00:00:00:00:02}), action=(next;)
  7.  
    table=1 (ls_in_port_sec_ip ), priority=0 , match=(1), action=(next;)
  8.  
    table=2 (ls_in_port_sec_nd ), priority=90 , match=(inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && arp.sha == 00:00:00:00:00:01), action=(next;)
  9.  
    table=2 (ls_in_port_sec_nd ), priority=90 , match=(inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && ip6 && nd && ((nd.sll == 00:00:00:00:00:00 || nd.sll == 00:00:00:00:00:01) || ((nd.tll == 00:00:00:00:00:00 || nd.tll == 00:00:00:00:00:01)))), action=(next;)
  10.  
    table=2 (ls_in_port_sec_nd ), priority=90 , match=(inport == "sw0-port2" && eth.src == 00:00:00:00:00:02 && arp.sha == 00:00:00:00:00:02), action=(next;)
  11.  
    table=2 (ls_in_port_sec_nd ), priority=90 , match=(inport == "sw0-port2" && eth.src == 00:00:00:00:00:02 && ip6 && nd && ((nd.sll == 00:00:00:00:00:00 || nd.sll == 00:00:00:00:00:02) || ((nd.tll == 00:00:00:00:00:00 || nd.tll == 00:00:00:00:00:02)))), action=(next;)
  12.  
    table=2 (ls_in_port_sec_nd ), priority=80 , match=(inport == "sw0-port1" && (arp || nd)), action=(drop;)
  13.  
    table=2 (ls_in_port_sec_nd ), priority=80 , match=(inport == "sw0-port2" && (arp || nd)), action=(drop;)
  14.  
    table=2 (ls_in_port_sec_nd ), priority=0 , match=(1), action=(next;)
  15.  
    table=3 (ls_in_pre_acl ), priority=0 , match=(1), action=(next;)
  16.  
    table=4 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;)
  17.  
    table=5 (ls_in_pre_stateful ), priority=100 , match=(reg0[0] == 1), action=(ct_next;)
  18.  
    table=5 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;)
  19.  
    table=6 (ls_in_acl ), priority=0 , match=(1), action=(next;)
  20.  
    table=7 (ls_in_qos_mark ), priority=0 , match=(1), action=(next;)
  21.  
    table=8 (ls_in_lb ), priority=0 , match=(1), action=(next;)
  22.  
    table=9 (ls_in_stateful ), priority=100 , match=(reg0[1] == 1), action=(ct_commit(ct_label=0/1); next;)
  23.  
    table=9 (ls_in_stateful ), priority=100 , match=(reg0[2] == 1), action=(ct_lb;)
  24.  
    table=9 (ls_in_stateful ), priority=0 , match=(1), action=(next;)
  25.  
    table=10(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
  26.  
    table=11(ls_in_dhcp_options ), priority=0 , match=(1), action=(next;)
  27.  
    table=12(ls_in_dhcp_response), priority=0 , match=(1), action=(next;)
  28.  
    table=13(ls_in_l2_lkup ), priority=100 , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
  29.  
    table=13(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0-port1"; output;)
  30.  
    table=13(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;)
  31.  
    Datapath: "sw0" (d7bf4a7b-e915-4502-8f9d-5995d33f5d10) Pipeline: egress
  32.  
    table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;)
  33.  
    table=1 (ls_out_pre_acl ), priority=0 , match=(1), action=(next;)
  34.  
    table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[0] == 1), action=(ct_next;)
  35.  
    table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;)
  36.  
    table=3 (ls_out_lb ), priority=0 , match=(1), action=(next;)
  37.  
    table=4 (ls_out_acl ), priority=0 , match=(1), action=(next;)
  38.  
    table=5 (ls_out_qos_mark ), priority=0 , match=(1), action=(next;)
  39.  
    table=6 (ls_out_stateful ), priority=100 , match=(reg0[1] == 1), action=(ct_commit(ct_label=0/1); next;)
  40.  
    table=6 (ls_out_stateful ), priority=100 , match=(reg0[2] == 1), action=(ct_lb;)
  41.  
    table=6 (ls_out_stateful ), priority=0 , match=(1), action=(next;)
  42.  
    table=7 (ls_out_port_sec_ip ), priority=0 , match=(1), action=(next;)
  43.  
    table=8 (ls_out_port_sec_l2 ), priority=100 , match=(eth.mcast), action=(output;)
  44.  
    table=8 (ls_out_port_sec_l2 ), priority=50 , match=(outport == "sw0-port1" && eth.dst == {00:00:00:00:00:01}), action=(output;)
  45.  
    table=8 (ls_out_port_sec_l2 ), priority=50 , match=(outport == "sw0-port2" && eth.dst == {00:00:00:00:00:02}), action=(output;)

 理解logical flow最好的方式就是使用ovn-trace命令。ovn-trace能够让你看到OVN对一个包是怎么处理的。

ovn-trace需要两个参数:

$ ovn-trace DATAPATH MICROFLOW 

 DATAPATH标识了sample packet开始进入的logical datapath(一个logical switch或者一个logical router)。MICROFLOW描述了模拟的sample packet。具体的细节详见ovn-trace(8) man page。

给定上文中的OVN配置,让我们来看看OVN会怎么处理一个由sw0-port1进入并将发往sw0-port2的包。ovn-trace的输出结果有多个详略程度可供选择。第一个是--minimal,它会告诉你这个包发生了些什么,但是会省略很多不必要的细节。在这个例子中,我们将看到的最终结果是该包将被发往sw0-port2。

  1.  
    $ ovn-trace --minimal sw0 'inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
  2.  
    # reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,dl_type=0x0000 output("sw0-port2");

下一个等级是--summary。在这个模式,我们能知道更多的关于包处理的细节,包括那个pipeline被执行了。如果我们对于同一个sample packet使用ovn-trace,我们将看到如下内容:

  1. 包从sw0-port1进入网络(sw0)并且运行了ingress pipeline
  2. 我们可以看到"sw0-port2"设置到了"output"这个变量中,这以为着该包的目的端口是"sw0-port2"
  3. 包从ingress pipeline输出,并且输出到"sw0"的egress pipeline并且输出变量设置为"sw0-port2"
  4. egress pipeline中的output action将被执行,它将把包输出到当前"output"变量对应的值中,即"sw0-port2"
    1.  
      $ ovn-trace --summary sw0 'inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
    2.  
      # reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,dl_type=0x0000
    3.  
      ingress(dp="sw0", inport="sw0-port1") {
    4.  
      outport = "sw0-port2";
    5.  
      output;
    6.  
      egress(dp="sw0", inport="sw0-port1", outport="sw0-port2") {
    7.  
      output;
    8.  
      /* output to "sw0-port2", type "" */;
    9.  
      };
    10.  
      };
      

当你在debug或者要修改代码的时候,你可能需要更细节的输出。ovn-trace有一个--detail选项。在这种模式下,你将获得更多的细节,关于包流动过程中遇到的每一个meaningful logical flow。你可以看到table number,pipeline stage name,full match,以及flow的priority number。你还可以看到负责创建该logical flow的OVN源代码的位置

  1.  
    $ ovn-trace --detailed sw0 'inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
  2.  
    # reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,dl_type=0x0000
  3.  
     
  4.  
    ingress(dp="sw0", inport="sw0-port1")
  5.  
    -------------------------------------
  6.  
    0. ls_in_port_sec_l2 (ovn-northd.c:2827): inport == "sw0-port1" && eth.src == {00:00:00:00:00:01}, priority 50
  7.  
    next(1);
  8.  
    13. ls_in_l2_lkup (ovn-northd.c:3095): eth.dst == 00:00:00:00:00:02, priority 50
  9.  
    outport = "sw0-port2";
  10.  
    output;
  11.  
     
  12.  
    egress(dp="sw0", inport="sw0-port1", outport="sw0-port2")
  13.  
    ---------------------------------------------------------
  14.  
    8. ls_out_port_sec_l2 (ovn-northd.c:3170): outport == "sw0-port2" && eth.dst == {00:00:00:00:00:02}, priority 50
  15.  
    output;
  16.  
    /* output to "sw0-port2", type "" */

另一个使用ovn-trace的例子是观察一个包为什么会被丢弃。我们已经设置了port security,现在让我们来看看从sw0-port1发出一个有着非预期源mac地址的包,它的执行路径是什么样的。从输出我们可以看到,这个包进入了sw0但是不能匹配table 0中任何一个flow,这意味着这个包会被丢弃。我们还可以看到table 0被命名为"ls_in_port_sec_l2",其实就是"Logical Switch ingress L2 port security"的缩写。

  1.  
    $ ovn-trace --detailed sw0 'inport == "sw0-port1" && eth.src == 00:00:00:00:00:ff && eth.dst == 00:00:00:00:00:02'
  2.  
    # reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:ff,dl_dst=00:00:00:00:00:02,dl_type=0x0000
  3.  
     
  4.  
    ingress(dp="sw0", inport="sw0-port1")
  5.  
    -------------------------------------
  6.  
    0. ls_in_port_sec_l2: no match (implicit drop)

另一个相似的例子是,一个包有着未知的目的mac地址。在这种情况下,我们将看到这个包成功通过了table 0,但是未能匹配table 13,"ls_in_l2_lkup","Logical Switch ingress L2 lookup"的缩写。

  1.  
    $ ovn-trace --detailed sw0 'inport == "sw0-port1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:ff'
  2.  
    # reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:ff,dl_type=0x0000
  3.  
     
  4.  
    ingress(dp="sw0", inport="sw0-port1")
  5.  
    -------------------------------------
  6.  
    0. ls_in_port_sec_l2 (ovn-northd.c:2827): inport == "sw0-port1" && eth.src == {00:00:00:00:00:01}, priority 50
  7.  
    next(1);
  8.  
    13. ls_in_l2_lkup: no match (implicit drop)

到目前为止我们已经看过了在单个L2 logical network中的几个例子。接下来我们来创建一个新的环境用来演示ovn-trace怎么跨多个网络进行工作。我们将会创建两个network,每个有2个port,并且将他们连接到一个logical router上。

  1.  
    #!/bin/bash
  2.  
     
  3.  
    # Create the first logical switch and its two ports.
  4.  
    ovn-nbctl ls-add sw0
  5.  
     
  6.  
    ovn-nbctl lsp-add sw0 sw0-port1
  7.  
    ovn-nbctl lsp-set-addresses sw0-port1 "00:00:00:00:00:01 10.0.0.51"
  8.  
    ovn-nbctl lsp-set-port-security sw0-port1 "00:00:00:00:00:01 10.0.0.51"
  9.  
     
  10.  
    ovn-nbctl lsp-add sw0 sw0-port2
  11.  
    ovn-nbctl lsp-set-addresses sw0-port2 "00:00:00:00:00:02 10.0.0.52"
  12.  
    ovn-nbctl lsp-set-port-security sw0-port2 "00:00:00:00:00:02 10.0.0.52"
  13.  
     
  14.  
    # Create the second logical switch and its two ports.
  15.  
    ovn-nbctl ls-add sw1
  16.  
     
  17.  
    ovn-nbctl lsp-add sw1 sw1-port1
  18.  
    ovn-nbctl lsp-set-addresses sw1-port1 "00:00:00:00:00:03 192.168.1.51"
  19.  
    ovn-nbctl lsp-set-port-security sw1-port1 "00:00:00:00:00:03 192.168.1.51"
  20.  
     
  21.  
    ovn-nbctl lsp-add sw1 sw1-port2
  22.  
    ovn-nbctl lsp-set-addresses sw1-port2 "00:00:00:00:00:04 192.168.1.52"
  23.  
    ovn-nbctl lsp-set-port-security sw1-port2 "00:00:00:00:00:04 192.168.1.52"
  24.  
     
  25.  
    # Create a logical router between sw0 and sw1.
  26.  
    ovn-nbctl create Logical_Router name=lr0
  27.  
     
  28.  
    ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 10.0.0.1/24
  29.  
    ovn-nbctl lsp-add sw0 sw0-lrp0
  30.  
    -- set Logical_Switch_Port sw0-lrp0 type=router
  31.  
    options:router-port=lrp0 addresses='"00:00:00:00:ff:01"'
  32.  
     
  33.  
    ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 192.168.1.1/24
  34.  
    ovn-nbctl lsp-add sw1 sw1-lrp1
  35.  
    -- set Logical_Switch_Port sw1-lrp1 type=router
  36.  
    options:router-port=lrp1 addresses='"00:00:00:00:ff:02"'

我们可以使用"ovn-nbctl show"命令来观察最终的逻辑网络的配置

  1.  
    $ ovn-nbctl show
  2.  
    switch bf4ba6c6-91c5-4f56-9981-72643816f923 (sw1)
  3.  
    port sw1-lrp1
  4.  
    addresses: ["00:00:00:00:ff:02"]
  5.  
    port sw1-port2
  6.  
    addresses: ["00:00:00:00:00:04 192.168.1.52"]
  7.  
    port sw1-port1
  8.  
    addresses: ["00:00:00:00:00:03 192.168.1.51"]
  9.  
    switch 13b80127-4b36-46ea-816a-1ba4ffd6ac57 (sw0)
  10.  
    port sw0-port1
  11.  
    addresses: ["00:00:00:00:00:01 10.0.0.51"]
  12.  
    port sw0-lrp0
  13.  
    addresses: ["00:00:00:00:ff:01"]
  14.  
    port sw0-port2
  15.  
    addresses: ["00:00:00:00:00:02 10.0.0.52"]
  16.  
    router 68935017-967a-4c4a-9dad-5d325a9f203a (lr0)
  17.  
    port lrp0
  18.  
    mac: "00:00:00:00:ff:01"
  19.  
    networks: ["10.0.0.1/24"]
  20.  
    port lrp1
  21.  
    mac: "00:00:00:00:ff:02"
  22.  
    networks: ["192.168.1.1/24"]

我们现在可以对一个包进行追踪,这个包从sw0-port1发往sw1-port2,中间需要经过路由器。minimal output将会显示最终的结果是,这个包会从sw1-port2输出。我们还将看到过程中对这个包所做的修改。当包经过logical router时,TTL会减小,源和目的mac地址都会针对下一跳进行更新。

  1.  
    $ ovn-trace --minimal sw0 'inport == "sw0-port1" &&
  2.  
    eth.src == 00:00:00:00:00:01 &&
  3.  
    ip4.src == 10.0.0.51 &&
  4.  
    eth.dst == 00:00:00:00:ff:01 &&
  5.  
    ip4.dst == 192.168.1.52 &&
  6.  
    ip.ttl == 32'
  7.  
    # ip,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:ff:01,nw_src=10.0.0.51,nw_dst=192.168.1.52,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=32
  8.  
    ip.ttl--;
  9.  
    eth.src = 00:00:00:00:ff:02;
  10.  
    eth.dst = 00:00:00:00:00:04;
  11.  
    output("sw1-port2");
如果你想要观察得更仔细,可以同样执行上述命令,将--minimal改为--summary或者--detailed即可。你也可以对sample packet进行改变,看看到底会发生什么。

  

A Powerful Abstraction

我在文章的最开始就已经说过,我觉得OVN的Logical Flows是非常强大的抽象,它可以让对OVN添加特性变得异常简单。既然现在我们对logical flow有了一定的了解,下面我将说明一些最近新添加的特性,用于演示向OVN添加特性是多么地简单。

  

SOURCE BASED ROUTING

OVN已经支持了L3 gateways,它能够提供OVN logical network和physical network的连接。一个典型的OVN网络一般会有一个L3 gateway位于某台机器上。使用单个L3 gateway的缺陷是所有发往physical network的流量必须流经L3 gateway所在的机器。

最近Gurucharan Shetty添加了在OVN logical network中对于multiple L3 gateway的支持。它能够基于源IP地址在gateway之间分发流量。

我们并不需要知道这一改变的所有细节。我只想说明的是我们仅仅需要改动非常少量的代码就能实现这一特性。让我们来看一看diffstat。

  1.  
    Documentation:
  2.  
    NEWS | 1
  3.  
    ovn/ovn-nb.xml | 28 +++++
  4.  
    ovn/utilities/ovn-nbctl.8.xml | 8 +
  5.  
     
  6.  
    Database schema update:
  7.  
    ovn/ovn-nb.ovsschema | 8 +
  8.  
     
  9.  
    Command line utility support for new db schema additions:
  10.  
    ovn/utilities/ovn-nbctl.c | 43 ++++++--
  11.  
     
  12.  
    Changes to how OVN builds logical flows to add support for this feature:
  13.  
    ovn/northd/ovn-northd.c | 24 +++-
  14.  
     
  15.  
    Test code:
  16.  
    tests/ovn-nbctl.at | 42 ++++----
  17.  
    tests/ovn.at | 219 ++++++++++++++++++++++++++++++++++++++++++
  18.  
     
  19.  
    8 files changed, 334 insertions(+), 39 deletions(-)

最终要的是ovn-northd.c中的24行代码变动。这些代码用于更新logical flow的创建。这让我非常震惊,因为这个特性对于网络的行为有着非常重要的影响,但是却只用几行的C代码就搞定了。

  

DSCP

另一个通过logical flow添加特性的例子是这个patch,它基于arbitrary traffic classifier为设定IP包中的DSCP域提供了支持。这个patch在OVN northbound database中添加了一个新的QoS table。在这个table中定义了一个match(或者traffic classfier)以及相应DSCP值,用于设置那些匹配该classfier的包。

这个改变对应了ovn-northd.c中的80行代码。这个patch看起来比它实际的要大一些,因为它会为QoS创建一个新的pipeline stage并且还需要对它之后的stage进行重新编号。实现这个特性,只需要能插入能够匹配配置好的match(traffic classfier),之后在执行OVN logical flow actions"ip.dscp=VALUE; next"的flow就可以了。

  

DHCP

OVN支持DHCPv4和DHCPv6,所有这些都是通过logical flow实现的。在很多情况下,如果要改变它们的行为,只需要改变那些生成DHCP相关的logical flow的代码就可以了。

最近的一个例子是这个patch,它添加了对无状态DHCPv6的支持。有了无状态DHCPv6,我们能够提供一些configuration entries(例如DNS地址),但是不提供IPv6地址。这对于DHCPv6的logical flows只是一个小小的调整,只要OVN创建的应答能够有选择地包含IPv6地址选项就可以了。

  

结论

我希望你对OVN Logical Flows已经有了更深的理解,它其实就是一个用于定义逻辑网络行为的,可扩展到多台机器上的match-action pipeline。