Ryu:OpenFlow协议源码分析

网络
Ryu支持OpenFlow所有的版本,是所有SDN控制器中对OpenFlow支持最好的控制器之一。这得益于Ryu的代码设计,Ryu中关于OpenFlow协议的代码量不多。阅读Ryu源码,不仅让我了解到了Ryu的运行细节,也学会了许多的编码知识。这为我当前开发的协议提供了很大的帮助。

Ryu支持OpenFlow所有的版本,是所有SDN控制器中对OpenFlow支持最好的控制器之一。这得益于Ryu的代码设计,Ryu中关于OpenFlow协议的代码量不多。阅读Ryu源码,不仅让我了解到了Ryu的运行细节,也学会了许多的编码知识。这为我当前开发的协议提供了很大的帮助。

[[142215]]

本篇将从交换机与控制器建立连接开始,介绍OpenFlow报文的解析的相关代码实现。关于如何注册handler和发送报文,可查看之前的RYU核心源码解读:OFPHandler,Controller,RyuApp和AppManager。该篇侧重点为Ryu整体架构的运作,重点在RyuApp和AppManager;本篇重点在于详细介绍OpenFlow的解析和封装实现。希望对读者提供帮助。

Ofp_handler

负责底层数据通信的模块是ofp\_handler模块。ofp\_handler启动之后,start函数实例化了一个controller.OpenFlowController实例。OpenFlowController实例化之后,立即调用\__call\__()函数,call函数启动了server\_loop去创建server socket,其handler为domain\_connection\_factory函数。每当收到一个switch连接,domain\_connection\_factory就会实例化一个datapath对象。这个对象用于描述交换机的所有行为。其中定义了接收循环和发送循环。

Datapath

datapath.serve函数是socket通信收发逻辑的入口。该函数启动了一个绿色线程去处理发送循环,然后本线程负责接收循环的处理。self.\_send\_loop是发送主循环。其主要逻辑为:不断获取发送队列是否有数据,若有,则发送;底层调用的是socket.send\_all()函数, 逻辑比较简单,不加赘述

接收函数\_reck\_loop中实现了数据的接收和解析。 重点较多,解释作为代码注释,注释如下:

OpenFlow协议实现

OpenFlow协议解析部分代码大部分在ofproto目录下,少部分在controller目录下。以下内容将首先介绍ofproto目录下的源码内容,再介绍controller目录下的ofp_event文件。

__init__

首先,__init__.py并不为空。该文件定义了两个功能类似的函数get_ofp_module()和get_ofp_modules(),前者用于取得协议版本对应的协议定义文件和协议解析模块,后者则取出整个字典。对应的字典在ofproto_protocol模块中定义。

ofproto\_protocol

在ofproto\_protocol定义了\_versions字典,具体如下:在ofproto\_protocol定义了\_versions字典,具体如下:

除此之外,该文件还定义了Datapath的父类ProtocolDesc,此类基本上只完成了与协议版本相关的内容。该类最重要的两个成员是self.ofproto和self.ofproto\_parser,其值指明所本次通信所使用的OpenFlow协议的版本以及对应的解析模块。

ofproto\_common

ofproto\_common文件比较简单,主要定义了OpenFlow需要使用的公共属性,如监听端口,报头长度,报头封装格式等内容。

ofproto\_parser

ofproto\_parser文件定义了所有版本都需要的解析相关的公共属性。如定义了最重要的基类MsgBase(StringifyMixin)。

StringifyMixin类的定义在lib.stringify文件,有兴趣的读者可自行查看。MsgBase基类定义了最基础的属性信息,具体如下所示:

此外,该类还定义了基础的parser函数和serialize函数。基础的parser函数基本什么都没有做,仅返回一个赋值后的消息体

serialize函数分为3部分,self.\_serialize\_pre(), self.\_serialize\_body()和self.\_serialize\_header()。本质上完成了header的序列化。关于body的序列化,将在对应的派生类中得到重写。

ofproto_v1_0

以1.0版本为例介绍ofproto\_v1\_x.py文件的作用。由于Ryu支持多版本的OpenFlow,所以在ofproto目录下,定义了从1.0到1.5版本的所有代码实现。所以其文件命名为ofproto\_v1_x.py,x从[1,2,3,4,5]中获得,分别对应相应的协议版本。

此类文件最重要的一个目的是定义了所有需要的静态内容,包括某字段的所有选项以及消息封装的格式以及长度。与OpenFlow消息内容相关的有协议的类型,动作的类型,port的类型等。此外对应每一个报文,都需要定义其封装的格式,以及封装的长度。Ryu采用了Python的Struct库去完成数据的解封装工作,关于Struct的介绍将在后续内容介绍。具体定义内容举例如下:

OFP\_HEADER\_PACK\_STR = '!BBHI'的意思是将header按照8|8|16|32的长度封装成对应的数值。在Python中分别对应的是1个字节的integer|一个字节的integer|2个字节的integer|4个字节的integer。

calcsize函数用于计算对应的format的长度。

其他内容均为静态的定义,无需赘述。

#p#

ofproto_v1_0_parser

