实验目标
- 在Ryu上开发二层MAC自学习交换(以下简称二层交换)应用
二层交换应用是最基础的计算机网络应用,通过开发二层交换应用,使用Ryu控制器开发网络应用
实验原理
模拟实现了二层交换机的运作原理:
- 交换机具有自学习能力,能记录源MAC地址和入端口的对应关系。
- 当一个数据包从某端口进入交换机,交换机将记录其源MAC地址和入端口的对应关系,然后查询对应的目的MAC地址对应的端口记录是否存在。
- 若查询成功,则将数据包转发到查询所得端口,若查询失败,则进行泛洪处理。
二层交换应用被广泛应用于局域网环境,是基础的计算机网络应用。
- 但是二层交换应用无法单独应用在有回环的网络拓扑中,还需要其他协议或者算法来辅助,解决广播数据包在环路中泛洪产生的广播风暴。
- 所以在测试本应用时,需搭建无环拓扑。
- 若搭建有环拓扑,则需运行STP(Spanning Tree Protocol)协议或者其他算法来解决环路风暴问题。
- 示例应用使用OpenFlow1.3版本协议,详细代码如下:
#coding:utf-8
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
class SimpleSwitch13(app_manager.RyuApp):
# 继承RyuApp父类,RyuApp是Ryu应用的抽象功能描述类。RyuApp类完成了Ryu应用所需要完成的基本操作
# 从而使得开发者在开发应用时专注于业务逻辑的实现
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args,**kwargs)
# mac_to_port字典用于保存MAC地址和交换机端口的对应关系
self.mac_to_port = {}
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
# 当datapath(此处的datapath等同于交换网桥,可以大致理解为一台交换机)与控制器建立连接之后,会通过features消息回复datapath的特性。
# switch_features_handler方法通过@set_ev_cls装饰器注册了EventOFPSwitchFeatures 事件。
# 当EventOFPSwitchFeatures事件在CONFIG_DISPATCHER阶段生成时,就会被分发到switch_features_handler方法,
# 从而执行处理方法,完成事件处理。
# @set_ev_cls装饰器的第一个参数是事件类型,第二个参数为产生事件的状态。
# Ryu的ofp_event模块定义了系统使用到的事件,包括OpenFlow事件和一些内部使用的自定义事件。
# Ryu定义了HANDSHAKE_DISPATCHER、CONFIG_DISPATCHER、MAIN_DISPATCHER和DEAD_DISPATCHER四种状态,
# 用于描述datapath 的OpenFlow 通道连接的通信状态。
# switch_features_handler方法给datapath下发一条table-miss流表项
# 用于指导交换机将匹配流表项失败的数据包用Packet-in的方式发送到控制器。
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
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):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
idle_timeout=0, hard_timeout=0,
match=match, instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
# _packet_in_handler 方法注册了EventOFPPacketIn 事件
# 当EventOFPPacketIn事件在MAIN_DISPATCHER状态下产生时,就会被分发给_packet_in_handler方法处理。
# 处理方法首先对数据包进行必要的解析
# 然后按照交换机dpid的入端口号为键值,将源MAC地址记录到mac_to_port字典中:mac_to_port[dpid][in_port]=SRC_MAC。
# 然后查看其目的MAC地址是否在mac_to_port字典数据项的值中。
# 若目的MAC地址不在表中,则将出端口设置为ofproto.OFPP_FLOOD,并通过Packetout报文指示交换机将数据包泛洪;
# 若查询成功,则将出端口设置为查询所得端口;最后调用add_flow方法将流表项下发到datapath,从而完成二层交换应用。
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]
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)
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)
运行实验
- 写完以上代码之后将其保存为Python 文件, 如
simple_switch_13_test.py
,然后保存到ryu/app目录下 - 由于这个文件是新加的,Ryu编译文件中并没有,所以还需要将Ryu重新编译安装
- 重新安装之后,通过ryu-manager命令启动Ryu控制器并加载新写的应用即可
具体命令如下
cd ryu
python setup.py install
ryu-manager ryu/app/simple_switch_13_test.py
启动Ryu之后,通过Mininet启动任意无环拓扑并连接到控制器。
sudo mn --controller remote
连接成功之后,在Mininet中进行pingall测试
- 具体测试结果如图所示