zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    【线段树】【积累】主席树杂题积累 2016CCPC长春K SequenceII
    【积累】最小不能表示正整数 (以及一些做法
    【字符串】回文树&&回文自动机PAM
    【字符串】后缀自动机SAM
    【字符串】AC自动机
    【字符串】Trie树
    StringUtils类中isEmpty与isBlank的区别
    【Git】pull遇到错误:error: Your local changes to the following files would be overwritten by merge:
    jsp 与jstl
    listener 作用
  • 原文地址:https://www.cnblogs.com/sixloop/p/ovn_logical_flow.html
Copyright © 2011-2022 走看看