我们基于ryu/app/example_switch_13.py源代码改写。
类定义和初始化
class ExampleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(ExampleSwitch13, self).__init__(*args, **kwargs)
# initialize mac address table.
self.mac_to_port = {}
# ...
为了实现Ryu应用程序,我们继承ryu.base.app_manager.RyuApp类。另外,需要使用OpenFlow 1.3,OFP_VERSIONS指定了OpenFlow 1.3版本。
并且,定义MAC地址表mac_to_port。
在OpenFlow协议中,已定义了OpenFlow交换机和控制器之间进行通信所需的握手等一些过程。然而,由于Ryu的框架是包涵了这些过程的,因此我们在开发Ryu的应用程序时没有必要考虑这些过程。
事件处理程序
对于Ryu,当接收到OpenFlow消息时,会生成与该消息对应的事件。 我们期望接收的消息所相对应的事件处理程序由Ryu应用来实现。
把事件处理程序定义为一个具有参数的事件对象函数,并使用ryu.controller.handler.set_ev_cls装饰器进行装饰。set_ev_cls指定了接收到的消息的事件类和OpenFlow交换机的状态。第一个参数为所对应事件的事件名(事件类名称是ryu.controller.ofp_event.EventOFP + OpenFlow消息名称, 例如,在Packet-In消息的情况下,它成为EventOFPPacketIn。)第二个参数意义是根据交换机状态来调用该函数。 对于交换机状态,如下表所示,可以选择某个或选择list(多个)。
添加 Table-miss Flow Entry(流表项)
对于Table-miss 流表项做以下解释:每个Flow Table(流表)中可能会有一个Table Miss Flow Entry,它是专门用来处理该Flow Table中没有其他Flow Entry(流表项)能匹配成功的流的,它的特点是它的Match Fields能够匹配任何流,并且Priority(优先级)一定是0。也就是说当某条流在流表中找不到与之匹配的相应流表项时,便会与Table-miss Flow Entry匹配,此时交换会发送Packet-In消息给控制器,将该流交于控制器处理。
控制器在与OpenFlow交换机握手完成后,将Table-miss Flow Entry添加到流表中,以准备好接收Packet-In消息。
具体来说,在接收到“交换机特征(特征回复)”消息后,将添加Table-miss Flow Entry。
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# ...
具体描述如下:
在ev.msg中,存储了与事件对应的OpenFlow消息类的实例。 在这里,它是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures。
在msg.datapath中,存储了与发出此消息的OpenFlow交换机对应的ryu.controller.controller.Datapath类的实例。
Datapath类执行重要的处理,例如与OpenFlow交换机的实际通信以及发布接收到消息相对应的事件。
Ryu应用使用的主要属性如下:
Ryu应用程序中使用的Datapath类的主要方法如下:
send_msg(msg):
发送OpenFlow消息。 msg是对应于发送OpenFlow消息的ryu.ofproto.ofproto_parser.MsgBase的子类。每一个事件类ev中都有msg成员,用于携带触发事件的数据包。
交换集线器本身并不特别使用接收到的交换机特性消息。 它被处理为一个事件,以获得添加Table-miss Flow Entry的时间。
def switch_features_handler(self, ev):
# ...
# install the table-miss flow entry.
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
Table-miss Flow Entry具有最低(0)优先级,此Entry匹配所有数据包。 在该Entry的指令中,通过指定要输出到控制器端口的输出动作,在接收到的数据包与任何正常流entries不匹配的情况下,发出Packet-In。
生成空匹配以匹配所有数据包。 匹配在OFPMatch类中表示。
接下来,生成OUTPUT操作类(OFPActionOutput)的实例以传送到控制器端口。 将控制器指定为输出目标,并将OFPCML_NO_BUFFER指定为max_len,以便将所有数据包发送到控制器。
最后,为优先级指定0(最低),并执行add_flow()方法来发送Flow Mod消息。 add_flow()方法的内容将在后面的部分中解释。
Packet-in Message
创建Packet-In事件处理程序的处理程序,以便接收具有未知目的地的接收到的数据包。
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# ...
常用的OFPPacketIn类属性如下:
更新MAC地址表
def _packet_in_handler(self, ev):
# ...
# get the received port number from packet_in message.
in_port = msg.match['in_port']
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
# ...
从OFPPacketIn匹配中获取接收端口(in_port)。目的MAC地址和发送方MAC地址是使用Ryu的分组库从接收到的报文的以太网报头获得的。根据获取的发送方MAC地址和收到的端口号,更新MAC地址表。为了支持与多个OpenFlow交换机的连接,MAC地址表被设计为为每个OpenFlow交换机进行管理。 数据路径ID用于识别OpenFlow交换机。
判断传送目的地端口
当MAC地址表中存在目的MAC地址时,使用相应的端口号。 如果未找到,则会生成为输出端口指定flooding(OFPP_FLOOD)的OUTPUT操作类的实例。
def _packet_in_handler(self, ev):
# ...
# if the destination mac address is already learned,
# decide which port to output the packet, otherwise FLOOD.
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD
# construct action list.
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)
self.add_flow(datapath, 1, match, actions)
# ...
如果找到目标MAC地址,则会将条目添加到OpenFlow交换机的流表中。
与添加Table-miss流条目一样,指定匹配和操作,并执行add_flow()来添加流条目。
与Table-miss流程条目不同,此时设置匹配条件。 这次交换集线器的实现,已经指定了接收端口(in_port)和目标MAC地址(eth_dst)。 例如,由端口1接收的寻址到主机B的数据包是目标。
对于这个流条目,这个优先级被指定为1.该值越大优先级越高,因此在此处添加的流条目将在Table-miss流条目之前进行判定。
基于包含上述操作的摘要,将以下条目添加到流表。传送到主机B的报文(目的MAC地址为B),由端口1接收到端口4。
提示:使用OpenFlow,在选项中规定了一个名为NORMAL的逻辑输出端口,当为输出端口指定NORMAL时,交换机的L2 / L3功能用于处理数据包。 这意味着,通过指示将所有数据包输出到NORMAL端口,可以使交换机作为交换集线器工作。 但是,我们使用OpenFlow实现每个处理。
添加 Flow Entry处理
Packet-In处理程序的处理还没有完成,但是在这里看一下添加流条目的方法。
def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# construct flow_mod message and send it.
inst=[parser.OFPInstructionActions
(ofproto.OFPIT_APPLY_ACTIONS,actions)]
# ...
最后,通过发出Flow Mod消息向流表添加一个条目。
def add_flow(self, datapath, priority, match, actions):
# ...
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
对应于Flow Mod消息的类是OFPFlowMod类。 生成OFPFlowMod类的实例,并使用Datapath.send_msg()方法将消息发送到OpenFlow交换机。
补充:
OFPFlowMod类的构造函数有很多参数。 其中许多通常可以是默认的。
datapath:
这是支持OpenFlow交换机的Datapath类实例对象来进行流表操作。 通常指从传递给处理程序的事件中获取,如Packet-In消息。
cookie (0):
控制器指定的可选值,可在更新或删除条目时用作过滤条件。 这不用于数据包处理。
cookie_mask (0):
更新或删除条目时,如果指定了0以外的值,则使用条目的cookie值将其用作操作目标条目的过滤器。
table_id (0:指定操作对象流表的表ID。
命令(ofproto_v1_3.OFPFC_ADD):指定要执行的操作。
idle_timeout (0):
指定此条目的有效期,以秒为单位。 如果条目未被引用,并且idle_timeout指定的时间过去,则该条目将被删除。 当条目被引用时,经过的时间被重置。当条目被删除时,流程删除消息被发送到控制器。
hard_timeout (0):
指定此条目的有效期,以秒为单位。 与idle_timeout不同,使用hard_timeout,即使该条目被引用,经过的时间也不会被重置。 也就是说,无论条目的引用,当指定的时间过去时,条目都将被删除。与idle_timeout一样,当条目被删除时,将发送一个流删除消息。
priority (0):
指定此条目的优先顺序。 值越大,优先级越高。
buffer_id (ofproto_v1_3.OFP_NO_BUFFER):
指定在OpenFlow交换机上缓冲的数据包的缓冲区ID。
out_port (0):
如果命令为OFPFC_DELETE或OFPFC_DELETE_STRICT,则目标条目将被输出端口过滤。 如果命令是OFPFC_ADD,OFPFC_MODIFY或OFPFC_MODIFY_STRICT,则会被忽略。要禁用输出端口的过滤,请指定OFPP_ANY。
out_group (0):
与out_port一样,由输出组过滤。要禁用,请指定OFPG_ANY。
flags (0):
您可以指定以下标志组合。
match (None):指定匹配。
instructions ([]):指定指令列表。
分组传输
现在我们返回到Packet-In处理程序,并解释最后的处理方式。
无论MAC地址表中是否找到目的MAC地址,最后都会发出分组消息,并接收到报文。
def _packet_in_handler(self, ev):
# ...
# construct packet_out message and send it.
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=in_port, actions=actions,
data=msg.data)
datapath.send_msg(out)
对应于分组消息的类是OFPPacketOut类。
OFPPacketOut类用于构建packet_out消息。
使用OpenFlow消息类对象调用Datapath类的send_msg方法,Ryu构建并将该在线数据格式发送到交换机。
补充:
OFPPacketOut的构造函数的参数如下:
datapath:指定与OpenFlow交换机对应的Datapath类的实例。
buffer_id:指定在OpenFlow上缓冲的数据包的缓冲区ID。 如果没有缓冲,则指定OFP_NO_BUFFER。
in_port:指定接收报文的端口。 如果不是接收到的数据包,则指定OFPP_CONTROLLER。
data:指定报文的二进制数据。 当为buffer_id指定OFP_NO_BUFFER时使用。 当使用OpenFlow交换机的缓冲区时,这将被省略。
在交换集线器实现中,已为buffer_id指定了Packet-In消息的buffer_id。 如果Packet-In消息的缓冲区id已被禁用,则为接收到的报文数据指定接收到的Packet-In报文。
以上是对交换集线器源代码的解释。 接下来,我们来执行这个交换集线器来确认实际操作。