zoukankan      html  css  js  c++  java
  • 实验五 RYU控制器基本应用

    ubuntu镜像地址:https://pan.baidu.com/s/1qYN_MtUboPmruHda1DgrTA  提取码:mhfi

    本实验分两个部分

    实验一使用命令行启动控制器,使用simple_switch_13.py组件查看效果,实验二对simple_switch_13.py进行分析。

    一、实验一

      参考:https://blog.csdn.net/caiqiiqi/article/details/79698143

     1、创建拓扑

       参数说明:

      --controller 自己指定一个控制器,一般用remote指定远程控制器。还可以用--ip 与 --port 指定地址和端口号

      --mac 自动设置mac地址,并让其从小到大排列

      --switch 设置交换机的类型。交换机分为内核型(lxbr),用户型(user),OVS型(ovsk,ovsbr,ivs)。内核型和OVS型比用户型吞吐量大的多,常被选用。

      -x 打开所有节点的终端

     2、设置交换机s1上的OpenFlow版本并查看流表

      3、执行ryu控制器

       参数说明:

      --verbose:显示调试信息

      ryu.app.simple_switch_13为提供了传统2层交换机策略的组件。其他组件见下图

      调试信息中,

      EVENT ofp_event->SimpleSwitch13 EventOFPSwitchFeatures

           switch features ev version=0x4,msg_type=0x6,msg_len=0x20,xid=0x99ea8d54,OFPSwitchFeatures(auxiliary_id=0,capabilities=79,datapath_id=1,n_buffers=256,n_tables=254)
      这里是获取交换机的特征的过程,会在其他随笔里具体分析。

      对以上几个字段分析:

        在ryu/ryu/lib/packet/openflow.py 可查看基本属性的意义。

         在ryu/ryu/ofproto/ofproto_v1_3.py可查看各类型的值的意义。

    属性 意义
    version 0x4 OpenFlow1.3版本
    msg_type 0x6

    这是OPEN_FEATURE_REPLY报文

    msg_len 0x20 报文长度
    xid 0x99ea8d54 交互的编号

     4、查看交换机s1流表,发现多了一条Table-miss的流表项。

       存在时长11秒,表号为0,匹配数据包22个,匹配字节数1764B,优先级为0(最低),动作为发送到控制器,65535表示不缓存数据包(OFP_NO_BUFFER)。

       5、用h1 ping h2,并用wireshark抓包详细过程

    • h1 ping h2过程:

    1). h1 -> ff:ff:ff:ff:ff:ff(广播) ARP Request, 查询h2的MAC地址;
    2). h2-> h1 ARP Reply, 响应h2的MAC地址;
    3). h1-> h2 ICMP echo request;
    4). h2-> h1 ICMP echo reply

    • 详细过程分析:  

      (1)h1 发送ARP请求报文,源mac为h1mac,目的mac为ffffffffffff,交换机未匹配到流表项,向控制器发送Packet_In报文(编号1440)。

      (2)控制器将入端口1与h1的mac地址绑定,并回复Packet_Out报文给交换机,让其执行FLOOD(泛洪)操作,交换机向除了入端口以外的端口泛洪ARP请求报文(编号1441)。

      (3)h2接收到ARP请求报文,回复ARP应答报文,源mac为h2mac,目的mac为h1mac,交换机未匹配到流表项,向控制器发送Packet_In报文(编号1446).

      (4)控制器将入端口2与h2的mac地址绑定,再看交换机送来的数据包,发现源mac(h2mac)和目的mac(h1mac)都已经和入端口绑定,所以可以下发一条流表项(Flow_Mod报文):

          FlowEntry1:源地址:h2mac,目的地址:h1mac,动作:output 1 (从端口1发出)(编号1447)

      (5)h1收到ARP应答报文后,开始向h2发送ICMP请求数据包,数据包发送至交换机时,交换机s1未匹配到流表项,向控制器发送Packet_In报文。(编号1450)

      (6)控制器查看交换机发来的数据包,发现源mac(h1mac)和目的mac(h2mac)都已经和入端口绑定,所以可以下发一条流表项(Flow_Mod报文):

          FlowEntry2:源地址:h1mac,目的地址:h2mac,动作:output 2 (从端口2发出)(编号1451)

      (7)交换机按流表项转发ICMP请求/应答数据包,ping过程顺利完成。

       6、查看交换机流表项和控制器日志

        交换机多了2条流表项,源mac地址分别为h1和h2,目的mac为h2和h1,优先级为1,入端口分别为1和2,动作分别为从2/1端口发出,这也就是由ping生成的流表项。第二条流表项匹配数据包数比第一条少1的原因是h1一开始发的ARP请求是广播形式,所以dst_mac为ff:ff:ff:ff:ff:ff,而不是00:00:00:00:00:01。

        同样,在控制器的Log日志里也写明了触发了多次Packet_In事件,由上述的分析说明最后3次Packet_In事件是由这次ping触发的,第一次为 h1的ARP请求报文触发的,第二次由ARP回复报文触发的,第三次是h1给h2发送ICMP请求报文触发的。

      二、实验二 分析simple_switch_13.py

        参考:https://blog.csdn.net/xiajx98/article/details/92798847

        1、simple_switch_13.py在ryu/ryu/app目录下,以下是完整代码:    

      1 # Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #    http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
     12 # implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 
     16 from ryu.base import app_manager
     17 from ryu.controller import ofp_event
     18 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
     19 from ryu.controller.handler import set_ev_cls
     20 from ryu.ofproto import ofproto_v1_3
     21 from ryu.lib.packet import packet
     22 from ryu.lib.packet import ethernet
     23 from ryu.lib.packet import ether_types
     24 
     25 
     26 class SimpleSwitch13(app_manager.RyuApp):
     27     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
     28 
     29     def __init__(self, *args, **kwargs):
     30         super(SimpleSwitch13, self).__init__(*args, **kwargs)
     31         self.mac_to_port = {}
     32 
     33     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
     34     def switch_features_handler(self, ev):
     35         datapath = ev.msg.datapath
     36         ofproto = datapath.ofproto
     37         parser = datapath.ofproto_parser
     38 
     39         # install table-miss flow entry
     40         #
     41         # We specify NO BUFFER to max_len of the output action due to
     42         # OVS bug. At this moment, if we specify a lesser number, e.g.,
     43         # 128, OVS will send Packet-In with invalid buffer_id and
     44         # truncated packet data. In that case, we cannot output packets
     45         # correctly.  The bug has been fixed in OVS v2.1.0.
     46         match = parser.OFPMatch()
     47         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
     48                                           ofproto.OFPCML_NO_BUFFER)]
     49         self.add_flow(datapath, 0, match, actions)
     50 
     51     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
     52         ofproto = datapath.ofproto
     53         parser = datapath.ofproto_parser
     54 
     55         inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
     56                                              actions)]
     57         if buffer_id:
     58             mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
     59                                     priority=priority, match=match,
     60                                     instructions=inst)
     61         else:
     62             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
     63                                     match=match, instructions=inst)
     64         datapath.send_msg(mod)
     65 
     66     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
     67     def _packet_in_handler(self, ev):
     68         # If you hit this you might want to increase
     69         # the "miss_send_length" of your switch
     70         if ev.msg.msg_len < ev.msg.total_len:
     71             self.logger.debug("packet truncated: only %s of %s bytes",
     72                               ev.msg.msg_len, ev.msg.total_len)
     73         msg = ev.msg
     74         datapath = msg.datapath
     75         ofproto = datapath.ofproto
     76         parser = datapath.ofproto_parser
     77         in_port = msg.match['in_port']
     78 
     79         pkt = packet.Packet(msg.data)
     80         eth = pkt.get_protocols(ethernet.ethernet)[0]
     81 
     82         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
     83             # ignore lldp packet
     84             return
     85         dst = eth.dst
     86         src = eth.src
     87 
     88         dpid = datapath.id
     89         self.mac_to_port.setdefault(dpid, {})
     90 
     91         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
     92 
     93         # learn a mac address to avoid FLOOD next time.
     94         self.mac_to_port[dpid][src] = in_port
     95 
     96         if dst in self.mac_to_port[dpid]:
     97             out_port = self.mac_to_port[dpid][dst]
     98         else:
     99             out_port = ofproto.OFPP_FLOOD
    100 
    101         actions = [parser.OFPActionOutput(out_port)]
    102 
    103         # install a flow to avoid packet_in next time
    104         if out_port != ofproto.OFPP_FLOOD:
    105             match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
    106             # verify if we have a valid buffer_id, if yes avoid to send both
    107             # flow_mod & packet_out
    108             if msg.buffer_id != ofproto.OFP_NO_BUFFER:
    109                 self.add_flow(datapath, 1, match, actions, msg.buffer_id)
    110                 return
    111             else:
    112                 self.add_flow(datapath, 1, match, actions)
    113         data = None
    114         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
    115             data = msg.data
    116 
    117         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
    118                                   in_port=in_port, actions=actions, data=data)
    119         datapath.send_msg(out)

        2、分段代码    

    1 class SimpleSwitch13(app_manager.RyuApp):

      继承了ryu.base.app_manager

        OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
        def __init__(self, *args, **kwargs):
            super(SimpleSwitch13, self).__init__(*args, **kwargs)
            self.mac_to_port = {}

      OFP_VERSIONS是指OpenFlow版本,这里调取了在ofproto_v1_3.py里声明的静态变量OFP_VERSION,值为4,为OpenFlow1.3版本。

      self.mac_to_port是一个保存(交换机id, mac地址)到转发端口的字典。  

     1     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
     2     def switch_features_handler(self, ev):
     3         datapath = ev.msg.datapath
     4         ofproto = datapath.ofproto
     5         parser = datapath.ofproto_parser
     6 
     7         match = parser.OFPMatch()
     8         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
     9                                           ofproto.OFPCML_NO_BUFFER)]
    10         self.add_flow(datapath, 0, match, actions)

      这是利用了一个装饰器实现了对事件的控制。这里要了解两个知识,控制器事件和控制器状态。

      控制器事件(Event),Event具体见ryu/controller/ofp_event.py,其事件名称是由接收到的报文类型来命名的,名字为Event+报文类型,例如本例中,控制器收到的是交换机发送的FEATURE_REPLY报文,所以事件名称为EventOFPSwitchFeatures。所以本事件其实就是当控制器接收到FEATURE_REPLY报文触发。

      控制器状态:ryu控制器和交换机交互有4个阶段,详见ryu/ryu/controller/handler.py

       4个状态:

    • HANDSHAKE_DISPATCHER:发送Hello报文并等待对端Hello报文。
    • CONFIG_DISPATCHER:协商版本并发送FEATURE-REQUEST报文。
    • MAIN_DISPATCHER:已收到FEATURE-REPLY报文并发送SET-CONFIG报文。
    • DEAD_DISPATCHER:与对端断开连接。

         综上,以上代码说明了当控制器处于CONFIG_DISPATCHER状态并且接受到FEATURE_REPLY报文时,执行switch_features_handler()函数。

      再来看函数中的内容:

    1    def switch_features_handler(self, ev):
    2  3         datapath = ev.msg.datapath
    3  4         ofproto = datapath.ofproto
    4  5         parser = datapath.ofproto_parser
    5  6 
    6  7         match = parser.OFPMatch()
    7  8         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
    8  9                                           ofproto.OFPCML_NO_BUFFER)]
    9 10         self.add_flow(datapath, 0, match, actions)

      datapath存储交换机的信息,match指流表项匹配,这里OFPMatch()指不匹配任何信息,actions是动作,表示匹配成功不缓存数据包并发送给控制器。最后add_flow是添加流表项的函数,我们在完整代码中可以看到add_flow调用了send_msg(mod)说明这里会发出Flow_Mod报文,所以上述过程就是下发Table-miss流表项的过程。  

     1     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
     2           ofproto = datapath.ofproto
     3           parser = datapath.ofproto_parser
     4   
     5           inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
     6                                                actions)]//立即执行动作,各静态变量说明见 ryu/ryu/ofproto/ofproto_v1_3.py
     7           if buffer_id:
     8               mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
     9                                       priority=priority, match=match,
    10                                       instructions=inst)
    11           else:
    12               mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
    13                                       match=match, instructions=inst)
    14           datapath.send_msg(mod)

      add_flow()函数作用是增加流表项,参数有datapath,优先级,匹配项,动作,buffer_id。上述代码说明了,此流表项匹配成功后应立即执行所规定的动作。如果此函数参数有buffer_id(就是交换机发送来的数据包有buffer_id,即交换机有缓存),那发送的Flow_Mod报文就带上buffer_id,若没有buffer_id,buffer_id就是None。最后,发出Flow_Mod报文,所以添加流表项是通过Flow_Mod报文实现。

    1 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    2     def _packet_in_handler(self, ev):

      这段代码说明了控制器在MAIN_DISPATCHER状态并且触发Packet_In事件时调用_packet_in_handler函数。

     # 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)

      这段代码是指传输出错,打印debug信息。

     1         msg = ev.msg
     2         datapath = msg.datapath
     3         ofproto = datapath.ofproto
     4         parser = datapath.ofproto_parser
     5         in_port = msg.match['in_port']
     6 
     7         pkt = packet.Packet(msg.data)
     8         eth = pkt.get_protocols(ethernet.ethernet)[0]
     9 
    10         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
    11             # ignore lldp packet
    12             return
    13         dst = eth.dst
    14         src = eth.src
    15 
    16         dpid = datapath.id
    17         self.mac_to_port.setdefault(dpid, {})
    18 
    19         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

      这里是从接收到的Packet_In报文中取出各种信息,如果报文时lldp报文,忽略它。随后用此dpid(交换机id)初始化mac_to_port,并在日志打印此Packet_In的基本信息。

    1  # learn a mac address to avoid FLOOD next time.
    2  self.mac_to_port[dpid][src] = in_port

      这一段代码是交换机自学习,类似传统二层交换机的MAC地址表自学习,取来往数据包的交换机id、源mac和入端口绑定来构造表。

      以下是最后一段代码:

     1         if dst in self.mac_to_port[dpid]://若在表中找到出端口信息,指示出端口
     2             out_port = self.mac_to_port[dpid][dst]
     3         else://否则泛洪
     4             out_port = ofproto.OFPP_FLOOD
     5 
     6         actions = [parser.OFPActionOutput(out_port)]
     7 
     8         # install a flow to avoid packet_in next time 创建流表项来避免再次收到Packet_in报文
     9         if out_port != ofproto.OFPP_FLOOD:
    10             match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
    11             # verify if we have a valid buffer_id, if yes avoid to send both
    12             # flow_mod & packet_out
    13             if msg.buffer_id != ofproto.OFP_NO_BUFFER://有buffer_id,带上buffer_id,然后只发送Flow_mod报文,因为交换机已经有缓存数据包,就不需要发送packet_out报文
    14                 self.add_flow(datapath, 1, match, actions, msg.buffer_id) //add_flow函数内部就已发送了Flow_mod报文。,后面不用send_msg()
    15                 return
    16             else://若没有buffer_id,发送的Flow_Mod报文就无需要带上buffer_id,但是下一步要再发送一个Packet_out报文带上原数据包信息。
    17                 self.add_flow(datapath, 1, match, actions)
    18         data = None
    19         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
    20             data = msg.data
    21      //发送Packet_out数据包 带上交换机发来的数据包的信息
    22         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
    23                                   in_port=in_port, actions=actions, data=data)
    24         datapath.send_msg(out)

      以上代码说明,若在字典中有找到对应的出端口,动作就指定为发送给某一个端口,并且检查交换机发来的数据包是否有buffer_id,若有buffer_id则发送Flow_mod报文并带上buffer_id,若没有buffer_id则发送Flow_mod报文不带上buffer_id。若字典中找不到出端口,则动作为泛洪。最后,如果交换机发来的数据包没有buffer_id,则要回复一个Packet_out报文并带上原数据包的信息。

      至此,此篇分析结束。

      

     

  • 相关阅读:
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    FFmpeg解码H264及swscale缩放详解
    linux中cat more less head tail 命令区别
    C语言字符串操作总结大全(超详细)
    如何使用eclipse进行嵌入式Linux的开发
  • 原文地址:https://www.cnblogs.com/kl107/p/13138568.html
Copyright © 2011-2022 走看看