zoukankan      html  css  js  c++  java
  • SDN实验---Ryu的应用开发(六)网络拓扑时延探测

    一:预备知识

    SDN实验---Ryu的应用开发(五)网络拓扑发现

    Ryu源码之模块功能分析

    Ryu源码之拓扑发现原理分析

    二:实验原理

    网络时延探测应用利用了Ryu自带的Switches模块的数据,获取到了LLDP数据发送时的时间戳,然后和收到的时间戳进行相减,得到了LLDP数据包从控制器下发到交换机A,然后从交换机A到交换机B,再上报给控制器的时延T1,示例见图1的蓝色箭头。

    同理反向的时延T2由绿色的箭头组成。

    此外,控制器到交换机的往返时延由一个蓝色箭头和一个绿色箭头组成,此部分时延由echo报文测试,分别为Ta,Tb。最后链路的前向后向平均时延T=(T1+T2-Ta-Tb)/2。

     

    三:时延探测代码实现

    (一)拓扑发现模块

    from ryu.base import app_manager
    
    from ryu.ofproto import ofproto_v1_3
    
    from ryu.controller import ofp_event
    from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER #只是表示datapath数据路径的状态
    from ryu.controller.handler import set_ev_cls
    
    from ryu.lib import hub
    from ryu.lib.packet import packet,ethernet
    
    from ryu.topology import event,switches
    from ryu.topology.api import get_switch,get_link,get_host
    
    import threading,time,random
    
    DELAY_MONITOR_PERIOD = 5
    LOCK = threading.RLock()
    
    
    class TopoDetect(app_manager.RyuApp):
        OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
        def __init__(self,*args,**kwargs):
            super(TopoDetect,self).__init__(*args,**kwargs)
            self.topology_api_app = self
            self.name = "topology"
            self.link_list = None
            self.switch_list = None
            self.host_list = None
    
            self.dpid2id = {}
            self.id2dpid = {}
            self.dpid2switch = {}
    
            self.ip2host = {}
            self.ip2switch = {}
    
            self.net_size = 0
            self.net_topo = []
    
            self.net_flag = False
            self.net_arrived = 0
            
            self.monitor_thread = hub.spawn(self._monitor)
    
        def _monitor(self):
            """
            协程实现伪并发,探测拓扑状态
            """
            while True:
                #print("------------------_monitor")
                self._host_add_handler(None) #主机单独提取处理
                self.get_topology(None)
                if self.net_flag:
                    try:
                        self.show_topology()
                    except Exception as err:
                        print("Please use cmd: pingall to detect topology and wait a moment")
                hub.sleep(DELAY_MONITOR_PERIOD) #5秒一次
    
    
        @set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
        def switch_feature_handle(self,ev):
            """
            datapath中有配置消息到达
            """
            LOCK.acquire()
            self.net_arrived += 1 #表示有1个交换机到达
            LOCK.release()
            print("------XXXXXXXXXXX------%d------XXXXXXXXXXX------------switch_feature_handle"%self.net_arrived)
    
            msg = ev.msg
            datapath = msg.datapath
            ofproto = datapath.ofproto
            ofp_parser = datapath.ofproto_parser
    
            match = ofp_parser.OFPMatch()
    
            actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]
    
            self.add_flow(datapath=datapath,priority=0,match=match,actions=actions,extra_info="config infomation arrived!!")
    
    
        def add_flow(self,datapath,priority,match,actions,idle_timeout=0,hard_timeout=0,extra_info=None):
            #print("------------------add_flow:")
            if extra_info != None:
                print(extra_info)
            ofproto = datapath.ofproto
            ofp_parser = datapath.ofproto_parser
    
            inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
    
            mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,
                                        idle_timeout=idle_timeout,
                                        hard_timeout=hard_timeout,
                                        match=match,instructions=inst)
            datapath.send_msg(mod);
    
        @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
        def packet_in_handler(self,ev):
            #print("------------------packet_in_handler")
            msg = ev.msg
            datapath = msg.datapath
            ofproto = datapath.ofproto
            ofp_parser = datapath.ofproto_parser
    
            dpid = datapath.id
            in_port = msg.match['in_port']
    
            pkt = packet.Packet(msg.data)
            eth_pkt = pkt.get_protocol(ethernet.ethernet)
            dst = eth_pkt.dst
            src = eth_pkt.src
    
            #self.logger.info("------------------Controller %s get packet, Mac address from: %s send to: %s , send from datapath: %s,in port is: %s"
            #                    ,dpid,src,dst,dpid,in_port)
            #self.get_topology(None)
    
    
        @set_ev_cls([event.EventHostAdd])
        def _host_add_handler(self,ev):    #主机信息单独处理,不属于网络拓扑
            self.host_list = get_host(self.topology_api_app) #3.需要使用pingall,主机通过与边缘交换机连接,才能告诉控制器
            #获取主机信息字典ip2host{ipv4:host object}  ip2switch{ipv4:dpid}
            for i,host in enumerate(self.host_list):
                self.ip2switch["%s"%host.ipv4] = host.port.dpid
                self.ip2host["%s"%host.ipv4] = host
    
    
        events = [event.EventSwitchEnter, event.EventSwitchLeave,
                   event.EventSwitchReconnected,
                   event.EventPortAdd, event.EventPortDelete,
                   event.EventPortModify,
                   event.EventLinkAdd, event.EventLinkDelete]
        @set_ev_cls(events)
        def get_topology(self,ev):
            if not self.net_arrived:
                return
            #print("------+++++++++++------%d------+++++++++++------------get_topology"%self.net_arrived)
    
            LOCK.acquire()
            self.net_arrived -= 1
            if self.net_arrived < 0:
                self.net_arrived = 0
            LOCK.release()
    
            self.net_flag = False
            self.net_topo = []
    
            print("-----------------get_topology")
            #获取所有的交换机、链路
            self.switch_list = get_switch(self.topology_api_app) #1.只要交换机与控制器联通,就可以获取
            self.link_list = get_link(self.topology_api_app) #2.在ryu启动时,加上--observe-links即可用于拓扑发现
            
            #获取交换机字典id2dpid{id:dpid} dpid2switch{dpid:switch object}
            for i,switch in enumerate(self.switch_list):
                self.id2dpid[i] = switch.dp.id
                self.dpid2id[switch.dp.id] = i
                self.dpid2switch[switch.dp.id] = switch
    
    
            #根据链路信息,开始获取拓扑信息
            self.net_size = len(self.id2dpid) #表示网络中交换机个数
            for i in range(self.net_size):
                self.net_topo.append([0]*self.net_size)
    
            for link in self.link_list:
                src_dpid = link.src.dpid
                src_port = link.src.port_no
    
                dst_dpid = link.dst.dpid
                dst_port = link.dst.port_no
    
                try:
                    sid = self.dpid2id[src_dpid]
                    did = self.dpid2id[dst_dpid]
                except KeyError as e:
                    print("--------------Error:get KeyError with link infomation(%s)"%e)
                    return
                self.net_topo[sid][did] = [src_port,1] #注意:这里1表示存在链路,后面可以修改为时延
                self.net_topo[did][sid] = [dst_port,1] #注意:修改为列表,不要用元组,元组无法修改,我们后面要修改时延
    
    
            self.net_flag = True #表示网络拓扑创建成功
    
        def show_topology(self):
            print("-----------------show_topology")
            print("----------switch network----------")
            line_info = "         "
            for i in range(self.net_size):
                line_info+="        s%-5d        "%self.id2dpid[i]
            print(line_info)
            for i in range(self.net_size):
                line_info = "s%d      "%self.id2dpid[i]
                for j in range(self.net_size):
                    if self.net_topo[i][j] == 0:
                        line_info+="%-22d"%0
                    else:
                        line_info+="(%d,%.12f)    "%tuple(self.net_topo[i][j])
                print(line_info)
    
            print("----------host 2 switch----------")
            for key,val in self.ip2switch.items():
                print("%s---s%d"%(key,val))
    View Code

    (二)模块导入

    from ryu.base import app_manager
    from ryu.base.app_manager import lookup_service_brick
    
    from ryu.ofproto import ofproto_v1_3
    
    from ryu.controller import ofp_event
    from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER,HANDSHAKE_DISPATCHER #只是表示datapath数据路径的状态
    from ryu.controller.handler import set_ev_cls
    
    from ryu.lib import hub
    from ryu.lib.packet import packet,ethernet
    
    from ryu.topology.switches import Switches
    from ryu.topology.switches import LLDPPacket
    
    import time

    (三)数据结构

    ECHO_REQUEST_INTERVAL = 0.05
    DELAY_DETECTING_PERIOD = 5
    
    class DelayDetect(app_manager.RyuApp):
        OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
        def __init__(self,*args,**kwargs):
            super(DelayDetect,self).__init__(*args,**kwargs)
            self.name = "delay"
    
            self.topology = lookup_service_brick("topology") #注意:我们使用lookup_service_brick加载模块实例时,对于我们自己定义的app,我们需要在类中定义self.name。
            self.switches = lookup_service_brick("switches") #此外,最重要的是:我们启动本模块DelayDetect时,必须同时启动自定义的模块!!! 比如:ryu-manager ./TopoDetect.py ./DelayDetect.py --verbose --observe-links
    
            self.dpid2switch = {} #或者直接为{},也可以。下面_state_change_handler也会添加进去
            self.dpid2echoDelay = {} #记录echo时延
    
            self.src_sport_dst2Delay = {} #记录LLDP报文测量的时延。实际上可以直接更新,这里单独记录,为了单独展示 {”src_dpid-srt_port-dst_dpid“:delay}
    
            self.detector_thread = hub.spawn(self._detector)

    (四)协程获取链路时延

        def _detector(self):
            """
            协程实现伪并发,探测链路时延
            """
            while True:
                if self.topology == None:
                    self.topology = lookup_service_brick("topology")
                if self.topology.net_flag:
                    print("------------------_detector------------------")
                    self._send_echo_request()
                    self.get_link_delay()
                    if self.topology.net_flag:
                        try:
                            self.show_delay()
                        except Exception as err:
                            print("------------------Detect delay failure!!!------------------")
                hub.sleep(DELAY_DETECTING_PERIOD) #5秒一次

    (五)获取Echo时延

        def _send_echo_request(self):
            """
            发生echo报文到datapath
            """
            for datapath in self.dpid2switch.values():
                parser = datapath.ofproto_parser
                echo_req = parser.OFPEchoRequest(datapath,data=bytes("%.12f"%time.time(),encoding="utf8")) #获取当前时间
    
                datapath.send_msg(echo_req)
    
                #重要!不要同时发送echo请求,因为它几乎同时会生成大量echo回复。
                #在echo_reply_处理程序中处理echo reply时,会产生大量队列等待延迟。
                hub.sleep(ECHO_REQUEST_INTERVAL)
    
        @set_ev_cls(ofp_event.EventOFPEchoReply,[MAIN_DISPATCHER,CONFIG_DISPATCHER,HANDSHAKE_DISPATCHER])
        def echo_reply_handler(self,ev):
            """
            处理echo响应报文,获取控制器到交换机的链路往返时延
    
                  Controller
                      |    
         echo latency |  
                     `|‘ 
                       Switch        
            """
            now_timestamp = time.time()
            try:
                echo_delay = now_timestamp - eval(ev.msg.data)
                self.dpid2echoDelay[ev.msg.datapath.id] = echo_delay
            except:
                return

    (六)获取LLDP时延

        @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
        def packet_in_handler(self,ev): #处理到达的LLDP报文,从而获得LLDP时延
            """
                          Controller
                        |        /|    
                       |/         |
                    Switch----->Switch
            """
            msg = ev.msg
            try:
                src_dpid,src_outport = LLDPPacket.lldp_parse(msg.data) #获取两个相邻交换机的源交换机dpid和port_no(与目的交换机相连的端口)
                dst_dpid = msg.datapath.id #获取目的交换机(第二个),因为来到控制器的消息是由第二个(目的)交换机上传过来的
                dst_inport = msg.match['in_port']
                if self.switches is None:
                    self.switches = lookup_service_brick("switches") #获取交换机模块实例
    
                #获得key(Port类实例)和data(PortData类实例)
                for port in self.switches.ports.keys(): #开始获取对应交换机端口的发送时间戳
                    if src_dpid == port.dpid and src_outport == port.port_no: #匹配key
                        port_data = self.switches.ports[port] #获取满足key条件的values值PortData实例,内部保存了发送LLDP报文时的timestamp信息
                        timestamp = port_data.timestamp
                        if timestamp:
                            delay = time.time() - timestamp
                            self._save_delay_data(src=src_dpid,dst=dst_dpid,src_port=src_outport,lldpdealy=delay)
            except:
                return
    
        def _save_delay_data(self,src,dst,src_port,lldpdealy):
            key = "%s-%s-%s"%(src,src_port,dst)
            self.src_sport_dst2Delay[key] = lldpdealy

    (七)根据LLDP和Echo时延,更新网络拓扑图中的权值信息

        def get_link_delay(self):
            """
            更新图中的权值信息
            """
            print("--------------get_link_delay-----------")
            for src_sport_dst in self.src_sport_dst2Delay.keys():
                    src,sport,dst = tuple(map(eval,src_sport_dst.split("-")))
                    if src in self.dpid2echoDelay.keys() and dst in self.dpid2echoDelay.keys():
                        sid,did = self.topology.dpid2id[src],self.topology.dpid2id[dst]
                        if self.topology.net_topo[sid][did] != 0:
                            if self.topology.net_topo[sid][did][0] == sport:
                                s_d_delay = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2;
                                if s_d_delay < 0: #注意:可能出现单向计算时延导致最后小于0,这是不允许的。则不进行更新,使用上一次原始值
                                    continue
                                self.topology.net_topo[sid][did][1] = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2

    (八)显示网络拓扑图和Echo、LLDP时延信息

        @set_ev_cls(ofp_event.EventOFPStateChange,[MAIN_DISPATCHER, DEAD_DISPATCHER])
        def _state_change_handler(self, ev):
            datapath = ev.datapath
            if ev.state == MAIN_DISPATCHER:
                if not datapath.id in self.dpid2switch:
                    self.logger.debug('Register datapath: %016x', datapath.id)
                    self.dpid2switch[datapath.id] = datapath
            elif ev.state == DEAD_DISPATCHER:
                if datapath.id in self.dpid2switch:
                    self.logger.debug('Unregister datapath: %016x', datapath.id)
                    del self.dpid2switch[datapath.id]
    
            if self.topology == None:
                self.topology = lookup_service_brick("topology")
            print("-----------------------_state_change_handler-----------------------")
            print(self.topology.show_topology())
            print(self.switches)
    
        def show_delay(self):
            print("-----------------------show echo delay-----------------------")
            for key,val in self.dpid2echoDelay.items():
                print("s%d----%.12f"%(key,val))
            print("-----------------------show LLDP delay-----------------------")
            for key,val in self.src_sport_dst2Delay.items():
                print("%s----%.12f"%(key,val))

    (九)全部代码

    from ryu.base import app_manager
    from ryu.base.app_manager import lookup_service_brick
    
    from ryu.ofproto import ofproto_v1_3
    
    from ryu.controller import ofp_event
    from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER,HANDSHAKE_DISPATCHER #只是表示datapath数据路径的状态
    from ryu.controller.handler import set_ev_cls
    
    from ryu.lib import hub
    from ryu.lib.packet import packet,ethernet
    
    from ryu.topology.switches import Switches
    from ryu.topology.switches import LLDPPacket
    
    import time
    
    ECHO_REQUEST_INTERVAL = 0.05
    DELAY_DETECTING_PERIOD = 5
    
    class DelayDetect(app_manager.RyuApp):
        OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
        def __init__(self,*args,**kwargs):
            super(DelayDetect,self).__init__(*args,**kwargs)
            self.name = "delay"
    
            self.topology = lookup_service_brick("topology") #注意:我们使用lookup_service_brick加载模块实例时,对于我们自己定义的app,我们需要在类中定义self.name。
            self.switches = lookup_service_brick("switches") #此外,最重要的是:我们启动本模块DelayDetect时,必须同时启动自定义的模块!!! 比如:ryu-manager ./TopoDetect.py ./DelayDetect.py --verbose --observe-links
    
            self.dpid2switch = {} #或者直接为{},也可以。下面_state_change_handler也会添加进去
            self.dpid2echoDelay = {}
    
            self.src_sport_dst2Delay = {} #记录LLDP报文测量的时延。实际上可以直接更新,这里单独记录,为了单独展示 {”src_dpid-srt_port-dst_dpid“:delay}
    
            self.detector_thread = hub.spawn(self._detector)
    
        def _detector(self):
            """
            协程实现伪并发,探测链路时延
            """
            while True:
                if self.topology == None:
                    self.topology = lookup_service_brick("topology")
                if self.topology.net_flag:
                    print("------------------_detector------------------")
                    self._send_echo_request()
                    self.get_link_delay()
                    if self.topology.net_flag:
                        try:
                            self.show_delay()
                        except Exception as err:
                            print("------------------Detect delay failure!!!------------------")
                hub.sleep(DELAY_DETECTING_PERIOD) #5秒一次
    
        def get_link_delay(self):
            """
            更新图中的权值信息
            """
            print("--------------get_link_delay-----------")
            for src_sport_dst in self.src_sport_dst2Delay.keys():
                    src,sport,dst = tuple(map(eval,src_sport_dst.split("-")))
                    if src in self.dpid2echoDelay.keys() and dst in self.dpid2echoDelay.keys():
                        sid,did = self.topology.dpid2id[src],self.topology.dpid2id[dst]
                        if self.topology.net_topo[sid][did] != 0:
                            if self.topology.net_topo[sid][did][0] == sport:
                                s_d_delay = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2;
                                if s_d_delay < 0: #注意:可能出现单向计算时延导致最后小于0,这是不允许的。则不进行更新,使用上一次原始值
                                    continue
                                self.topology.net_topo[sid][did][1] = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2
    
    
        def _send_echo_request(self):
            """
            发生echo报文到datapath
            """
            for datapath in self.dpid2switch.values():
                parser = datapath.ofproto_parser
                echo_req = parser.OFPEchoRequest(datapath,data=bytes("%.12f"%time.time(),encoding="utf8")) #获取当前时间
    
                datapath.send_msg(echo_req)
    
                #重要!不要同时发送echo请求,因为它几乎同时会生成大量echo回复。
                #在echo_reply_处理程序中处理echo reply时,会产生大量队列等待延迟。
                hub.sleep(ECHO_REQUEST_INTERVAL)
    
        @set_ev_cls(ofp_event.EventOFPEchoReply,[MAIN_DISPATCHER,CONFIG_DISPATCHER,HANDSHAKE_DISPATCHER])
        def echo_reply_handler(self,ev):
            """
            处理echo响应报文,获取控制器到交换机的链路往返时延
    
                  Controller
                      |    
         echo latency |  
                     `|‘ 
                       Switch        
            """
            now_timestamp = time.time()
            try:
                echo_delay = now_timestamp - eval(ev.msg.data)
                self.dpid2echoDelay[ev.msg.datapath.id] = echo_delay
            except:
                return
    
    
        @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
        def packet_in_handler(self,ev): #处理到达的LLDP报文,从而获得LLDP时延
            """
                          Controller
                        |        /|    
                       |/         |
                    Switch----->Switch
            """
            msg = ev.msg
            try:
                src_dpid,src_outport = LLDPPacket.lldp_parse(msg.data) #获取两个相邻交换机的源交换机dpid和port_no(与目的交换机相连的端口)
                dst_dpid = msg.datapath.id #获取目的交换机(第二个),因为来到控制器的消息是由第二个(目的)交换机上传过来的
                dst_inport = msg.match['in_port']
                if self.switches is None:
                    self.switches = lookup_service_brick("switches") #获取交换机模块实例
    
                #获得key(Port类实例)和data(PortData类实例)
                for port in self.switches.ports.keys(): #开始获取对应交换机端口的发送时间戳
                    if src_dpid == port.dpid and src_outport == port.port_no: #匹配key
                        port_data = self.switches.ports[port] #获取满足key条件的values值PortData实例,内部保存了发送LLDP报文时的timestamp信息
                        timestamp = port_data.timestamp
                        if timestamp:
                            delay = time.time() - timestamp
                            self._save_delay_data(src=src_dpid,dst=dst_dpid,src_port=src_outport,lldpdealy=delay)
            except:
                return
    
        def _save_delay_data(self,src,dst,src_port,lldpdealy):
            key = "%s-%s-%s"%(src,src_port,dst)
            self.src_sport_dst2Delay[key] = lldpdealy
    
        @set_ev_cls(ofp_event.EventOFPStateChange,[MAIN_DISPATCHER, DEAD_DISPATCHER])
        def _state_change_handler(self, ev):
            datapath = ev.datapath
            if ev.state == MAIN_DISPATCHER:
                if not datapath.id in self.dpid2switch:
                    self.logger.debug('Register datapath: %016x', datapath.id)
                    self.dpid2switch[datapath.id] = datapath
            elif ev.state == DEAD_DISPATCHER:
                if datapath.id in self.dpid2switch:
                    self.logger.debug('Unregister datapath: %016x', datapath.id)
                    del self.dpid2switch[datapath.id]
    
            if self.topology == None:
                self.topology = lookup_service_brick("topology")
            print("-----------------------_state_change_handler-----------------------")
            print(self.topology.show_topology())
            print(self.switches)
    
        def show_delay(self):
            print("-----------------------show echo delay-----------------------")
            for key,val in self.dpid2echoDelay.items():
                print("s%d----%.12f"%(key,val))
            print("-----------------------show LLDP delay-----------------------")
            for key,val in self.src_sport_dst2Delay.items():
                print("%s----%.12f"%(key,val))
    View Code

    四:实验测试

    (一)启动Ryu

    ryu-manager ./TopoDetect.py ./DelayDetect.py --verbose --observe-links

    (二)启动mininet

    sudo mn --topo=linear,4 --switch=ovsk --controller=remote --link=tc

    注意:需要在mininet中使用pingall,才能使得交换机获得host存在,从而使得控制器获取host消息!!

     

    (三)结果显示

  • 相关阅读:
    Docker
    springboot与缓存
    微信小程序资源
    Docker的使用及注意事项
    xml解析
    Intellij Idea2018破解教程(激活到2100年)
    natapp内网映射
    HEAD detached from XXX
    JSON语法
    关于苹果、奔驰、杜蕾斯这些红极一时的品牌
  • 原文地址:https://www.cnblogs.com/ssyfj/p/14191693.html
Copyright © 2011-2022 走看看