OVS网桥建立和连接管理

前言

Open vSwitch作为一个被广泛应用的虚拟交换机,除了完成流表匹配、数据转发等功能外,其自身对网桥的创建更新和连接管理也尤为重要,这是其高性能的保障。本文按照源码的行文思路,从网桥的创建、配置、更新到主动、被动连接管理,依次进行梳理学习。

重要结构体:

本文将按照下图主线进行分析。其中,会提到一系列有关联的结构体bridge、ofproto、connmgr、ofconn、rconn、vconn,其附属关系也如图中所示:

bridge、ofproto、connmgr等前三个结构体都是对于一个网桥而言,且分别和网桥都是一对一关系,互相之间也是一对一关系。从connmgr开始是和连接实例相关,一个connmgr对应多个ofconn(如一个网桥连接多个控制器,则需要建立相应多个ofconn实例;和ofctl等工具连接时也要建立ofconn实例);一个ofconn对应一个rconn;一个rconn对应多个vconn(因为和控制器建立连接外,可能还会实现snoop功能,即监测并复制交互的of消息,则一个rconn会对应一个vconn和一个vconn结构体类型的数组monitor。这样看来,vconn并非连接两个实体,可称为虚拟连接)。

每个结构体具体含义在后面分析中会逐一说明,这里只需要对其有一个大体关系的了解,方便后面分析中保持一个清晰的思路。

一 从Main开始,创建ovs-vswitchd服务端

为了保障思路的完整性,从main函数快速引入(ovs-vswitchd.c),然后进入到网桥建立阶段。

1、 main函数中,先通过参数解析得到unixctl_path和remote:

ovs有两大进程vswitchd和ovsdb-server,remote用于这两个进程的IPC,即进程间socket通信。remote其实是一个socket文件地址,由 ovsdb-server服务端绑定监听时产生,作用类似于网络socket的Ip+Port地址,remote格式如unix:/usr/local/var/run/openvswitch/db.sock。后面创建网桥时会使用。

2、 通过下面命令创建vswitch unixctl服务端,个人理解这个是用于以后ovs vswitch的管理工具,如ofctl等连接的服务端:

3、 同时会启动守护进程,最终一些守护进程会退出:

4、 之后,初始化网桥,主要是初始化网桥bridge模型,通过remote地址,来从OVSDB服务端获取配置信息来实现网桥初始化配置:

这里会遇到一个IDL概念。关于IDL,即接口定义语言,一般用于RPC(远程过程调用)。这里可以理解为用于vswitch和OVSDB服务之间的交互调用,一些信息就需要不断从OVSDB获取,如网桥信息。

5 最后,则通过while循环进入正式工作状态(主要调用函数bridge_run(),进入网桥建立阶段)。这里会反复循环,直到服务需要停止时,才退出ovs-vswitchd服务端。

二 bridge_run():网桥建立、配置和更新(vswitchd/bridge.c)

OVSDB包含三级结构,表table、记录record和属性columm。例如,在bridge表中,每一个record就是一个网桥实例的信息集合,其中uuid和datapath_id等就是这条record的属性。Open_vSwitch表(ovs信息表)是肯定有的,属于根表,一般有且只有一个record,属性有uuid、版本、系统版本和网桥bridges(当无网桥时则为空[])。当然,这些都是可以通过ovs-vsctl list 表名(如Open_vSwitch、bridge等表)来查询其具体内容。

在ovs源码中,以结构体名为ovsrec_开头的就是表结构体,如果实例化了表就是一项纪录record。因此,在bridge_run()里,通过ovsrec_open_vswitch_first(idl)和接口idl得到根表open_vswitch(*cfg,即结构体ovsrec_open_vswitch)。其实一个cfg就是跟表的一个record,cfg里的成员也就是属性columm(比如版本属性)。

在bridge_run()中,主要工作依次如下:

1、初始化网桥的ofproto库:主要调用bridge_init_ofproto()->ofproto_init(),且这个函数只执行一次,通过静态变量initialized控制。

这里重要的是,注册了变量ofproto_dpif_class(结构体ofproto_class,ofproto-dpif.c),这是ofproto的执行函数库的集合。注册函数为ofproto_class_register(&ofproto_dpif_class),将ofproto_dpif_class赋值给ofproto_classes[i]。个人理解,i只可能为0(n_ofproto_classes只能为1),即ofproto_classes[0]就是ofproto_dpif_class(即ofproto的函数库)。ofproto_dpif_class很有用,比如含有ofproto的运行、流表项的插入等操作函数,之后会用到。

当然,注册了ofproto库,即ofproto_dpif_class之后,才可以创建ofproto。结构体ofproto代表一个OpenFlow交换机,之后会在bridge_reconfigure()中调用ofproto_create()创建ofproto。

