Ryu代码解析-简单交换机
上一篇文章是关于Hub的,有关一些函数的用法都在上一篇文章里,大家有需要可以自行查看。
一个简单的交换机需要具有以下功能:
1.能够学习MAC地址,并且把MAC地址和接口联合起来填充MAC地址表。
2.当收到数据包的目的MAC地址在MAC地址表里面时,将数据包发送出去。
3.当收到的数据包目的MAC地址不在MAC地址表里面的时候,进行泛洪。
下面我们开始讲一下需要的算法:
1.使用Packet-In报文来学习MAC地址。
2.在学习到MAC地址之后,再收到数据包,交换机会先检查MAC地址表里面有没有目标地址,如果有,就用Packet-Out报文来把数据包送出去;如果没有,就泛洪数据包。
3.所谓的流表,就是包含了源和目的一个表,它是根据MAC地址表建立的,只有MAC地址表里面同时包含源和目的的时候,才会增加流表项。流表是在源和目的双向通信完成后建立的(这个表述可能有点牵强,我举个例子说明一下)
一台交换机连接两个主机,主机A向主机B发了数据包,主机B应答A的数据包经过交换机后,就建立了一条流表项,但是要注意:这个流表项的源是主机B,目的是A,大家可以分析一下为什么。
如果流表里面没有对应的流表项,在OpenFlow1.3版本,交换机可以选择丢弃数据包,或者匹配下一个流表,或者是传输给控制器,默认是丢弃数据包的,而在1.0,交换机只会Packet-In上报控制器。
控制器和交换机握手完成之后,一条默认流表项应该被添加进流表。它的作用是为了让交换机发送Packet-In消息。这条默认流表项被称为Table-Miss,不过Table-Miss不是默认存在的,他也是由控制器下发的。
我们在控制器代码里面定义的MAC地址表是只有控制器知道的,对于交换机来说,只有流表才能帮助它转发,所以控制器要给交换机下发流表,如果你不在交换机添加详细的流表项,那么你应该让交换机收到每一个数据包都上报控制器,由控制器下发Packet-Out报文进行处理,否则数据包会被丢弃。
下面展示源代码,源代码是ryu/app目录下面的simple_switch_13.py。13对应的是OpenFlow1.3版本。
#!/usr/bin/env python
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {}
@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
# install table-miss flow entry
#
# We specify NO BUFFER to max_len of the output action due to
# OVS bug. At this moment, if we specify a lesser number, e.g.,
# 128, OVS will send Packet-In with invalid buffer_id and
# truncated packet data. In that case, we cannot output packets
# correctly. The bug has been fixed in OVS v2.1.0.
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
@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)
多引入了一种交换机状态,用于下发Table-miss流表,同时为了获取数据包的MAC地址,需要引入packet和ethernet。
OpenFlow版本要指定为1.3,不同版本之间差距还是挺大的。
初始化的时候我们需要先定义一个字典,用于存储MAC地址表。
switch_features_handler函数的作用就是下发一条Table
miss流表,用于将不知如何转发的数据包上报控制器,鉴于流表项的特殊性,我们在交换机向控制器发送完自身信息的时候让控制器下发这条流表项,这个时候修饰器会调用这个函数。这条流表项需要具有以下特点:
优先级最低,保证最后匹配。
必须要能匹配所有数据包。
  所以我们指定了一个空的Match,并且把这条流表项的优先级设置为0,将OFPCML_NO_BUFFER指定为max_len。调用的add_flow函数是我们自己定义的。
add_flow函数的参数表较多,因为这些在下发流表的时候调用的FlowMod类里面都要用到,所以简要的介绍一下用到的参数(括号内是默认值),如果需要查看所有参数,请查阅Ryu
Controller Book。
- datapath:是传来数据的交换机,内有ID编号。
- priority(0):指定流表项的优先级,影响匹配顺序,优先级越大越先匹配。
- match(None):流表项要匹配的内容,比如进入接口,目的地址等。 buffer_id(ofproto_v1_3.OFP_NO_BUFFER):指定在OpenFlow交换机上缓冲的数据包的缓冲区ID。当未指定缓冲区ID时,请设置OFP_NO_BUFFER。
instructions ([]):交换机要执行的指令列表。
指定好所有的值最后就调用send_msg来发送流表项。
- _packet_in_handler函数要完成的功能比较多,首先它需要获取进入的接口,源和目的MAC地址,然后填充MAC地址表,注意一个细节,我们需要根据交换机编号和源地址来指定接口,因为同一个接口可能会对应不同的MAC地址。
然后做一下判断,如果MAC地址表里面没有目的MAC地址,就设置为广播;有地址就设置为出接口。
然后是添加流表项操作,我们只能在找到目的的情况下添加流表项。 最后调用send_msg函数发送Packet-Out消息。
代码讲解完毕。
下面展示一下运行过程:
在mininet上创建一个线性拓扑,三台交换机分别连三台主机。
mn --topo linear,3 --mac --switch ovsk --controller remote
然后运行Ryu代码,在网络中没有通信之前,所有交换机的流表应该都是空的。
运行了Ryu程序之后,交换机会收到一条Table miss流表项。
这个时候交换机会把所有未知目的地的数据包转发到控制器,现在我们在网络中创造一点流量。
用h1 ping h2,结果发现,控制器收到了许多Packet-In消息
通信完成后,交换机里面多了两条流表项
过一会再重复ping,控制器就不会收到Packet-In数据包了。
这就是一个简单的交换机。
61234