跳转到正文
莫尔索随笔
返回

Ryu SDN 控制器学习总结:OpenFlow 协议与 Open vSwitch 实践

预计 5 分钟

第一时间捕获有价值的信号

本文深入探讨 Ryu SDN 控制器,从 Open vSwitch 工具转向专用控制器,详细解析 OpenFlow 协议细节与 Ryu 架构,助你掌握 SDN 应用开发。

核心内容

之前一直在用 ovs-vsctl 工具控制 Open vSwitch,决定使用专门的控制器,趁机学习下openflow协议细节,辗转了POX,Floodlight,OpenDaylight等多个控制器之后,发现还是喜欢Python语言的控制器ryu。 ryu是日本NTT公司推出的SDN控制器框架,它基于Python开发,模块清晰,可扩展性好,逐步取代了早期的NOX,支持OpenFlow 1.0到1.5版本,也支持Netconf,OF-CONIFG等其他南向协议,提供了丰富的组件,便于开发者构建SDN应用。

ryu 代码结构

  1. ryu/app 基于ryu控制器开发的app,内含ryu的图形界面、北向防火墙、北向QoS、简易L2交换机、L3路由器等。
  2. ryu/base 内含app_manager.py脚本,其作用是RYU应用的管理中心,用于加载ryu应用程序,接受从APP发送过来的信息,同时也完成消息的路由。定义了两大基类:RyuApp和AppManager。RyuApp定义了App的基本属性,AppManager则定义了用于管理多个App的方法和属性。
  3. ryu/controller 该文件夹中含有许多关键的ryu源码文件,controller.py文件中含有两个十分重要的类:OpenFlowController 定义了构建一个控制器的基本方法和属性,DataPath 是用于描述OpenFlow控制器和交换机是如何连接的类:
class OpenFlowController(object):
    def __init__(self):
        super(OpenFlowController, self).__init__()
        if not CONF.ofp_tcp_listen_port and not CONF.ofp_ssl_listen_port:
            self.ofp_tcp_listen_port = ofproto_common.OFP_TCP_PORT
            self.ofp_ssl_listen_port = ofproto_common.OFP_SSL_PORT
            # For the backward compatibility, we spawn a server loop
            # listening on the old OpenFlow listen port 6633.
            hub.spawn(self.server_loop,
                      ofproto_common.OFP_TCP_PORT_OLD,
                      ofproto_common.OFP_SSL_PORT_OLD)
        else:
            self.ofp_tcp_listen_port = CONF.ofp_tcp_listen_port
            self.ofp_ssl_listen_port = CONF.ofp_ssl_listen_port

        # Example:
        # self._clients = {
        #     ('127.0.0.1', 6653): <instance of StreamClient>,
        # }
        self._clients = {}
...
class Datapath(ofproto_protocol.ProtocolDesc):
    """
    A class to describe an OpenFlow switch connected to this controller.
    An instance has the following attributes.
    .. tabularcolumns:: |l|L|
    # entry point
    ==================================== ======================================
    Attribute                            Description
    ==================================== ======================================
    id                                   64-bit OpenFlow Datapath ID.
                                         Only available for
                                         ryu.controller.handler.MAIN_DISPATCHER
                                         phase.
    ofproto                              A module which exports OpenFlow
                                         definitions, mainly constants appeared
                                         in the specification, for the
                                         negotiated OpenFlow version.  For
                                         example, ryu.ofproto.ofproto_v1_0 for
                                         OpenFlow 1.0.
    ofproto_parser                       A module which exports OpenFlow wire
                                         message encoder and decoder for the
                                         negotiated OpenFlow version.
                                         For example,
                                         ryu.ofproto.ofproto_v1_0_parser
                                         for OpenFlow 1.0.
    ofproto_parser.OFPxxxx(datapath,...) A callable to prepare an OpenFlow
                                         message for the given switch.  It can
                                         be sent with Datapath.send_msg later.
                                         xxxx is a name of the message.  For
                                         example OFPFlowMod for flow-mod
                                         message.  Arguemnts depend on the
                                         message.
    set_xid(self, msg)                   Generate an OpenFlow XID and put it
                                         in msg.xid.
    send_msg(self, msg)                  Queue an OpenFlow message to send to
                                         the corresponding switch.  If msg.xid
                                         is None, set_xid is automatically
                                         called on the message before queueing.
    send_packet_out                      deprecated
    send_flow_mod                        deprecated
    send_flow_del                        deprecated
    send_delete_all_flows                deprecated
    send_barrier                         Queue an OpenFlow barrier message to
                                         send to the switch.
    send_nxt_set_flow_format             deprecated
    is_reserved_port                     deprecated
    ==================================== ======================================
    """
...
  1. ryu/cmd 定义了ryu的命令系统。

  2. ryu/contrib 社区贡献者的代码。

  3. ryu/lib 该目录下定义了一些数据报的格式和数据结构,及诸多网络协议。

  4. ryu/ofproto 该目录下的文件分为两类,一类定义协议的数据结构,另外一类用于协议报文解析。

  5. ryu/services 完成了bgp、vrrp协议及ovsdb的实现。

  6. ryu/tests 用于测试。

  7. ryu/topology 定义了自定义拓扑文件中需要的一些模块和方法,其中的switches.py(关键文件),完成了对一整套交换机处理流程的定义。

L2Switch example(来自官网)

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_0