2、运行网桥:bridge_run_(),主进行两项工作,第一是通过函数ofproto_type_run(type)让每一个类型的datapath运行,第二是调用ofproto_run(br->ofproto)让每一个bridge的ofproto运行,ofproto的运行细节在后面三中着重分析。

3、网桥配置: bridge_reconfigure()(bridge.c):

主要将ovsdb信息同步过来,如对新网桥创建、controller连接等。但进入这个函数的前提是idl_seqno变化(ovsdb改变或是和ovsdb重连接都会导致idl_seqno变化)。

在bridge_reconfigure(),主要工作如下:

  • 先对跟表ovs_cfg(即open_vswitch表)的一些属性做了设置,比如属性other_config的最大流设置为20万。
  • 更新网桥,包括添加新的网桥:在add_del_bridges()函数里,删除名字和类型变化的网桥(bridge_destroy(br)),通过跟表ovs_cfg->cfg->bridges[i]来一一建立没有建立的新网桥(bridge_create函数),即将新网桥实例插入变量all_bridges哈希表中。
  • 上面是更新网桥,这里需要更新ofproto,包括ofproto的添加:

    对网桥类型发生变化的网桥的ofproto进行删除(bridge_delete_ofprotos()函数),然后逐一对那些网桥还不存在ofproto的网桥创建一个ofproto,即通过调用函数ofproto_create()。注意,结构体ofproto代表一个ovs网桥,其含有of表、meter表、port、连接管理connmgr等属性,相比bridge结构体,ofproto属于更切实的一个ovs网桥的属性集合,这个可以在下面初始化ofproto各个属性过程中感受到。

    在ofproto_create()函数中,会进行新的ofproto创建过程和属性初始化:

    先根据datapath类型找到对应class,对于ofproto,其class就应该是ofproto_dpif_class;然后,通过class->alloc函数创建一个ofproto,并且后面对ofproto的各个属性进行初始化,例如初始化属性ofproto->ofproto_class = class(class即ofproto_dpif_class)、初始化属性ofproto—>connmgr(通过connmgr_create()函数)、初始化ofproto的meter表等。 其中,ofproto->ofproto_class->construct(ofproto),实现ofproto_dpif_class库中的函数construct()对新的ofproto的进一步构建,包括255张表的初始化和一些流的添加,比如miss流。

  • 回到函数bridge_reconfigure(),继续之前思路,调用函数bridge_configure_remotes(),实现网桥与控制器建立socket连接、监听和服务开启相关的操作。在最后,也调用了bridge_run_()。

    在函数bridge_configure_remotes()里,主要调用ofproto_set_controllers()->connmgr_set_controllers()->add_controller()进行控制器连接,深究之后,最终会在add_controller()里通过函数ofconn_create()、rconn_connect和rconn_connect()进行连接创建和连接尝试。注意,这里面会用到char类型的target变量,即控制器的ip:port,最终会传给rconn->target,与rconn->name关联。

    此外,还检查网桥是否存在snoop(相当于网桥的一个监听服务,如s1网桥,则通过ovs-ofctl snoop s1 连接这个snoop socket服务,然后网桥会监听其和控制器之间的of消息)。存在,则需要通过ofproto_set_snoops()函数开启这个snoop监听端口,从而复制每一份of消息。打开的监听属于被动连接pvconn,最终调用listen函数打开监听。(/usr/local/var/run/openvswitch下可以看到s1.snoop文件,即snoop的socket文件)

  • sfow、stp、网桥端口port、接口iface和qos等、mac配置。

4、 网桥配置分析完后,回到最初的bridge_run()中,继续后面的思路。其最后主要进行和状态更新相关的操作,如统计数据更新的时间间隔>=5000ms、接口状态更新、更新状态提交给数据库等。

三 ofproto_run():更为切实的网桥实例(ofproto/ofproto.c)

之前提到过,结构体ofproto代表一个ovs网桥,其含有of表、meter表、port、连接管理connmgr等属性,相比bridge结构体,s属于更为切实的一个ovs网桥的属性集合体。在之前bridge_run()中提到第二个工作是bridge的运行(即bridge_run_()),在bridge运行bridge_run_()中就需要完成每一个bridge的ofproto的运行(ofproto_run()),以这个为主线继续展开分析。

在ofproto_run()中,主要工作如下:

1、首先,让ofproto运行一次,即:

