编写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_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