class L2Switch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)

    # 调用装饰器,第一个参数表示发生的事件种类,第二个参数告诉交换机只有在握手之后被调用。
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg
        dp = msg.datapath
        ofp = dp.ofproto
        ofp_parser = dp.ofproto_parser

        actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
        out = ofp_parser.OFPPacketOut(
            datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
            actions=actions)
        dp.send_msg(out)

分析具体的数据操作:

  • ev.msg:每一个事件类ev中都有msg成员,用于携带触发事件的数据包。
  • msg.datapath:已经格式化的msg其实就是一个packet_in报文,msg.datapath直接可以获得packet_in报文的datapath结构。datapath用于描述一个交换网桥,也是和控制器通信的实体单元。datapath.send_msg()函数用于发送数据到指定datapath,通过datapath.id可获得dpid数据。
  • datapath.ofproto对象是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如动作类型OFPP_FLOOD。
  • datapath.ofp_parser则是一个按照OpenFlow解析的数据结构。
  • actions是一个列表,用于存放action list,可在其中添加动作。
  • 通过ofp_parser类,可以构造构造packet_out数据结构,括弧中填写对应字段的赋值即可。
  • 如果datapath.send_msg()函数发送的是一个OpenFlow的数据结构,RYU将把这个数据发送到对应的datapath。

一个标准的控制器处理事件的模板,@set_ev_cls(ofp_event.Event, DISPATCHER(s))装饰器的含义就是,当接收到DISPATCHER(s)情况的Event事件进行handler_function处理,其中handler_function是自定义的函数处理过程,命名可以任意指定;ofp_event.Event是由ofp_event.py提供的一系列事件。

@set_ev_cls(ofp_event.Event, DISPATCHER(s))
    def handler_function(self, ev):
        ...

DISPATCHER(s)可以为单独一个,也可以为由多个DISPATCHER组成的列表,DISPATCHER描述的情况包括:

DefinationExplanation
HANDSHAKE_DISPATCHER交换HELLO消息
CONFIG_DISPATCHER等待接收SwitchFeatures消息
MAIN_DISPATCHER正常状态
DEAD_DISPATCHER连接断开

ofp_event

ofp_event类位于ryu/controller/ofp_event.py,主要定义了OpenFlow中的各种事件,配合set_cls_ev可以对指定事件进行处理,接下来,在分析学习官方案例的同时,也会整合Ryu源码与OpenFlow1.3.3协议的内容,对这些事件进行深入的理解与分析。

EventOFPMsgBase

事件类描述了从交换机接收OpenFlow消息的过程,按照约定,它们被命名为ryu.controller.ofp_event.EventOFPxxxx,其中xxxx是相应的OpenFlow消息的名称。例如,EventOFPPacketIn用于packet-in message,Ryu的OpenFlow控制器部分自动解码从交换机接收到的OpenFlow消息,并将这些事件发送(ryu.controller.handler.set_ev_cls)到Ryu应用程序,EventOFPMsgBase事件类是所有EventOFPxxxx的基类。

class EventOFPMsgBase(event.EventBase):
    """
    The base class of OpenFlow event class.
    OpenFlow event classes have at least the following attributes.
    .. tabularcolumns:: |l|L|
    ============ ==============================================================
    Attribute    Description
    ============ ==============================================================
    msg          An object which describes the corresponding OpenFlow message.
    msg.datapath A ryu.controller.controller.Datapath instance
                 which describes an OpenFlow switch from which we received
                 this OpenFlow message.
    timestamp    Timestamp when Datapath instance generated this event.
    ============ ==============================================================
    The msg object has some more additional members whose values are extracted
    from the original OpenFlow message.
    """

    def __init__(self, msg):
        self.timestamp = time.time()
        super(EventOFPMsgBase, self).__init__()
        self.msg = msg
EventOFPStateChange
发起事件处理事件
交换机状态变化EventOFPStateChange
class EventOFPStateChange(event.EventBase):
    """
    An event class for negotiation phase change notification.
    An instance of this class is sent to observer after changing
    the negotiation phase.
    An instance has at least the following attributes.
    ========= =================================================================
    Attribute Description
    ========= =================================================================
    datapath  ryu.controller.controller.Datapath instance of the switch
    ========= =================================================================
    """

    def __init__(self, dp):
        super(EventOFPStateChange, self).__init__()
        self.datapath = dp

该class是处理协商阶段变更通知的事件,在协商更改后发生此消息给观察者,当我们使用以下的命令,我们就成为了观察者,发生此类消息时,便可以进行接收和处理。

@set_ev_cls(ofp_event.EventOFPStateChange,
                [MAIN_DISPATCHER, DEAD_DISPATCHER])

在协商阶段,MAIN_DISPATCHER意味着有新的交换机接入,DEAD_DISPATCHER意味着有交换机脱离连接。

EventOFPPortStateChange
发起事件处理事件
OFPPortStatsRequestEventOFPPortStateChange
class EventOFPPortStateChange(event.EventBase):
    """
    An event class to notify the port state changes of Dtatapath instance.
    This event performs like EventOFPPortStatus, but Ryu will
    send this event after updating ``ports`` dict of Datapath instances.
    An instance has at least the following attributes.
    ========= =================================================================
    Attribute Description
    ========= =================================================================
    datapath  ryu.controller.controller.Datapath instance of the switch
    reason    one of OFPPR_*
    port_no   Port number which state was changed
    ========= =================================================================
    """

    def __init__(self, dp, reason, port_no):
        super(EventOFPPortStateChange, self).__init__()
        self.datapath = dp
        self.reason = reason
        self.port_no = port_no

参考链接

Welcome to RYU the Network Operating System(NOS) RYU入门教程(李呈大神)