实验6:开源控制器实践--Ryu

(一)基本要求

1.安装ryu

实验6:开源控制器实践--Ryu

2.验证L2Switch.py

实验6:开源控制器实践--Ryu

(二)进阶

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代码还是有些困难的,但看一些网络上资料的解读,还是可以领悟许多。正好找资料的运气不错,找到了挺多好理解的资料,正文里放了其中一个的参考链接感觉挺完整的。

上一篇:实验6:开源控制器实践——RYU


下一篇:WPS下 宏使用js编写及一些脚本