p即为传递进来的一个ofproto实例。命令实质调用的函数为ofproto_class结构体的run()(ofproto-dpif.c):

  • 主要将ofproto_dpif->pins队列里待发送的packet-in消息发给控制器(调用函数connmgr_send_packet_in(ofproto->up.connmgr, pin)),跟踪下去,最终还是调用rconn进行发送。
  • 促使port、netflow、sflow等dpif运行一次,如port_run(ofport)、dpif_sflow_run(ofproto->sflow)。
  • 一些序号更新、对过期流表项进行删除等(rule_expire(rule_dpif_cast(rule))。

2、run_rule_executes(p)对ofproto->execute_rule队列进行执行,即完成流表项插入等操作。

因为在flowmod等操作时,例如,会调用插入表项函数do_add_flow(),进而会调用ofopgroup_submit(group)->ofopgroup_complete(group)->guarded_list_push_back(&ofproto->rule_executes,&re->list_node, 1024)),push到rule_executes队列中,因此,这里需要调用函数run_rule_executes()完成队列中插入等操作的真正执行工作,具体调用关系为ofproto->ofproto_class->rule_execute(e->rule, &flow, e->packet);

3、和表项驱逐相关的操作、port更新等。

4、根据ofproto状态,调用ofproto的连接管理器connmgr进行相应处理,分为三个状态,这里以状态S_OPENFLOW为主,调用函数connmgr_run(p->connmgr, handle_openflow),即主要进行OF消息管理。

到这里,网桥的建立、更新等就基本完成,从这里之后,就主要和连接管理相关。

四 connmgr_run():实现连接管理(ofproto/connmgr.c)

connmgr顾名思义(connect manager,连接管理器),主要完成OVS网桥的连接管理。每一个网桥ofproto都有一个connmgr实体来管理连接。connmgr主要完成两种连接管理,一种是主动连接(即与控制器连接,作为客户端),一种是被动连接(主要提供ofctl等工具连接请求服务和snoop服务,作为服务端)。connmgr结构体主要内容见下面,all_conns是主动连接,services和snoops等提供被动连接服务。

1、处理主动连接。对连接管理器的每一个主动连接ofconn(理解为一个of连接)进行处理,即实现与控制器的主动连接处理,包括of消息接收处理等,具体调用函数ofconn_run()如下:

ofconn_run()比较重要,牵扯到网桥和控制器连接管理与of消息接收、发送处理等,之后会着重深入分析。

2、 连接管理监视器: ofmonitor_run(mgr),这方面没有深入研究;

3、 对mgr接收到的每一个被动连接请求,创建rconn实例和ofconn实例(ofconn创建的是被动连接服务类型OFCONN_SERVICE)来提供连接服务。

4、 尝试接收connmgr管理的每一个snoop的连接请求,如果成功,通过函数add_snooper()实现snoop功能。

snoop是connmgr提供的一个of消息监听服务,属于被动连接服务。如网桥s1,则可以通过命令ovs-ofctl snoop s1发起连接connmgr的snoop服务,connmgr一旦接受到一个snoop连接请求,则通过add_snooper()函数进行具体动作实现:

监听思路很简单,就是得到监听服务请求后,然后主动监听和控制器的连接并复制交互的of消息。具体为:循环检查和控制器的主动连接集合mgr->all_conns中得到每一个ofconn,然后判断获取最高级别的ofconn实例(ofconn连接控制器有时分为master,slave等,因此ofconn级别不同)。最后将选出的ofconn赋值给best,并调用rconn_add_monitor(best->rconn, vconn),rconn有个属性成员数组monitors(vconn结构体),完成和控制器交互of消息的监听复制工作,因此rc->monitors增加一个监听实例vconn即可:

五 ofconn_run(): 处理与控制器的连接 (connmgr.c)

connmgr管理主动连接和被动连接,主动连接是根据前一个提到的函数ofconn_run()进行实现,其主要完成网桥与控制器连接的状态转移管理和of消息的接收处理等。

在ofconn_run()中主要实现的工作依次如下:

1、 首先发送储存在pin中的packet_in消息(和3中a的第一条貌似重复,没深究其关系)。跟踪下去,会发现利用ofconn->rconn->vconn实现消息给控制器的发送。

2、 运行rconn,完成网桥与控制器连接的实时状态监测和状态转移管理,连接正常时还负责消息的发送。rconn_run()会在六中着重分析。

3、 利用函数rconn_recv(ofconn->rconn)尝试从rconn中接收of消息,如果存在,则调用函数handle_openflow(ofconn, of_msg)对of消息进行处理,如packet-in,flowmode等。

这里限制在最多50条消息内的循环处理(一个ofconn对应一个控制器,因此防止一个ofconn接收控制器消息次数和时间过长,影响别的ofconn对其他控制器等连接的消息处理),如果没有消息则直接退出循环,结束此ofconn的连接处理。消息的处理则是通过传入参数ofconn和消息msg,调用函数handle_openflow()实现of消息处理,这里就是OpenFlow协议那一套机制了。

