前言
理解了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覆盖了reg0~reg3(reg0是最高位,reg3是最低位)。类似的,xxreg1代表reg4~reg7. 支持的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 == 132actions:(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等。主要是方便查看。参考链接: