ovn_logical_flow

前言

理解了OVN logical flow,对流量转发行为和路径分析来说有很大帮助。

ovn-trace命令就是工作在logical_flow层面的,从这个角度来看,我们可以忽略logical_flow 转换成ovs flow后是什么样子的,我们相信翻译之后的流表会按logical flow中定义的规则那样工作,因此,我们把重心放在logical flow中规则的设计与分析(以上是自己理解)。

概述

Logical_Flow 表中的每一行代表一条logical flow,ovn-northd通过向这个表中注入logical flow来实现在OVN_Northbound 数据库中定义的2、 3层拓扑结构。

每个hypervisor,会通过ovn-controller 把logical flow翻译成OpenFlow流并落到Open vSwitch中。
logical flow是通过OVN指定格式来表达的,下面会详细描述。

一个逻辑datapath 流很像一个OpenFlow流,只是逻辑流都写在逻辑port和逻辑datapath,
而不是物理port和物理datapath。逻辑和物理流之间的翻译有助于确保逻辑datapath之间的隔离。(逻辑流抽象也让OVN中心化组件负担更小,因为它不用分别计算并push物流到各个chassis。
下面这些描述说的好像是OVN自身执行了这些步骤,但事实上是OVN(ovn-controller)通过OpenFlow和OVSDB协议,在操控着Open vSwitch来使它按照自己的意图工作。

在高层次上,OVN把包转到logical datapath的逻辑入pipeline,之后可能输出包到一个或多个逻辑端口或逻辑组播组。而对于每个出端口,OVN把包转到logical datapath的 逻辑出pipeline,之后要么drop要么转到目的地。在入pipeline到出pipeline之间,到逻辑组播组的输出会被扩展成多个普通逻辑出端口,这样出pipeline阶段就可以简单的一次处理一个逻辑出端口。同样,在入pipeline到出pipeline之间,如果需要的话,OVN也会封包到tunnels来跨 hypervisor转发。

更详细的说,对于一个包,OVN首先在table 0搜索相应的logical_datapath, a pipeline of ingress 以及match字段匹配的row,如果找到多行,则选择优先级最高的。然后OVN执行该行的actions(按顺序),一些action会改变包头,然后指定next和output actions。
next指令会递归执行上面的过程,只不过这次搜索当前table_id+1.

logical flow中的字段

logical_datapath: 指的是某个Datapath_Binding,表示logical flow所属的逻辑datapath。
pipeline:(ingress或者egress)。首先决定一个包的目的地是ingress flows。 ACL规则实在egress阶段实现的。
table_id: 这个OpenFlow中table id很像。
priority:int(0~65535) 。值越大优先级越高。如果有两个优先级一样的都匹配了,那么最终执行的到底是哪一条是不确定的。

match:一个匹配表达式。

OVN提供了OpenFLow匹配能力的超集。语法很像普通编程语言的bool表达式。
大多数条件是符号和常量的比较。例如:ip4.dst 192.168.0.1, ip.proto == 6, arp.op == 1, eth.type == 0x800。 用&& 和||能够组成更大的表达式。
表达式也支持通过括号来分组。逻辑非用!表示true和false用1和0表示。
符号:
type:有两种类型。int和string。int有位宽。
kinds:有三种符号:
fields:字段:字段代表一个包的头字段或者metadata字段。例如,vlan.tci就代表包的VLAN TCI字段。
subfields 子字段:为了表达字段中的某几位方便。比如vlan.vid可能和vlan.tci[0..11]是等价的。
predicates 谓词:它是一个bool表达式的速记表示。predicates用起来更像是1-bit的字段。例如:ip4等价于eth.type
0x800. 也是为了方便表示。

Prerequisites :一个Symbol可能会有先决条件。

用这个sysmbol的时候就暗示了这个附加条件。

例如。icmp.type符号的先决条件就是icmp4,它可能被翻译成icmp4.type==0 && icmp4 , 同样地又会扩展成icmp4.type == 0 && eth.type == 0x800 && ip4.proto == 1

关系操作符:所有标准的关系操作符都支持,如==,!=,>,>=.
常量:整形常量可以用十进制或十六进制表示(0x)。
杂项:顺序 tcp.src == 80 and 80 == tcp.src 都可以。
范围可以用例如1024=tcp.src=49151表示。
注释:可以用/* */,但不支持多行。
符号:
大部分符号是整形的。inport和outport是string类型。inport代表一个lofical port,因此它的值是Port_Binding表中logical_port 的name。output可能是Port_Binding中的portname,也可能是Multicast_Group表中的逻辑组播组的name。
形如 regX 的符号是32-bit的int。而xxregX是128-bit的int。它覆盖了于4个32-bit int。例如xxreg0覆盖了reg0reg3(reg0是最高位,reg3是最低位)。类似的,xxreg1代表reg4reg7.
支持的symbol如下:
· reg0...reg9
· xxreg0 xxreg1
· inport outport
· flags.loopback
· eth.src eth.dst eth.type
· vlan.tci vlan.vid vlan.pcp vlan.present
· ip.proto ip.dscp ip.ecn ip.ttl ip.frag
· ip4.src ip4.dst
· ip6.src ip6.dst ip6.label
· arp.op arp.spa arp.tpa arp.sha arp.tha
· tcp.src tcp.dst tcp.flags
· udp.src udp.dst
· sctp.src sctp.dst
· icmp4.type icmp4.code
· icmp6.type icmp6.code
· nd.target nd.sll nd.tll
· ct_mark ct_label
· ct_state, which has the following Boolean subfields:
· ct.new: True for a new flow
· ct.est: True for an established flow
· ct.rel: True for a related flow
· ct.rpl: True for a reply flow
· ct.inv: True for a connection entry in a bad state
ct_state 和它的子字段在遇到ct_next的时候会被初始化,下面会描述.

支持的谓词predicates如下:
· eth.bcast expands to eth.dst == ff:ff:ff:ff:ff:ff
· eth.mcast expands to eth.dst[40]
· vlan.present expands to vlan.tci[12]
· ip4 expands to eth.type == 0x800
· ip4.mcast expands to ip4.dst[28..31] == 0xe
· ip6 expands to eth.type == 0x86dd
· ip expands to ip4 || ip6
· icmp4 expands to ip4 && ip.proto == 1
· icmp6 expands to ip6 && ip.proto == 58
· icmp expands to icmp4 || icmp6
· ip.is_frag expands to ip.frag[0]
· ip.later_frag expands to ip.frag[1]
· ip.first_frag expands to ip.is_frag &&& !ip.later_frag
· arp expands to eth.type == 0x806
· nd expands to icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255
· nd_ns expands to icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255
· nd_na expands to icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255
· tcp expands to ip.proto == 6
· udp expands to ip.proto == 17
· sctp expands to ip.proto == 132

actions:(string类型)

当logical flow中最高优先级的flow被匹配时,将会执行它的actions。
actions列和match列用同样的语法。actions字段留空或是只有'drop;',都会造成匹配的包被drop。否则这一列就应该包含由分号分隔的多个action。
目前支持下面这些action:
output:
在入pipeline阶段,这个action会开始执行出pipeline,如果outport是logical port的名字,那么出pipeline会被执行一次,如果是multical group,则会对group中每个logical port分别执行一次出pipeline。
在出pipeline阶段,这个action 才代表真正的要输出的logical port (备注:出pipeline的outport只能是logical port的名字,不能是组播组的名字)。
默认情况下,如果outport==inport,output这个action相当于一个空操作。有时候覆盖这个默认行为是有用的,比如为一个arp请求做arp回应,用了一个flags.loopback=1,这样就允许从某个端口如的报文被pipeling处理后再从这个入端口出去。

next:
next(table):
执行 另一个逻辑datapath的table 作为子过程。不指定table参数的话,会执行当前table_id+1的表,指定table的话就会跳到指定table执行当前pipeline。

field = constant:
给字段赋值。如:outport = “vif0”可以修改outport的值,要想设置一个字段的某些bits的话,可以用如vlan.pcp[2] = 1设置某一位;或是用vlan.pcp = 4/4;指定 VLAN PCP的最高位。
对一个隐含有先决条件的字段赋值会添加需要match的先决条件,因此,比如一个设置了tcp.dst的flow只会应用到tcp流。而不管是否匹配tcp的其他字段。
并不是所有的字段都能够修改(如eth.type和ip.proto就是只读的),另外并不是所有的可修改字段都可以给部分bits赋值(比如ip.ttl就必须整体赋值)。还有些特殊的,比如output字段在ingress pipeline阶段可以修改,但是在egress pipeline就不能修改。
field1=field2:
用一个字段的值给另一个字段赋值。如reg0=ip4.src;也可以为某些bits赋值,如vlan.pcp = reg0[0..2]; 。
field1和field2必须是同类型的,即都是string或int。另外如果都是int的话,位宽必须一样。
有些隐含先决条件的字段,隐含的回去匹配先决条件,这种情况下,可能会出现互相矛盾的赋值语句,比如ip4.src=ip6.src[0..31],这意味着这条流永远不会被匹配到。
field1 <-> field2;
交换两个字段的值(必须两个字段都是可修改的)。
ip.ttl--
对Ipv4或ipv6的ttl减一。如果减一之后=0,就会停止包处理过程,后面的action也不会被继续执行。(为了合理的处理这种情况,我们可以设置一条高优先级的flow来匹配ip.ttl=={0,1})
ct_next:
应用connection tracking到flow中,初始化CS_state以匹配后面的tables。自动跳转到下一个talbe(类似于后面跟了一个next action)
有一个负面影响就是,分片的ip包需要重组之后才能匹配。如果分片的包被输出的话,那么它将带有重叠的碎片。由于contrack状态被复制到了logical port, 因此可是使用重叠的地址,为了使相关的流量都能匹配,需要执行ct_commit.
ct_next后面也可能带有下面的这些actions,但是这些不会有副作用,通常用处也不大。
ct_commit;
ct_commit(ct_mark=value[/mask]);
ct_commit(ct_label=value[/mask]);
ct_commit(ct_mark=value[/mask], ct_label=value[/mask]);
通过先调用ct_next,然后再执行这些action,可以把一个flow提交到与它关联的contrack表项。当用到ct_mark=value[/mask] and/or ct_label=value[/mask]的时候,这些值会被设置到contrack表项中。ct_mark是一个32-bit的字段。ct_label是128-bit的。value[/mask]如果超过64bits的话应该用hex string表示。
注意:如果执行完ct_commt之后希望包能够被接下来的table继续处理,那么后面要跟一个next action;你也可以不加next,这样的话contrack状态被提交之后就会drop这个包。有些场景可能会这么用,比如drop一个包之间设置一下contrack表项的ct_mark.
ct_nat;
ct_dnat(IP);
ct_dnat 会根据contrack表DNAT zone发包或是unDNAT一个反向包,之后这个包被 送到下一个table(类似于后面跟了个next action)。下一个表能够看到之前connection tracker做的修改。
ct_nat(IP)把包送到DNAT zone(dst IP变成括号中的IP)然后提交这个connection。同样这个包被 送到下一个table(类似于后面跟了个next action)。下一个表能够看到之前connection tracker做的修改。
ct_snat;
cs_snat(IP);
和dnat过程类似。
arp{action;...};
临时替换ipv4包为arp包,然后针对这个arp包执行里面嵌套定义的每个action。arp里面定义的action会应用到原始包。
这些actions所作用的arp包是基于原来的ipv4包初始化生成的。下面列出了嵌套的action可能会修改的一些字段:
· eth.src unchanged
· eth.dst unchanged
· eth.type = 0x0806
· arp.op = 1 (ARP request)
· arp.sha copied from eth.src
· arp.spa copied from ip4.src
· arp.tha = 00:00:00:00:00:00
· arp.tpa copied from ip4.dst
arp包和ip包有相同的VLAN头(如果有的话)。

get_arp(P, A);
参数P(string): logical port
参数A(int):32-bit IP address。
在P的mac binding 表中查找A,如果查到的话,存储它的Ethernet address到eth.dst.否则存储为00:00:00:00:00:00.
用法举例:get_arp(outport, ip4.dst);

put_arp(P, A, E);
参数P(string): logical port
参数A(int):32-bit IP address。
参数E(int) :48-bit Ethernet address
添加或更新P的mac binding表的IP address A的48-bit Ethernet address E。
例子:put_arp(inport, arp.spa, arp.sha);

下面三个是ipv6 邻居发现相关的操作,暂不研究。
nd_na{action...};
get_nd(P, A);
put_nd(P,A,E);

R = put_dhcp_opts(offerip = IP, D1 = V1, D2 = V2, ..., Dn = Vn);
设置dhcp选项。
Example: reg0[0] = put_dhcp_opts(offerip = 10.0.0.2, router = 10.0.0.1,
netmask = 255.255.255.0, dns_server = {8.8.8.8,7.7.7.7});
ct_lb;
ct_lb(ip[: port] ...);
负载均衡相关,暂不研究。

icmpv4{action;。。。}
临时替换一个ipv4包为一个icmpv4包,并执行里面嵌套的action。action可能会修改下面的一些字段:
· ip.proto = 1 (ICMPv4)
· ip.frag = 0 (not a fragment)
· icmp4.type = 3 (destination unreachable)
· icmp4.code = 1 (host unreachable)

tcp_request;
这个action根据下面的伪代码的逻辑转换当前的tcp 包:
if (tcp.ack) {
tcp.seq = tcp.ack;
} else {
tcp.ack = tcp.seq + length(tcp.payload);
tcp.seq = 0;
}
tcp.flags = RST;
然后,这个action丢掉所有的tcp选项和payload数据,更新tcp校验和checksum.
external_ids:
stage-name(option string):当前所处阶段的可读性表示,如ls_out_port_sec_ip、ls_out_acl等。主要是方便查看。

参考链接: http://openvswitch.org/support/dist-docs/ovn-sb.5.html

原文地址:https://www.cnblogs.com/sixloop/p/ovn_logical_flow.html