OVS代码结构(by quqi99)

Posted quqi99

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OVS代码结构(by quqi99)相关的知识,希望对你有一定的参考价值。

作者:张华 发表于:2021-12-28
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明
( http://blog.csdn.net/quqi99 )

问题

为了理解这个错误:

openvswitch: ovs-system: deferred action limit reached, drop recirc action

初步看来代码路径大概是:

ovs_dp_process_packet -> ovs_execute_actions -> process_deferred_actions -> do_execute_actions(OVS_ACTION_ATTR_RECIRC) -> (execute_recirc|sample|clone|execute_check_pkt_len) -> clone_execute -> add_deferred_actions -> action_fifo_put

但由于ovs的代码结构不清楚,所以对上面代码路径仍然不是很理解

为什么要理解这个错误,是因为下面这个VM can’t ping GW的问题:
VM(10.10.30.20, hosted in ecs4), GW chassis(ecs12)
在计算节点上抓包并分析, 从VM上得ping GW

openstack port list --server <vm>
tcpdump -enli tap<first-11-chars> -p `hostname`_<vm-1-tap>.pcap
tshark -r ecs4_xx.pcap ip.src==10.10.30.20 and icmp

在GW chassis(the node with the highest priority)上抓包

sudo ovn-nbctl lrp-list neutron-<router-uuid>
sudo ovn-nbctl lrp-get-gateway-chassis lrp-<ovn-port-uuid>
tcpdump -enli bond1 "(icmp or arp)" -w `hostname`_bond1.pcap
tshark -r ecs4_xx.pcap ip.src==10.10.30.20 and icmp

确实看到了intermittent pings

$ tshark -r ecs4_37552ee4-38.pcap ip.src==10.10.30.20 and icmp
254 74.664491 10.10.30.20 → 10.10.30.1 ICMP 98 Echo (ping) request id=0x17ab, seq=99/25344, ttl=64
267 75.679441 10.10.30.20 → 10.10.30.1 ICMP 98 Echo (ping) request id=0x17ab, seq=100/25600, ttl=64
268 75.679799 10.10.30.1 → 10.10.30.20 ICMP 98 Echo (ping) reply id=0x17ab, seq=100/25600, ttl=254 (request in 267)

ecs4上的sosreport看到了下列3种error:

$ grep -r 'deferred action limit reached' var/log/kern.log |tail -n1
Nov  8 13:14:30 ecs4 kernel: [9964180.307470] openvswitch: ovs-system: deferred action limit reached, drop recirc action

2021-11-10T00:00:31.476Z|147680|poll_loop|INFO|wakeup due to [POLLIN] on fd 3 (10.10.5.180:42162<->10.10.5.166:6642) at lib/stream-ssl.c:832 (101% CPU usage)
2021-11-10T00:01:07.194Z|147681|timeval|WARN|Unreasonably long 1110ms poll interval (1095ms user, 12ms system)
2021-11-10T00:01:07.194Z|147682|timeval|WARN|faults: 17299 minor, 0 major
2021-11-10T00:01:07.194Z|147683|coverage|INFO|Dropped 5 log messages in last 74 seconds (most recently, 35 seconds ago) due to excessive rate

$ var/log/ovn/ovn-controller.log.1.gz:2021-11-10T08:21:40.110Z|154925|ovsdb_idl|WARN|transaction error: "details":"Transaction causes multiple rows in \\"MAC_Binding\\" table to have identical values (lrp-fbf33f64-0cce-497d-a261-2d3d88e20b80 and \\"::\\") for index on columns \\"logical_port\\" and \\"ip\\". First row, with UUID 4e63d47d-791b-4cc1-ab3c-3d3ac29b5439, existed in the database before this transaction and was not modified by the transaction. Second row, with UUID 6d6281a0-a16e-4fbc-b8b2-da59038f22d5, was inserted by this transaction.","error":"constraint violation"

$ sudo ovn-nbctl show| egrep "^router |lrp-fbf33f64-0cce-497d-a261-2d3d88e20b80"| grep "port lrp" -B1
router 4307456d-3f8b-412c-a784-812f3e73fbfc (neutron-dbf7c13b-751a-41da-b504-09576617213e) (aka ansible-int)
port lrp-fbf33f64-0cce-497d-a261-2d3d88e20b80
$ sudo ovn-nbctl show 4307456d-3f8b-412c-a784-812f3e73fbfc

OVS代码结构

这篇文章不错: https://blog.csdn.net/nb_zsy/article/details/107893255

ovs-vswitchd是OVS的关键组件,和OpenFlow控制器,OVSDB,内核模块交互。
系统核心组件:

  • 通过OpenFlow和外界通讯
  • 通过OVSDB协议和ovsdb-server通讯
  • 通过netlink和内核通讯
  • 通过netdev抽象接口和系统通讯实现了镜像,端口绑定,VLAN功能
  • CLI工具包括ovs-ofctl, ovs-appctl

vswitch模块再分为下列子模块/库

  • ovs-vswitchd,vswitchd后台进程
  • ofproto,ovs桥的抽象库
  • ofproto-provider,用于控制特定类型的OpenFlow交换机的接口 netdev,抽象网络设备的
  • netdev-provider,硬件和OS中的到网络设备的特定接口


openflow controller下发流规则到ovsdb-server/ovs-vswitchd,再经netlink缓存到kernel datapath (可通过ovs-appctl dpctl/dump-flows type=ovs查看缓存的流规则)。以后kernel datapath直接根据这些cache流规则转发数据,如果不知道如何转发又得经netlink去查询(这个叫慢路径)。OVS的设计思路就是通过slow-path和fast-path的配合使用,完成网络数据的高效转发。同理,若网卡支持hw offload,可通过TC将流规则也cache在网卡硬件中以提升性能。

一个OVS网桥管理2类资源:

  • 它所控制的转发平面(datapath)
  • 连接到它的网络设备(物理的或虚拟的)(netdev)

关键数据结构:

  • OVS网桥实现 ofproto, ofproto-provider.h
  • datapath管理 dpif, dpif-provider.h 网络设备管理
  • netdev,netdev-provider.h

更多的ovs代码总体架构和数据结构相关的内容见:openvswitch虚拟交换机架构 - http://blog.sina.com.cn/s/blog_c2d5d2de0102yy02.html

Datapath

从先openvswitch datapath内核模块开始,负责执行数据处理,也就是把从接收端口收到的数据包在流表中进行匹配,并执行匹配到的动作。一个datapath可以对应多个vport,一个vport类似物理交换机的端口概念。一个datapath关联一个flow table,一个flow table包含多个条目,每个条目包括两个内容:一个match/key和一个action.

数据处理时先进ovs_dp_process_packet,它根据mask和key进行匹配查找,没有找到flow, 需要发送到用户态进行慢速匹配。匹配的话有这些actions:

  • OVS_ACTION_ATTR_OUTPUT:获取 port 号,调用 do_output()发送报文到该 port
  • OVS_ACTION_ATTR_USERSPACE:调用 output_userspace()发送到用户态
  • OVS_ACTION_ATTR_HASH:调用 execute_hash()获取 skb 的 hash 赋值到 ovs_flow_hash
  • OVS_ACTION_ATTR_PUSH_VLAN:调用 push_vlan()增加 vlan 头
  • OVS_ACTION_ATTR_RECIRC:在 action_fifos 全局数组中添加一个 deferred_action
  • OVS_ACTION_ATTR_SET:调用 execute_set_action()设置相关参数
  • OVS_ACTION_ATTR_SAMPLE:概率性的发送报文到用户态(与sflow相关)。

究竟何为OVS_ACTION_ATTR_RECIRC

对于openflow中的action ct会生成在datapath中用的action,可能包含两种action: OVS_ACTION_ATTR_CT和OVS_ACTION_ATTR_RECIRC,前者又包含了commit(OVS_CT_ATTR_FORCE_COMMIT),ct_mark(OVS_CT_ATTR_MARK), ct_label和nat(OVS_CT_ATTR_NAT)等信息,后者仅仅包含了recirc_id,用来重新注入datapath后查看到table id,即用来跳转到指定table执行。
见 - ovs conntrack及nat - https://www.jianshu.com/p/b52dc7496dbb

错误"deferred action limit reached, drop recirc action"是在说FIFO cache满了。

ovs upcall & dpif

datapath中根据key查不到流表就要触发upcall过程来查找openflow流表, 见 - https://www.codenong.com/cs109398201/

dpif是ovs-vswitched中的模块,upcall查到了openflow流表,然后dpif将它转化成精确流表并向kernel来下发流表, 见- ovs的upcall及ofproto-dpif处理细节 - https://blog.csdn.net/majieyue/article/details/52844738

do_xlate_actions会将openflow流表转化为精确流表. xlate_output_action根据不同的actions结果走不同的流程,比如当匹配流表的结果的normal时,进入xlate_normal, 在xlate_normal中就会做地址学习。

具体地,通过dp_packet生成udpif_keys,然后来查找datapath flow, struct dpif_upcall代表了一个报文的upcall, 一次处理UPCALL_MAX_BATCH个请求。
接收的数据放到struct dpif_upcall和stuct ofpbuf中,接收时,upcall_receive调用upcall_xlate,进而调用xlate_actions, rule_dpif_lookup_from_table查找到匹配的流表规则,进而生成actions

ct action

见 - ovs conntrack及nat - https://www.jianshu.com/p/b52dc7496dbb

ovs通过ct action实现contrack,kernel datapath会使用kernel的contrack来实现,对于userspace datapath来说由ovs本身来实现。ct action例子如下,见:https://blog.csdn.net/quqi99/article/details/51198098

#添加nat表项
ovs-ofctl add-flow br0 "table=0, priority=50, ct_state=-trk, tcp, in_port=veth_l0, actions=ct(commit,nat(src=10.1.1.240-10.2.2.2:2222-3333))"

//在一个ct里指定多次nat,只有最后一个nat生效,可参考do_xlate_actions中,ctx->ct_nat_action = ofpact_get_NAT(a)只有一个ctx->ct_nat_action 
ovs-ofctl add-flow br0 "table=0, priority=50, ct_state=-trk, tcp, actions=ct(commit,nat(src=10.1.1.240-10.2.2.2:2222-3333), nat(dst=10.1.1.240-10.2.2.2:2222-3333)), veth_r0"

//可以通过指定多个ct,实现fullnat,即同时转换源目的ip。
//但是这两个ct必须指定不同的zone,否则只有第一个ct生效。因为在 handle_nat 中,判断只有zone不一样才会进行后续的nat操作
//错误方式,指定了src和dst nat,但是zone相同,只有前面的snat生效
ovs-ofctl add-flow br0 "table=0, priority=50, ct_state=-trk, tcp, actions=ct(commit,nat(src=10.1.1.240-10.2.2.2:2222-3333)), ct(commit,nat(dst=10.1.1.240-10.2.2.2:2222-3333)), veth_r0"

//正确方式,使用不同zone,指定fullnat
ovs-ofctl add-flow br0 "table=0, priority=50, ct_state=-trk, tcp, actions=ct(commit,zone=100, nat(src=10.1.1.240-10.2.2.2:2222-3333)), ct(commit, zone=200, nat(dst=10.1.1.240-10.2.2.2:2222-3333)), veth_r0"

ct_state连接状态,可能的值如下, 这些flag得结合"+“或者”-“来使用,”+“表示必须匹配,”-"表示必须不匹配。可以同时指定多个flag,比如: ct_state=+trk+new。

  • new 通过ct action指定报文经过conntrack模块处理,不一定有commit。通常是数据流的第一个数据包
  • est, 表示conntrack模块看到了报文双向数据流,一定是在commit 的conntrack后
  • rel, 表示和已经存在的conntrack相关,比如icmp不可达消息或者ftp的数据流 rpl 表示反方向的报文
  • inv, 无效的,表示conntrack模块没有正确识别到报文,比如L3/L4 protocol handler没有加载,或者L3/L4 protocol handler认为报文错误
  • trk, 表示报文经过了conntrack模块处理,如果这个flag不设置,其他flag都不能被设置
  • snat, 表示报文经过了snat,源ip或者port dnat 表示报文经过了dnat,目的ip或者port

ct支持的action如下:

  • commit 只有执行了commit,才会在conntrack模块创建conntrack表项
  • force, 强制删除已存在的conntrack表项 table 跳转到指定的table执行
  • zone, 设置zone,隔离conntrack
  • exec, 执行其他action,目前只支持设置ct_mark和ct_label,比如exec(set_field: 1->ct_mark)
  • alg=<ftp/tftp> 指定alg类型,目前只支持ftp和tftp
  • nat 指定ip和port

代码是如何实现的呢?例如下面这条流表:

ovs-ofctl add-flow br0 "table=0, priority=50, ct_state=-trk, tcp, in_port=veth_l0, actions=ct(commit,nat(src=10.1.1.240-10.2.2.2:2222-3333))"

通过ct_state匹配没经过conntrack处理的报文,一般刚被ovs接收的报文都能匹配到,执行的action是ct,其参数为commit和nat,表示需要创建conntrack表项,同时对报文做snat。

  • 解析ct参数之后,struct ofpact_conntrack用来保存ct后面的参数,struct ofpact_nat保存ct的nat信息,struct ofpact_conntrack->actions用来保存action
  • ovs-vswitchd的xlate模块将上面的ct流表信息转换为本地flow table, 所以slowpath需解析ct
    action:
do_xlate_actions
const struct ofpact *a;
OFPACT_FOR_EACH (a, ofpacts, ofpacts_len)
    switch (a->type)
    //action为CT
    case OFPACT_CT:
        //ofpact_get_CT获取struct ofpact_conntrack及其后面嵌套的action
        compose_conntrack_action(ctx, ofpact_get_CT(a));

ct_clear action & possible patch

这个possible patch是ct_clear action
https://patchwork.ozlabs.org/project/openvswitch/patch/162133301920.596425.5363293849951682159.stgit@wsfd-netdev64.ntdv.lab.eng.bos.redhat.com/

$ git tag --contains 355fef6f2c
v2.16.0
v2.16.1
v2.16.2

我们在使用:openvswitch-switch=2.13.3-0ubuntu0.20.04.1
有了它,The idea the packet nested recirculation path goes to deep and it will not follow the whole chain of actions because of it.

下面这个commit(3594ffab)很可疑,但它也已经在2.13.3里了

ovn-controller处理整个SB DB将logical flows翻译成OF flows,
每个包进来的时候都翻译加重了CPU的负担,另一方面翻译时间过长影响其他进来的包(如dhcp reply)而导致重传
一个新的pinctrl模块来处理进来的包(它不能访问SB DB),但有些OVN actions(like dns_lookup, arp, pu_arp, put_nd)需要访问 SB DB, 就由pinctrl_handler来生成一个local DB
见:https://github.com/openvswitch/ovs/commit/3594ffab6b4b423aa635a313f6b304180d7dbaf7

$ git tag --contains 3594ffab6b4b423aa635a313f6b304180d7dbaf7 |grep 2.13.3
v2.13.3

clone action

clone action引入自: https://mail.openvswitch.org/pipermail/ovs-dev/2016-November/325542.html
见:https://mail.openvswitch.org/pipermail/ovs-discuss/2017-October/045566.html
ovs有两种action,

  • openflow action: 又分standard和extenstion, clone是extenstion action.
  • datapath action: 类似openflow action, 但在datapath层

检查内存与CPU

内存检查如下:

总共用了116.847G
$ sudo cat ps  | awk 'BEGIN sum=0 print $11 " " $2 " " $6/1024 "MB"; sum+=$6 END print sum/1048576 "GB"' | tail -n1
116.847GB
还剩667G内存,内存似乎没问题
$ cat proc/meminfo |head -n2
MemTotal:       791233768 kB
MemFree:        667366732 kB

CPU检查如下, 内存最大的是sosreport:

$ cat ps |awk '/^root/ print $3' |awk 'sum +=$1; END print sum'
385

CPU加起来385,是很大,但也说明不了什么问题,因为可能有多核,还是mpstat更精确。但sosreport里没有mpstat,看uptime吧,看起来没问题. 但uptime也是多核的平均,还是不清楚一个核加起来究竟有没有可能超过100%. 现有sosreport没有这个数据,还得使用mpstat或perf来抓。

$ cat uptime 
 06:35:25 up 118 days,  1:12,  1 user,  load average: 2.29, 1.89, 1.96
$ cat ps |sort -n -k3 -r |head -n4
root     1040259  101  0.0 597928 161032 pts/5   -    06:35   0:16 journalctl --no-pager
root           -  101    -      -     - -        R    06:35   0:16 -
root     1038141 89.6  0.0 1153448 317936 pts/5  -    06:35   0:29 /usr/bin/python3 /usr/bin/sos report --all-logs --name=iVolve --case-id=00320557 --batch
libvirt+ 1210143 57.2  2.1 19030584 16970708 ?   -    Oct29 10572:23 /usr/bin/qemu-system-x86_64 -name

$ grep -r 'ovn-controller' sos_commands/systemd/systemctl_status_--all  |grep PID
   Main PID: 198508 (ovn-controller)
$ ls proc/198508/
mountinfo

ovn-controller可能造成cpu高的因素

ovn-controller主要将SB DB中的逻辑流转化成实际流表。之前是每个进来的包都转化可能转化过程很长影响其他进来的包(如dhcp reply),所以又加了一个pinctrl thread专门来做转换的事(不和SB DB打交道了,但又有一些和SB DB打交道的action又有一个专门的地方做,见- https://github.com/openvswitch/ovs/commit/3594ffab6b4b423aa635a313f6b304180d7dbaf7)

这里提到即使用了pinctrl,但GARPs包太多也会造成pinctrl高CPU,见: https://mail.openvswitch.org/pipermail/ovs-dev/2019-August/362210.html

这个pdf不错, 它提到了更多高CPU的可能 - OVN issues in the field - https://www.openvswitch.org/support/ovscon2019/day1/1436-OVSCON-Nouman.pdf

  • ovn-remote-probe-interval=5, 连SB DB >5 seconds时造成高CPU
  • inactivity_probe=5, 连ovsdb-server造成高CPU
  • ovn-openflow-probe-interval=5, 连openflow connection造成高CPU
  • pinctrl thread, 处理dhcp packet-ins造成高CPU的问题
  • lookup_arp/lookup_nd,处理GARPs包,仅仅需要时才通过新添加的lookup_nd发
  • ovsdb-server read-only, 当ovsdb-server vip切换之后由于bug导致 到SB DB的connection read-only了
  • MAC binding update failure, 由前一个read-only的例子造成
  • VRRP failure, VIP切换后VM发GARP, pinctrl thread更新local mac_binding cache, main ovn
  • controller thread discards the learnt mac without updating if it’s older than 1 second.
  • conjunction flow issue, 导致大量的OF rule

是上面第7条造成的吗?- https://bugs.launchpad.net/charm-neutron-api/+bug/1921986

ovsdb-idl

void
bridge_init(const char *remote)

    idl = ovsdb_idl_create(remote, &ovsrec_idl_class, true, true);
   unixctl_command_register("qos/show-types", "interface", 1, 1)
    ...
   unixctl_command_register("bridge/dump-flows", "bridge", 1, 1,)
    lacp_init(); // register command lacp/show
    bond_init(); // register bond commands
   cfm_init();
   bfd_init();
   ovs_numa_init();
   stp_init();
   lldp_init();
   rstp_init();
    ifnotifier = if_notifier_create(if_change_cb, NULL);

ovsdb-idl用于和ovsdb打交道,ovs-vswitchd首先创建到ovsdb-server类型为OVSDB-IDL的连接,IDL是接口定义语言(Interface Definition Language)的缩写;OVSDB-IDL维护一个数据库在内存中的复制,它发起RPC请求到一个OVSDB数据库服务器并解析响应,转换JSON数据为客户端容易使用的数据结构,OVSDB IDL定义在ovsdb-idl.h。

这个错误日志是和insert mac_binding相关的,ovsdb_idl用于和ovsdb打交道,在ovsdb_idl用于insert的只有ovsdb_idl_insert_row方法,也只有下列路径调用这个方法:

ovsdb_idl_loop_run -> ovsdb_idl_run -> ovsdb_idl_process_msg -> ovsdb_idl_process_response -> ovsdb_idl_db_parse_update -> ovsdb_idl_db_parse_update__ -> ovsdb_idl_process_update -> ovsdb_idl_insert_row

而在ovn中和mac_bindings打交道的函数应该是run_put_mac_bindings, 它应该再调用上面的ovsdb-idl:

main -> $ovn/controller/pinctrl.c#pinctrl_run -> run_put_mac_bindings

下面的commit中2e28b8e5d有点像 -

$ git log v20.03.1..master --grep='binding' --oneline --no-merges controller/pinctrl.c
2e28b8e5d pinctrl: Fix segfault seen when creating mac_binding for local GARPs.
e3a398e91 controller: Add ipv6 prefix delegation state machine
8f1c38ef4 pinctrl: fix IP buffering with connection-tracking

抓ovn-host的call strace

ovn-host没有dbg后缀的内建符号表,但launchpad上有 - https://launchpad.net/ubuntu/focal/+source/ovn
注:UCA的符号表要自己编译(当然现在用的是UA的ovn-host包), 见: https://blog.csdn.net/quqi99/article/details/50745614

# https://wiki.ubuntu.com/DebuggingProgramCrash#Debug_Symbol_Packages
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/debuginfo_debs.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C8CAB6595FDFF622
sudo apt update
sudo apt-cache policy ovn-host-dbgsym
sudo apt install openvswitch-dbg libc6-dbg -y
wget http://launchpadlibrarian.net/468769213/ovn-host-dbgsym_20.03.0-0ubuntu1_amd64.ddeb
sudo dpkg -i ./ovn-host-dbgsym_20.03.0-0ubuntu1_amd64.ddeb
# sudo apt install ovn-host-dbgsym=20.03.2-0ubuntu0.20.04.2
sudo apt install linux-tools-`uname -r` linux-tools-common -y
sudo apt install sysstat -y
git clone https://github.com/bboymimi/easy-flamegraph.git
cd easy-flamegraph
sudo tail -f -n0 /var/log/ovn/ovn-controller.log | awk '/wakeup due to/ match($11, /\\(([0-9]+)%/, col) && col[1]>99  system("sudo /usr/bin/perf record -p `pidof ovn-controller` -g --call-graph dwarf sleep 5"); system("mpstat -P ALL"); system("sudo pkill tail"); ' > tailresult.log 2>&1
sudo ./gen-flamegraph.sh -i ./perf.data
sudo tar -czvf perf-output.tar.gz ./perf.data perf-output/

注意:上面的awk + match是work的,但下面的awk + substr是不work的,不知道为什么
substr($11,2,length($11)-2) 相当于将 (100% 变成 100
# awk从field中提取数据 - https://unix.stackexchange.com/questions/468010/awk-extract-string-from-a-field
sudo tail -f -n0 /var/log/ovn/ovn-controller.log | awk '/wakeup due to/ && substr($11,2,length($11)-2)>99  system("mpstat -P ALL"); system("sudo pkill tail"); '

Reference

[1] https://blog.csdn.net/nb_zsy/article/details/107893255
[2] https://blog.csdn.net/quqi99/article/details/111831695
[3] https://xiaohutou.github.io/2018/06/01/ovs-code-note-1/
[4] OVS内核KEY值提取及匹配流表代码分析 - http://ry0117.com/2016/12/24/OVS%E5%86%85%E6%A0%B8KEY%E5%80%BC%E6%8F%90%E5%8F%96%E5%8F%8A%E5%8C%B9%E9%85%8D%E6%B5%81%E8%A1%A8%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/
[5] http://blog.sina.com.cn/s/blog_c2d5d2de0102yy02.html

以上是关于OVS代码结构(by quqi99)的主要内容,如果未能解决你的问题,请参考以下文章

OVS代码结构(by quqi99)

OVS代码结构(by quqi99)

如何实现OpenStack STT隧道(by quqi99)

set up ovn development env (by quqi99)

set up ovn development env (by quqi99)

set up ovn development env (by quqi99)