(一)基本要求
1.安装ryu
2.验证L2Switch.py
(二)进阶
simple_switch_13.py代码注释
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
# 继承ryu.base.app_manager.RyuApp
# 基类ryu.base.app_manager.RyuAPP是所有开发APP必备继承的类,
# 可以理解成开发APP的环境,而且有了它都不用注册,非常方便
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] # 指定OpenFlow 1.3版本
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {}
# 定义MAC地址列表,这里的mac_to_port表就是对应的交换机二层通信查询表
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
# ev.msg 是用来存储对应事件的 OpenFlow 消息类别实体
# msg.datapath是用来存储OpenFlow交换机ryu.controller.controller.Datapath 类别所对应的实体
datapath = ev.msg.datapath
ofproto = datapath.ofproto # ofproto表示使用的OpenFlow版本所对应的ryu.ofproto.ofproto_v1_3
parser = datapath.ofproto_parser # 和ofproto一样,有对应版本ryu.ofproto.ofproto_v1_3_parser,解析协议才能使用
# 下发table-miss流表项,让交换机对于不会处理的数据包通过packet-in消息上交给Ryu控制器!!!
# 匹配数据包
# 若数据包没有 match 任何一个普通 Flow Entry 时,则触发 Packet-In
match = parser.OFPMatch()
# 通过预留端口ofproto.OFPP_CONTROLLER,将packet-in消息发送给controller,并通过ofproto.OFPCML_NO_BUFFE指明Racket-in消息的原因是table miss
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
# 仔细看发送的调用函数的参数,第一个是端口,第二是bufferid,若不为空,则去指定缓存区去查找流表
# 执行 add_flow() 方法以发送 Flow Mod 消息
self.add_flow(datapath, 0, match, actions)
# priority = 0 ,优先级最低,为了所有流表都匹配不到的时候,才会发送到controller
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
# 新增流表项
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# Apply Actions 是用来设定那些必须立即执行的 action 所使用
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
# 通过 Flow Mod 消息将 Flow Entry 新增到 Flow table 中
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
# 这里作者添加的if语句,表示流表下发缓存区id有无来区分下发,有缓存区id,到对应缓存区下发,无则自动分配,不传参buffer_id
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)
# 送往 Controller 的封包可以僅只傳送 header 部分( Ethernet header ),剩下的則存在緩衝區間中以增加效率。
#但目前( 2014年1月 )Open vSwitch 存在臭蟲的關係,會將所有的封包都傳送,並不會只傳送 header。
#这条logger.debug日志是为了提示送往controller的packet_in包是截取header 部分,这里我是这么理解的,有错误请大家告知
# 为了接收处理未知目的地的数据包,需要执行Packet-In 事件管理
msg = ev.msg # 每一个事件类ev中都有msg成员,用于携带触发事件的数据包
datapath = msg.datapath # 已经格式化的msg其实就是一个packet_in报文,msg.datapath直接可以获得packet_in报文的datapath结构
# datapath用于描述一个交换网桥,也是和控制器通信的实体单元。
# datapath.send_msg()函数用于发送数据到指定datapath。
# 通过datapath.id可获得dpid数据。
ofproto = datapath.ofproto # datapath.ofproto对象是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如动作类型OFPP_FLOOD
parser = datapath.ofproto_parser # datapath.ofp_parser则是一个按照OpenFlow解析的数据结构。
# 更新Mac地址表
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
# pkt.get_protocols 传入的是 协议类 参数
# 得到协议的ethernet.ethernet类实例的协议列表,看源码知道了lib.packet.packet/ethernet
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, {})
# 指定交换机dpid,默认mac_to_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
# 判断转发的数据包的连接端口
# 目的 MAC 位址若存在于 MAC 地址表,则判断该连接端口号码为输出。
# 反之若不存在于 MAC 地址表则 OUTPUT action 类别的实体并生成 flooding( OFPP_FLOOD )给目的连接端口使用。
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
# 有目的地址寻找端口号,否则泛洪
else:
out_port = ofproto.OFPP_FLOOD
# 准备泛洪的packet_out指令给后面的if判断语句最后的else
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, eth_src=src)
# 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)
# 在 MAC 地址表中找寻目的 MAC 地址,若是有找到则发送 Packet-Out 讯息,并且转送数据包。
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
# 表示要泛洪发送Packet-Out,把本身的二进制数据取出来
data = msg.data
#需要Flooding 的actions和msg。buffer_id已经准备好了
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)
1.mac_to_port:
这里的mac_to_port表就是对应的交换机二层通信查询表
2.dpid:
packet_in_handler函数通@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)标签来注册对packet_in事件的监听。simple_switch的packet_in_handler函数中解析了以太网帧头,得到了源、目的mac地址,记录交换机id、源mac地址到收包端口号。
3.新增功能:
增加了table-miss flow entry,主要是为了第一次未能匹配流表的流(packet-in)发送给controller
4.流规则下发:
自学习后,检验目的地址是否已经学习,如果已经学习到,则向交换机下发流表,并让交换机向相应端口转发包。如果没有,则无法下发流表,让交换机洪范地转发包。
5.优先级:
switch_features_handler的priority=0,_packet_in_handler的流表的priority=1,所以switch_features_handler的优先级高
参考链接:https://blog.csdn.net/weixin_46239293/article/details/115560411
(三)总结
本次的实验和上次的实验差不多,更多的是去找资料理解原理。
通过实验5的经验积累,基础要求部分有遇到的问题比较少,一个是安装ryu的时候,不能完全用文件里给的命令,会太大不让装,后来去网上刚好搜到了老师前几年的博文,把后面的命令用"sudo pip3 install -r tools/pip-requires -i https://pypi.tuna.tsinghua.edu.cn/simple","sudo python3 setup.py installL2Switch"替换掉就好了,还有一个就是搬L2Switch代码的时候敲漏了或者打错字了,跑的时候改改没大毛病。
做进阶入门者要理解ryu代码还是有些困难的,但看一些网络上资料的解读,还是可以领悟许多。正好找资料的运气不错,找到了挺多好理解的资料,正文里放了其中一个的参考链接感觉挺完整的。