注:就像之前所说,结构体关系为connmgr->ofconn->rconn-vconn。不仅connmgr提供主动和被动两种连接,连接实现的ofconn结构体实例也会分两种类型(),如下:

  • OFCONN_PRIMARY:和控制器的连接,可以对应connmgr->all_conns
  • OFCONN_SERVICE:作为服务端被动连接,如等待ovs-ofctl工具连接请求,一般以P开头(ptcp, pssl, punix)表示被动连接。 可以对应connmgr->services。

六 rconn_run(): 消息发送和状态转移管理 (rconn.c)

最后一部分,就是对于连接的状态进行相应检测和管理的工作,主要集中体现在rconn_run()函数中:

1、 消息发送:每个连接rc->vconn实例的队列中放置着待发送的消息,则通过调用vconn_run()->vconn->class->run函数完成队列中消息发送工作。class->run指vconn执行函数库的run方法(vconn_stream_run()->stream_send())。其实,深究下去发送动作最终需要调用 vconn_stream_run()->stream_send() 、 stream->class->send等,这里牵扯到更底层的stream连接管理结构体,最终对应到相应TCP、UNIX连接等原始的socket连接上。

2、 监测连接,完成of消息发送和接收功能,发送那些负责监测赋值of消息的连接实例(rc->n_monitors[i])队列中的消息,即将监测控制器连接后,复制得到的of消息发送回ofctl客户端;此外,需要将监测复制得到的of消息接收到相应rc->n_monitors[i]实例中。

3、 状态转移和管理:通过循环保证连接的状态稳定,状态稳定不再转移时则退出本次状态管理:

rconn代表网桥和一个控制器或是和管理工具实例的连接实例,但需要用rconn->state表征其连接状态。这里状态主要分为七种,VOID(无连接)、BACKOFF(退避尝试重连接)、CONNECTING(正在连接)、ACTIVE(连接活跃)、IDLE(连接空闲)、RECONNECT(重新连接)、LISTENING(监听)。七种状态会进行状态转移,这里面具有较为复杂的转移机制(主要根据是否接收消息和超时时间进行状态转移),其关系全部体现在文件lib/rconn.c中。 其中,只有ACTIVE(连接活跃)、IDLE(连接空闲)属于连接完全建立状态,可以进行消息的正常交互。

这里说明两点:1 每种状态下都有相应状态处理函数,命名为run+状态名,如最为重要的run_ACTIVE()函数(实现超时进入IDLE状态功能);2 状态转移时调用函数state_transition(rc, S_状态)完成状态转移。这样,基本自己琢磨rconn.c文件中这些函数和其功能,则可以弄清OVS连接状态转移机制。

注:上面提到vconn结构体,为了不打破阅读连续性没有展开说明,这里简单补充。每一个rconn实例表示一个网桥和控制器或是管理工具之间的连接,连接中消息的传递是依赖于rconn的属性成员vconn完成,例如消息的接收与发送等。下面是vconn结构体:

  • vconn_class:可以理解为vconn的方法函数库,有连接、发送、接收等具体方法函数, 具体可以见lib/vconn-stream.c中宏定义(#define STREAM_INIT(NAME) ...)。
  • state:表征连接状态,分为断开、正在连接、等待发送hello、等待接收hello和连接建立等状态,其中只有连接建立状态才算正常连接且可以消息交互。

后记

上文基本梳理出网桥的建立、配置、更新和主被动连接的管理等思路,这可以为OVS的进一步开发提供参考。当然,一些细节还没有仔细探究,所以一些理解错误或是不到位的地方,希望大家多指正。

作者简介:

晏思宇,2014/09-至今,北京邮电大学信息与通信工程学院未来网络理论与应用实验室(FNL实验室)研二,主要研究方向为SDN、Open vSwtich等。

个人博客www.yansy.xyz

--------------华丽的分割线------------------
本文系《SDNLAB原创文章奖励计划》投稿文章,该计划旨在鼓励广大从业人员在SDN/NFV/Cloud网络领域创新技术、开源项目、产业动态等方面进行经验和成果的文字传播、分享、交流。有意向投稿的同学请通过官方唯一指定投稿通道进行文章投递,投稿细则请参考《SDNLAB原创文章奖励计划》


  • 本站原创文章仅代表作者观点,不代表SDNLAB立场。所有原创内容版权均属SDNLAB,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用,转载须注明来自 SDNLAB并附上本文链接。 本站中所有编译类文章仅用于学习和交流目的,编译工作遵照 CC 协议,如果有侵犯到您权益的地方,请及时联系我们。
  • 本文链接https://www.sdnlab.com/16144.html
分享到:
相关文章
条评论

登录后才可以评论

晏思宇 发表于16-03-04
10