协议
协议文档网址:
https://github.com/ndsc-mimic/Engine/tree/master/libraries/netip/docs
版本1.4.1
datapath_id
datapath_id 是网络设备的唯一标识(交换机?);
module_id
module_id 是backend和module的唯一标识;
xid
xid 是message的标识符之类的,验证配对;
Ryu控制器
工作流程
首先从由于Ryu中的app工作方式主要是通过event为基础的,应用组件之间通信通过事件机制完成。比如底层的协议解析模块解析报文之后,生成对应的报文事件,然后分发到各个监听该事件的监听函数。并且采用的event的订阅发布模式,所以详细看了相关代码。
主要模块
1、event、handler的挂接方式(需完善)
由于Ryu中的app工作方式主要是通过event为基础的,应用组件之间通信通过事件机制完成。比如底层的协议解析模块解析报文之后,生成对应的报文事件,然后分发到各个监听该事件的监听函数。并且采用的event的订阅发布模式,所以详细看了相关代码。
参考网址:(比较详细)
http://blog.csdn.net/yugongpeng_blog/article/details/45842935
1) 整体流程
i. 注册handler及event订阅初始化:两个注册
•main()中load_app,context(待 了解),以及关注的instantiate_apps(app实例化)。在manager.py中;
•注册app,在SERVICE_BRICKS(ofp_handler(ofp_event)也在其中)里面,然后注册实例(实际上是注册处理函数),register_instance在handler.py中;
•首先利用inspect.getmembers获取app中method(方法函数),用has_caller函数筛选出有caller属性(详见后文中的@set_ev_cls修饰符的使用)的method(即有处理函数功能的函数),将处理函数与其对应的事件添加到各自app的handlers的属性中;
•接着,update_bricks,找出event的source发布者lookup_service_brick_by_mod_name,实际就是ofp_event(因为它里面定义了OpenFlow的基本事件),但是ofp_handler类的名字name实际就是ofp_event(自己改的),所以就是找出了ofp_handler这个app(它实际就是一个基础的默认运行的app),它就是基本OpenFlow事件的发布者;接着注册observer:就是把刚刚说的那些有处理函数的app的名字和对应的事件添加至ofp_handler的observers属性中(ofp_handler继承于ryuapp,所以也继承了该属性);
总而言之,主要就是两个注册:一个是App内部的处理函数在App内部的event_handlers的属性中注册(得益于@set_ev_cls的使用使得处理函数区别于其他的函数);一个是App(名字)本身在ofp_handler的observer属性中的注册(订阅);
除了在初始化中注册,也可以通过observe_event来注册;
ii. 消息推送:ofp_handler作为app运行
一再提到ofp_handler实际就是一个app,所以也会运行;
•它的start()改了,起的是OpenFlowController()(使用call使其可调用);
•OpenFlowController()调用server_loop,循环监听init()中的端口,检测到switch的连接,就会起一个datapath_connection_factory,循环接收该switch的数据message,转化为event(msg_to_event),运行send_event_to_observers(datapath类的self.ofp_brick的定义可以看出是self.ofp_brick就是ofp_handler),这个函数先得到ofp_handler的observer属性的内容(就是初始化时注册的app和对应的event),也就是找到该event的observer订阅者app, 然后用send_event将该event添加put进app的events队列(每个app都有一个event_loop,检测self.events队列是否为空);
总而言之,这一步完成了消息源的event推送工作(推送给谁已经在第一步注册时确定了)
iii. 消息处理
上步讲完相应的事件已经推送到了各个app的self.events中;
•首先所有app继承于app_manager的RyuApp中,有个event_loop,就是self.events队列的处理循环:
•每个app处理时,会检测self.event_handler(第一步中已经注册过了),找出每个event的处理函数,扔给他们处理;
至此,完整的event订阅、推送、处理完成。
iv. 总结
observer是以app为粒度的,也就是以app的名义来订阅某些事件的,因为事件的推送显然只能以app为粒度进行推送(不可能以method为粒度);推送到app后,由app内部已注册的handler再进行处理。
2) 涉及模块
i. python 的inspect模块详细介绍:
实际就是检查功能,如遍历检查class中method,属性等等;
http://blog.csdn.net/yugongpeng_blog/article/details/45842935
ii. @set_ev_cls修饰符的原理及使用
•使用:普通的Ryu app通过set_ev_cls装饰器来自定义事件的处理函数,尤其是Packet_In事件,但是对于控制器与交换机建立连接并且彼此交换配置信息时的处理函数是通过set_ev_handler来定义的。在OFPHandler中定义了这些处理函数。上述内容参考网址:
http://blog.csdn.net/yugongpeng_blog/article/details/45843105
•原理:由handler中的set_ev_cls函数可以看出,被@set_ev_cls修饰的method都会加上一个caller的属性(这个属性就是为了日后筛选出所有的处理函数,为他们注册(注册事件和处理函数(的名字)),也就是添加至该app(就是该method所属的app)的event_handlers属性中),便于日后使用(每个app都有一个event_loop,它会检测该app的events属性(即事件队列)不为空时,他就会从自己的event_handler属性(刚刚添加了)中查找该event对应处理函数进行处理;至于app的events事件队列中的事件谁推送的,那就是消息推送者的事了send_event_to_observers,ofp_handler就是消息源,他负责事件的推送工作,至于推送给谁,这就是由ofp_handler的observer属性得到的(它继承于app_manager.ryuapp,所以继承了该属性),而这个属性也就是初始化时注册实例时的update_bricks中完成的(会将事件和该app(拥有该event的处理函数的app)添加至ofp_handler的observer的属性中);
iii. handler
见@set_ev_cls
iv. observer
ryu-shim
主要模块
CoreConnection
1、zmp相关文章
官网: http://zguide.zeromq.org/page:all
ZMP的Router/Dealer模式
http://blog.csdn.net/kobejayandy/article/details/20163527
ZeroMQ之push/pul
http://lbxc.iteye.com/blog/1533654
•PUHS: 用于发送消息,定义一个zeromq的socket实例,用于send msg
•PULL: 用于接收消息,recv msg
2、解封
解封涉及netIDE的内部协议,全都位于netip.py文件中。
1) netIDE_decode_header
将接收的msg按照特定的标准NetIDE_Header_Format进行解封(事先验证报头长度),得到头部信息,并返回;
2) handshake的协议协商(backend和shim之间的协商)
•protocol和version各自都一个字节,所以count+2,依次偏移;
•Core发来的(即backend的handshake)message中依次比较protocol(主要是南向协议)和version,若在shim所在的controller的支持范围,则添加;否则更新;如果没有,shim会回一个自己支持的协议及版本。
3) 封包以及解封包都由struct.py中的pack,unpack函数完成;
4) netIDE_encode
•会将待封装的元组翻译一下,如将type的HELLO消息写为0x01等,形成value(包含version, type_code, length, xid, module_id, datapath_id, msg信息的数组),然后由pack函数进行最后的封包工作;
•格式为netIDE_encode(type, xid, module_id, datapath_id, msg),对比最后的报文可以看出,传入不需要netIDE_version字段以及length字段,其中verison统一的,而length可以计算msg,所以都不需要传入,在函数内部生成即可。
5) OpenFlow消息的转发
•首先清理xid的database:由于存储xid信息时会存入time,以此作为清理的标准(超时的删);
•替换xid:用到了bytearray函数:bytearray应该是将数据按照字节划分,生成数组,所以原来16bit的就会生成两个,数组中就会有两个元素,所以原数组中的of_array[4][5][6][7]这几位刚好是xid的4个字节(OpenFlow协议中version-1字节,type-1字节,length-2字节)替换为new_xid;
•生成新的message发给对应的datapath;
主要有两个函数需要关注:ofproto_parser.header(用于解析OpenFlow的头部)以及datapath部分;
6) xid部分
Ryu-Shim
1、self
self应该是ryu-shim中最重要的部分,因为为了对switch透明无感,ryu-shim需要具备controller的功能,所以ryu-shim是继承了app_manager.ryuapp的类的,也就使其具备ryu-controller的功能;
•作为传入值传入Core-Connection(也就是Core-Connection中待传入的controller)
•在传入Core-Connection之后作为controller,但是在core-connection中由self.controller=controller,作为core-connection函数中的self的controller属性了,所以往后的core-connection函数中的self.controller实际就是ryu-controller,拥有ryu-controller的所有性质;例如self.contoller.swicths.
2、_handle_ConnectionUp
3、版本协商
3、add_flow
4、feature request
并不会等backend来问,而是一旦收到backend的NETIDE_HELLO消息就自动会为新的client(backend)重新获取一份feature)
•传入值为module_id,同处理OpenFlow消息一样,会储存old xid,module_id,替换为new xid (与交换机的通信都有这一步);
•然后向他所有的switch(datapath)发送该请求;
步骤流程
1、 运行RyuShim
1) 定义相关变量
如CORE_IP, CORE_PORT,shim_id(即为Core的IP及端口号)等;其实最主要的是self,也就是继承了ryu的app-manager部分(相当于是控制器了,这样就可以完成控制器部分的工作:向switch收发消息并处理)
2) 执行CoreConnection
传入值主要为CORE_IP, CORE_PORT;
3) 注册交换机(连接)及OpenFlow版本协商
i. 注册处理函数
_handle_ConnectionUp处理函数被@set_ev_cls修饰符修饰,表明_handle_ConnectionUp成为EventOFPSwitchFeatures事件的处理函数。
ii. 注册监听者(监听事件)
首先调用observe_event为ryu-shim的这个对象实例注册为EventOFPPacketIn事件的observer(监听该事件);
iii. 获取事件后运行处理函数
• 然后得到msg(由获取的事件的msg属性直接得到,msg = ev.msg,再由msg得到datapath以及version版本。然后进行版本协商,另外若datapath不在对象实例的switch中则添加进去(索引为datapath.id);
• 然后设置match,actions,用add_flow函数向该datapath发送封装好的消息,添加流表(install the table-miss default behavior???)(match,actions等传入值);
至此完成switch连接的相关准备工作(获取了交换机feature及版本等)。
4) 主循环(监听交换机的event)
i. 注册监听者(监听事件)
同上,首先调用observe_event为ryu-shim的这个对象实例注册为所有的事件的observer(监听所有事件);
ii. 主循环函数(循环条件:ryu-shim为运行态且event队列不为空)
•获取事件ev,及其状态state,根据ev,state得到注册过该事件的处理函数(get_handlers),然后让每个处理函数去处理它;
•由ev可以获得msg,datapath,buf等信息,然后由xid获取旧的xid以及module_id(之前Core,backend发来时替换的,并且存储了以new_xid为索引的数组,保存有old_xid和module_id);然后将xid替换回来set_xid;
•准备发送:(不会将交换机的HELLO消息发给Core,backend,直接不管了);然后有了OpenFlow,data,以及NETIDE消息头部所需的type(OpenFlow消息),datapath和module_id,就按NETIDE头部格式进行封装(netIDE_encode),然后发送;
注意:还会判断该消息是否属于异步消息(类似packetin消息),若是则将xid直接置为0(既不会去查找替换xid,也不会从OpenFlow中提取出xid作为netIDE 的xid),否则(是同步消息)由OpenFlow中的xid替换回原先的xid。
2、 CoreConnection
1) __init__接收相关传值;
2) run
i. 循环接收Core的message
•socket的while循环语句,监测接收Core的message,若收到并符合要求(get_multipart_message)则执行handle_read,传值为msg;
•结束后继续该循环;
涉及zmq相关知识
ii. 解封分析
•handle_read 接收msg,解封,得到头部信息decoded_header;
•由获取到的decoded_header中的length字段(payload的size)得到message_length;通过msg以及协议中头部长度即可获取真正封装的data部分(payload)即message_data;
(message_data=msg[header_size : headsize+message_length])
•分析报文头部 (事先准备,检查netIDE的版本是否一致)
a) 若是NETIDE_HELLO消息(实际是与backend的之间的HELLO,由backend先发出)
•检测是否连接交换机(或mininet);
•然后协商出公共支持的netIDE 的版本;因为第一次连接,再次代入message_length以及message_data到handshake的协议格式中解析(解封,所以handshake的data里还有data)
•通过handshake的data中的信息,协商出公共的协议版本,得到negotiated_protocols,然后按照handshake协议封装,得到proto_data;
•将协商结果通告backend:按照netIDE的封装方法,类型为HELLO,module_id是backend_id(由decoded_header中的MOD_ID字段得到),data部分就是proto_data,得到最终的封包msg;用socket.send发送msg(应该是给Core,但是最终是要给backend的)
•再然后立即向datapath发送feature request(传入值为module_id,也就是说会记录是为哪个backend(module)来查的),获取交换机的属性;(并不是等backend来问,而是一旦收到backend的HELLO消息就自动会为新的client(backend)重新获取一份feature)
至此,与OpenFlow无关的连接消息等已经全部完成。
b) 若是OpenFlow消息(message_data中就是OpenFlow协议了)
•先清理xid database中的old 条目;
•然后由OpenFlow协议解析出message_data中的头部信息,主要是为了拿到xid(注意:此时拿到的xid是从data即OpenFlow消息格式中获取的xid,而不是netIDE的xid)(因为由流程可以看出shim需要将backend的xid换一个新的xid,然后发给switch,再将switch返回的换回原来的xid,所以也需要存储);
•然后由xid和module_id按照crc32规则(hash?)生成一个新的xid,new_xid,并将xid、module_id以及当前time(清理xid的database用得到)存储下来,与new_xid形成映射关系(数组index),为了后续由new_xid找到old_xid;
•然后再将message_data即OpenFlow数据头部的xid部分替换为new_xid,然后向datapath(DPID由netIDE的协议头已获取)发出;
至此,完成OpenFlow消息的转发(替换xid)工作。
ryu-backend
主要模块
步骤流程
1、 运行Ryu-backend
初始化及start CoreConnection线程;
2、 CoreConnection
1) 初始化(涉及zmq):backend与Core的通告流程
i. backend注册
•backend用socket向Core发送netIDE格式报文,类型是NETIDE_MODULE_ANN,即为module通告,data为backend_name,backend_name是“backend-ryu”+“os.pid”确定(ryu+进程号),可以看出xid, module_id, datapath_id此时都为0,因为还没分配;
•等待Core的响应NETIDE_MODULE_ACK(Core处理分配id),收到后解析报文,解析得MOD_ID,即得到了backend_id;
(也就是说由Core根据backend的name与pid,分配给backend一个id)
(发送ANN后就等待ACK,认证方法就是MODULE_ACK && data==backend_name,也就是ACK返回的data也会带着ANN中的data部分,也就是backend_name,作为认证方式)
ii. module注册
待注册的module其实就是各种app,这些app列表是通过app_manager.SERVICE_BRICKS获取的(Ryu控制器部分可知app初始化时就会加入到SERVICE_BRICKS中);除了ofp_event(ofp_handler)和backend本身(他们其实都是app,因为继承了RyuApp类),其余的都会注册,分配得到module_id;同时还会将其存入running_modules[module_name]=module_id中;
•同上,只不过可以看出'NETIDE_MODULE_ANN', 0, backend.backend_id, None, module_name;module_id不再是0而是backend_id了,因为已经分配了,所以module_id暂时采用backend_id代替;
•继续同上,NETIDE_MODULE_ACK中的可以看出MOD_ID即为Core分配给该模块的module_id,认证方式就是ACK的data部分和ANN的data部分是否一致,即module_name是否一致;
至此,module_id分配完毕(重点应该在Core,怎么分配的)。
iii. 与shim的handshake握手
•HELLO消息实际是shim与backend之间的通信,目的是协商公共支持的协议等。
•HELLO消息由backend向shim发送;
至此,整个backend的连接完全完成。(连接到了shim)
2) 接收core循环
进入handle_read函数进行处理,解析netide头部等信息:
a) 若是NETIDE_OPENFLOW消息
•初步筛选:要完成handshake,以及新datapath的处理;
•对于backend_info中没有存储的datapath(DPID)-即新的datapath(switch),首先创建一个对应的backenddatapath的实例(继承于controller.datapath,传入值为DPID和of协议,解析器等),然后存入backend_info的datapath中(索引为DPID,值为该实例对象),然后初始化(创建EventOFPStateChange的事件通告observer,然后处理)等???;
•如果backend_info已有DPID,索引出backenddatapath的实例;
•然后调用实例的handle_event进行处理(传入值为netide的header和msg);
•解析出所需各种头部信息后,将msg转化为event(msg_to_event),得到该event的observer(即apps),然后根据报文中module_id找到app(通过running_modules的记录(MOD_ID和module(App)的对应)查找,实际module_ann时都已经记录了),然后直接由ryu.base.app_manager.lookup_service_brick得到app,并得到app中的handler进行处理;
这里的处理类似于Ryu本身的消息推送环节(不同的是:1、Ryu本身的处理是转化为event后直接推送给各个app的events,是推送,然后由各个app中的event_loop进行处理,这里是直接进行处理)(效果应该一样啊);2、这里由于多了一个MOD_ID,所以即使是相同的event类型也有可能需要递送给不同的app(module)这确实是Ryu的一个问题,不同的app虽然都处理某种event,但是有些event给特定的app却无法区分,Ryu-backend可以解决这一问题。
•handle_event:将msg转化为event(ofp_event.ofp_msg_to_ev函数),然后获取该event的observer(订阅者),进而由该event的处理函数进行(应该就是EventOFPPacketIn的处理,扔给simple_switch模块了,由simple_switch.py代码可以看出它确实订阅了该消息);
b) 若是NETIDE_HELLO消息
暂不深究,由shim代码可知也是版本协商等
note
1、虽然说收到的都是Core的消息,但多数都是与backend的通信,包括HELLO消息;
2、
Python基础
1、 修饰符