本模块用于定义报文的解析等动态内容。模块中定义了与OpenFlow协议对应的Common\_struct及message type对应的类。每一个message对应的类都是有MsgBase派生的,其继承了父类的parser函数和serialize函数。若报文无消息体,如Hello报文,则无需重写parser和serialize函数。

本模块定义了几个重要的全局函数:\_set\_msg\_type,\_register\_parser,msg\_parser和\_set\_msg\_reply。其作用介绍如下:

_set_msg_type: 完成类与ofproto模块中定义的报文名字的映射,原因在于ofproto模块定义的名字并不是类名,而解析时需要使用ofproto中的名字。

_register_parser:完成对应的类与类中的parser函数的映射,当解析函数从ofproto模块的名字映射到类之后,若需要解析,则需从类对应到对应的解析函数。parser函数是一个类函数,所以在使用时必须传入对应的类的对象作为参数。

msg_parser:从_MSG_PARSERS中获取对msg_type的parser,并返回解析之后的内容。

_set_msg_reply:完成该类与对应的回应报文的映射。

源码如下

报文如果有消息体,则需要重写parser函数或者serialize函数,具体根据报文内容而不一样。此处,分别以Packet\_in和Flow\_mod作为parser的案例和serialize的案例,示例如下:

此模块代码量大,包括OpenFlow协议对应版本内容的完全描述。分类上可分为解析和序列化封装两个重点内容。读者在阅读源码时可根据需求阅读片段即可。

Inet & ether

这两个模块非常简单,ether定义了常用的以太网的协议类型及其对应的代码;inet定义了IP协议族中不同协议的端口号,如TCP=6。

oxm_field

在1.3等高版本OpenFlow中,使用到了oxm\_field的概念。oxm全称为OpenFlow Extensible Match。当OpenFlow逐渐发展成熟,flow的match域越来越多。然而很多通信场景下使用到的匹配字段很少,甚至只有一个。OXM是一种TLV格式,使用OXM可以在下发流表时仅携带使用到的match域内容,而放弃剩余的大量的match域。当使用的match域较少时,统计概率上会减少报文传输的字节数。

nx_match

该文件定义了nicira extensible match的相关内容。

ofp_event

这个模块的位置并不再ofproto,而位于controller目录下。controller模块下的event定义了基础的事件基类。ofp\_event模块完成了OpenFlow报文到event的生成过程。模块中定义了EventOFPMsgBase(event.EventBase)类和\_ofp\_msg\_name\_to\_ev\_name(msg\_name)等函数的定义。相关函数都非常的简单,可从函数名了解到其功能,不再赘述。示例代码如下

Struct lib

Python的struct库是一个简单的,高效的数据封装\解封装的库。该库主要包含5个函数,介绍如下:

struct.pack(fmt, v1, v2, ...): 将V1,V2等值按照对应的fmt(format)进行封装。

struct.pack_into(fmt, buffer, offset, v1, v2, ...):将V1,V2等值按照对应的fmt(format)封装到buffer中,从初始位置offset开始。

struct.unpack(fmt, string): 将string按照fmt的格式解封

struct.unpack_from(fmt, buffer[offset=0,]): 按照fmt的格式,从offset开始将buffer解封。

 

struct.calcsize(fmt): 计算对应的fmt的长度。

更家详细的封装语法,请查看struct对应的链接。此处仅对常用语法进行介绍:

!:大端存储

c: char

B: 一个字节长度,unsigned char.

H:两个字节,16位

I: 4个字节,int型

Q: 64bits

x: padding

3x:3个字节的padding

5s: 5字节的字符串

总结

Ryu对OpenFlow协议的支持非常好,入门也比较容易,网上的资源也比较多,是一个非常值得推荐的SDN控制器。本篇对Ryu中从底层的数据收发到OpenFlow报文的解析的代码进行简要的分析,希望对读者有一定的帮助。

责任编辑:何妍 来源: SDNLAB
相关推荐

2012-03-05 13:41:58

OpenFlow

2015-07-15 15:24:25

OpenFlowSDN

2015-09-23 16:14:03

Ryu拓扑结构

2020-10-09 14:13:04

Zookeeper Z

2014-06-16 14:35:31

OpenFlow

2015-10-10 09:31:03

网络协议NETCONFOpenFlow

2015-09-11 09:15:32

RyuSDN

2011-05-23 14:16:32

2015-09-02 10:33:11

OpenFlowSDN

2015-01-26 10:22:38

OpenFlow

2013-08-28 09:26:01

2013-03-14 09:44:41

ONFOpenFlowSDN

2014-12-11 11:25:22

思科ACISDN

2012-11-26 09:49:37

SDNOpenFlowVLAN

2013-05-10 09:40:46

OpenFlow标准接口协议SDN

2013-02-18 09:16:41

路由器路由技术OpenFlow

2011-05-24 15:57:42

OpenFlow

2011-05-24 15:52:47

OpenFlow起源

2011-05-24 16:18:30

OpenFlow应用

2013-06-07 09:59:27

SDN虚拟化OpenDlow
点赞
收藏

51CTO技术栈公众号