编写ryu应用

编写ryu应用非常简单,只需编写一个继承RyuApp基类的python类即可。

下面,我们以编写一个二层交换机为例,讲解ryu应用的编写:

a、如下所示,创建一个L2Switch.py的文件,编辑如下

from ryu.base import app_manager

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

保存文件,运行如下代码

# ryu-manager L2Switch.py

ok,要是没有意外,我们的应用已经可以正常运行了,只是现在还不具备一个二层交换机的功能。

一个正常的二层交换机具有如下功能:

1、解析入口数据包的mac地址,然后查找“mac转发表”进行数据转发。

2、若“mac转发表”中没有相应条目,则flood数据包到所有端口(除了数据包的进口)

3、根据应答数据包,学习端口与mac的对用关系,更新"mac转发表"

作为交换机的控制器,我们需要完成如下事情,

1、对上报的数据包进行mac学习,查找"mac转发表"下发流表
2、交换机特性操作

在ryu控制器中使用“修饰函数set_ev_cls”注册自定义处理函数到相应的事件中,

如下面“处理数据包进入事件”的代码

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        # If you hit this you might want to increase
        # the "miss_send_length" of your switch
        if ev.msg.msg_len < ev.msg.total_len:
            self.logger.debug("packet truncated: only %s of %s bytes",
                              ev.msg.msg_len, ev.msg.total_len)
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        in_port = msg.match['in_port']

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]

        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            # ignore lldp packet
            return
        dst = eth.dst
        src = eth.src

        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

        # learn a mac address to avoid FLOOD next time.
        self.mac_to_port[dpid][src] = in_port

        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        actions = [parser.OFPActionOutput(out_port)]

        # install a flow to avoid packet_in next time
        if out_port != ofproto.OFPP_FLOOD:
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
            # verify if we have a valid buffer_id, if yes avoid to send both
            # flow_mod & packet_out
            if msg.buffer_id != ofproto.OFP_NO_BUFFER:
                self.add_flow(datapath, 1, match, actions, msg.buffer_id)
                return
            else:
                self.add_flow(datapath, 1, match, actions)
        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER:
            data = msg.data

        out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                  in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)

上面代码中,自定义了_packet_in_handler函数,并将此函数注册到ofp_event.EventOFPPacketIn事件,这样,一旦有数据包上报到控制器,就会调用此函数。

set_ev_cls另外一个参数是controller与交换机datapath的状态,datapath有四种状态:

HANDSHAKE_DISPATCHERCONFIG_DISPATCHERMAIN_DISPATCHERDEAD_DISPATCHER

在源码中定义如下:

# just represent OF datapath state. datapath specific so should be moved.
HANDSHAKE_DISPATCHER = "handshake"
CONFIG_DISPATCHER = "config"
MAIN_DISPATCHER = "main"
DEAD_DISPATCHER = "dead"

从上面datapath定义可以看出,datapath在一个生命周期中经历的状态为

handshake->config->main->dead

下面分析具体的数据操作:

    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数据结构。括弧中填写对应字段的赋值即可。 

具体代码请参见simple_switch_13.py

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
永久连接: http://www.nfvschool.cn/?p=604
标签:

发表评论