ryu应用编写

ryu应用编写

编写ryu应用非常简单,只需编写一个继承RyuApp基类的python类即可。下面,我们以编写一个二层交换机为例,讲解ryu应用的编写。

  • 创建一个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,要是没有意外,我们的应用已经可以正常运行了,只是现在还不具备一个二层交换机的功能。

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

  • 解析入口数据包的mac地址,然后查找“mac转发表”进行数据转发;
  • 若“mac转发表”中没有相应条目,则flood数据包到所有端口(除了数据包的进口);
  • 根据应答数据包,学习端口与mac的对用关系,更新"mac转发表"。

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

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

在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_DISPATCHER、CONFIG_DISPATCHER、MAIN_DISPATCHER、DEAD_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

Was this helpful?

0 / 0

发表回复 0