SDN Expriment2——动态转发规则和链路故障恢复

1 实验题目

部署一个如下图的网络环境,在此拓扑的基础上完成动态转发规则的改变和链路故障恢复功能。
在这里插入图片描述

  • 实验资料下载
    https://www.aliyundrive.com/s/Lycd3MP5CbZ

2 实验内容

2.1 动态改变转发规则

在上图所示的拓扑结构中,h1到h2有两条通路,所谓动态改变转发规则,就是要让h1到h2的包在两条路径上交替转发,具体描述如下:
假设h1 ping h2,初始路由规则为s1-s4-s5,5秒后,路由转发规则变为s1-s2-s3-s5,再过5秒,转发规则又回到最初的s1-s4-s5,通过这个循环调度的例子动态改变交换机的转发规则。

2.1.1 实现思路

因为实验拓扑比较简单,仅存在两个主机,并且两主机之间有且仅存在两条路径。每经过5秒,install_path()中选择另外一条path即可。

2.1.2 具体实现

  • hard_timeout为硬超时,指流标下发之后开始,经过指定时间之后无条件将流表删除。设置hard_timeout=5,5秒后交换机上的流表就会被删除,重新向控制器询问如何转发。
  • 求取最长路径。选择将short_path()中的w=1改成w=-1,即为最长路径求取函数。
  • 设置全局变量,确定在下发流表时,选择长路径还是短路径。记录第一次下发流表时间firstTime,在之后下发流表时计算当前时间和fistTime的时间差,从而确定选择装载的路径。

2.1.3 测试截图

  • 启动控制器
sudo ryu-manager dynamic_rules.py --observe-links

在这里插入图片描述

  • 启动加载自定义拓扑文件
sudo mn --custom SDNexp2Topo.py --topo mytopo --controller remote

在这里插入图片描述

  • xterm h1打开h1终端,使其不断地ping h2

  • 观察路径随时间变化

2.2 链路故障恢复功能

在上图所示的拓扑结构中,h1到h2有两条通路,若其中正在进行传输的路径因为发生故障而断开连接,系统应当及时做出反应,改变转发规则到另外一条路径上;若故障修复,系统也应当即时做出反应,改变转发规则到优先级较高的路径上
假设h1 ping h2,首选的路由规则为s1-s4-s5,由于故障,s1-s4之间的链路被断开,系统应当将转发规则改变为备选路径s1-s2-s3-s5,若此时s1-s4之间的故障被修复,链路恢复连接,系统应该将路径重新确定为首选路径s1-s4-s5
本实验中,路径的优先级有链路上的跳数决定,跳数越少的优先级越高

2.2.1 实现思路

当链路改变时,删除现有流表,重新计算最短路径,重新生成流表。

2.2.2 具体实现

  • 修改install_path()函数,使其始终获取short_path()的结果,以达到始终选取当前拓扑中最短路径的效果。
  • 增加删除流表函数。参考此博客
def delete_flow(self, datapath,match):
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        cookie = cookie_mask = 0
        table_id = 0
        priority=100 # 使用最短路,priority并没有什么用
        idle_timeout = 10
        hard_timeout = 60
        buffer_id = ofp.OFP_NO_BUFFER
        #match = ofp_parser.OFPMatch(in_port=3, eth_type=flow_info[0], ipv4_src="10.0.0.1", ipv4_dst="10.0.0.2")
        actions = []
        inst = []
        req = ofp_parser.OFPFlowMod(datapath, 
                                cookie, cookie_mask, table_id, 
                                ofp.OFPFC_DELETE, idle_timeout, 
                                hard_timeout, priority, buffer_id, 
                                ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, 
                                match, inst)
        datapath.send_msg(req)
  • 当链路发生改变时,删除旧流表项,重新获取最短路径,重新安装流表。
def get_OFPPortStatus_msg(self, ev):
        msg=ev.msg
        datapath=ev.msg.datapath
        dpid = msg.datapath.id
        ofproto=datapath.ofproto
        parser = datapath.ofproto_parser 

        # self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port) #{ip:(dpid,port)}
        for ip in self.ip_to_port:
            # self.logger.info(ip, str(self.ip_to_port[ip]))
            if self.ip_to_port[ip] == self.path[-1]:
                dstip=ip
                dst=self.ip_to_mac[dstip]

        for ip in self.ip_to_port:
            if self.ip_to_port[ip] == self.path[0]:
                srcip=ip
                src=self.ip_to_mac[srcip]
        
        # for example,path=[(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
        # 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出
        for i in range(len(self.path) - 2, -1, -2):
            datapath_path = self.datapaths[self.path[i][0]]

            match1 = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
                                    ipv4_src=srcip, ipv4_dst=dstip)
            self.delete_flow(datapath=datapath_path,match=match1)
            match2 = parser.OFPMatch(in_port=self.path[i+1][1], eth_src=dst, eth_dst=src, eth_type=0x0800,
                                    ipv4_src=dstip, ipv4_dst=srcip)
            self.delete_flow(datapath=datapath_path,match=match2)

2.2.3 测试截图

  • 启动控制器
sudo ryu-manager recovery_rules.py --observe-links

在这里插入图片描述

  • 启动加载自定义拓扑文件
sudo mn --custom SDNexp2Topo.py --topo mytopo --controller remote

在这里插入图片描述

  • xterm h1打开h1终端,使其不断地ping h2

  • 输入link s1 s4 downlink s1 s4 up,会发现路径变化

  • 查看s1流表
    在这里插入图片描述

3 代码

3.1 拓扑

# SDNexp2Topo.py
from mininet.topo import Topo  
from mininet.net import Mininet  
from mininet.node import RemoteController  
from mininet.node import Node  
from mininet.node import CPULimitedHost  
from mininet.link import TCLink  
from mininet.cli import CLI  

class MyTopo(Topo):
 
    def __init__(self):
        super(MyTopo,self).__init__()
        s=[]
        for i in range(5):
            s_sw=self.addSwitch('s{}'.format(i+1))
            s.append(s_sw)
        self.addLink(s[0],s[1])
        self.addLink(s[1],s[2])
        self.addLink(s[2],s[4])
        self.addLink(s[0],s[3])
        self.addLink(s[3],s[4])

        hs = self.addHost('h{}'.format(1))
        self.addLink(hs,s[0])
        hs = self.addHost('h{}'.format(2))
        self.addLink(hs,s[4])
        
topos = {"mytopo":(lambda:MyTopo())}

3.2 依赖文件(network_monitor.py)

本实验提供两个文件dynamic_rules.py和network_monitor.py,后者是前者在运行时所需要用到的文件,两者必须放在同一文件夹下

# network_monitor.py
from __future__ import division

from collections import defaultdict
from operator import attrgetter
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib import hub
from ryu.lib.packet import packet
from collections import defaultdict
import time

SLEEP_PERIOD = 2
ISOTIMEFORMAT='%Y-%m-%d %X'


class Network_Monitor(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _NAME = 'Network_Monitor'

    def __init__(self, *args, **kwargs):
        super(Network_Monitor, self).__init__(*args, **kwargs)

        self.datapaths = {}
        self.port_stats = {}
        self.port_speed = {}
        self.flow_stats = {}
        self.flow_speed = {}
        self.get_flow_speed_dict = {}
        self.stats = {}
        self.DpidPort_to_ip = {}

        self.monitor_thread = hub.spawn(self._monitor)

    @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.datapaths:
                self.datapaths[datapath.id] = datapath
        elif ev.state == DEAD_DISPATCHER:
            if datapath.id in self.datapaths:
                del self.datapaths[datapath.id]

    def _monitor(self):
        while True:
            self.stats['port'] = defaultdict(lambda: None)
            self.stats['flow'] = defaultdict(lambda: None)
            for datapath in self.datapaths.values():
                self._request_stats(datapath)
            hub.sleep(SLEEP_PERIOD)
            # self.logger.info("port speed : %s", self.get_port_speed(1, 2))


    def _request_stats(self, datapath):
        self.logger.debug('send stats request: %016x', datapath.id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # The controller uses this message to query information about ports statistics.
        req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
        datapath.send_msg(req)

        # The controller uses this message to query individual flow statistics.
        req = parser.OFPFlowStatsRequest(datapath)
        datapath.send_msg(req)

    def _save_stats(self, dist, key, value, length):
        if key not in dist:
            dist[key] = []
        dist[key].append(value)

        if len(dist[key]) > length:
            dist[key].pop(0)

    def _get_speed(self, now, pre, period):
        if period:
            return (now - pre) / period
        else:
            return 0

    def _get_time(self, sec, nsec):
        return sec + nsec / (10 ** 9)

    def _get_period(self, n_sec, n_nsec, p_sec, p_nsec):
        return self._get_time(n_sec, n_nsec) - self._get_time(p_sec, p_nsec)

    def get_port_speed(self, sw_src=None, src_port=None):
        if sw_src is None or src_port is None:
            return self.port_speed
        return self.port_speed.get((sw_src, src_port), (None, None))



    # def get_port_speed(self, dpid=None, port=None):
    #     if dpid is None or port is None:
    #         return self.port_speed
    #     return self.port_speed.get((dpid, port), None)
    #
    # def get_flow_speed(self, dpid=None):
    #     if dpid is None:
    #         return self.flow_speed
    #     else:
    #         return self.flow_speed[dpid]
    # EventOFPFlowStatsReply: switch will reply all of its flows (on flow table) to controller
    @set_ev_cls(ofp_event.EventOFPFlowStatsReply, [MAIN_DISPATCHER, DEAD_DISPATCHER])
    def _flow_stats_reply_handler(self, ev):
        body = ev.msg.body
        dpid = ev.msg.datapath.id
        self.stats['flow'][dpid] = body
        self.flow_stats.setdefault(dpid, {})
        self.flow_speed.setdefault(dpid, {})
        flow_list = {}
        for flow in body:
            if (flow.priority != 0) and (flow.priority != 65535):
                key = (flow.match.get('in_port', 0), flow.match.get('ipv4_src', 'all'), flow.match.get('ipv4_dst', 'all'))
                value = (flow.packet_count, flow.byte_count, flow.duration_sec, flow.duration_nsec)
                flow_list[key] = value
                # 从每一条流表项中取出outport消息
                out_port = flow.instructions[0].actions[-1].port
                key1 = (dpid, out_port)
                value1 = (flow.match.get('ipv4_src', 'all'), flow.match.get('ipv4_dst', 'all'))
                self.DpidPort_to_ip[key1] = value1
                # self.logger.info("tmp : %s", str(tmp))
                # self.logger.info("flow : %s", str(flow))
                # self.logger.info("DpidPort_to_ip : %s", str(self.DpidPort_to_ip))
        # self.logger.info("flow_list : %s", str(flow_list))
        # have delete the flow_list in switch, so the key in flow_stats is not equal flow_list
        for key in self.flow_stats[dpid]:
            if key not in flow_list:
                # the flow has been delete
                #self.logger.info("key : %s", str(key))
                value = (0, 0, 0, 0)
                self._save_stats(self.flow_stats[dpid], key, value, 20)
                self._save_stats(self.flow_speed[dpid], key, 0, 20)

        for key in flow_list:
            self._save_stats(self.flow_stats[dpid], key, flow_list[key], 20)
            # Get flow's speed.
            pre = 0
            period = SLEEP_PERIOD

            tmp = self.flow_stats[dpid][key]
            if len(tmp) > 1:
                pre = tmp[-2][1]
                period = self._get_period(tmp[-1][2], tmp[-1][3], tmp[-2][2], tmp[-2][3])
            speed = self._get_speed(self.flow_stats[dpid][key][-1][1], pre, period) * 8
            # self.logger.info("flow_speed: %s", speed)
            self.save_flow_speed(dpid, key[1], key[2], speed)
            self._save_stats(self.flow_speed[dpid], key, speed, 20)
            # self.logger.info("monitor get_flow_speed_dict address: %s", id(self.get_flow_speed_dict))

        for key in self.flow_stats[dpid]:
            if key not in flow_list:
                temp_key = (dpid, key[1], key[2])
                if self.get_flow_speed_dict.get(temp_key) is not None:
                    del self.get_flow_speed_dict[temp_key]
            # self.logger.info("get_flow_speed_dict : %s", self.get_flow_speed_dict)

    def save_flow_speed(self, dpid, src_ip, dst_ip, speed):
        # judge the key???
        key = (dpid, src_ip, dst_ip)
        value = speed
        self.get_flow_speed_dict[key] = value

    # def get_flow_speed(self, dpid, src_ip, dst_ip):
    #     if dpid and src_ip and dst_ip:
    #         key = (dpid, src_ip, dst_ip)
    #         #self.logger.info("flow_speed_dict: %s", self.get_flow_speed_dict.keys())
    #         return self.get_flow_speed_dict.keys()
    #
    #     else:
    #         return

    @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
    def _port_stats_reply_handler(self, ev):
        body = ev.msg.body
        self.stats['port'][ev.msg.datapath.id] = body
        for stat in sorted(body, key=attrgetter('port_no')):
            if stat.port_no != ofproto_v1_3.OFPP_LOCAL:
                key = (ev.msg.datapath.id, stat.port_no)
                value = (stat.tx_bytes, stat.rx_bytes, stat.rx_errors, stat.duration_sec, stat.duration_nsec)

                self._save_stats(self.port_stats, key, value, 5)

                # Get port speed.
                pre = 0
                period = SLEEP_PERIOD
                tmp = self.port_stats[key]
                if len(tmp) > 1:
                    pre = tmp[-2][0]
                    period = self._get_period(tmp[-1][3], tmp[-1][4], tmp[-2][3], tmp[-2][4])

                speed = self._get_speed(self.port_stats[key][-1][0], pre, period)

                # Downlink bandwidth
                # self.port_speed = {(dpid,port):speed}
                # speed bps
                self.port_speed[key] = (speed * 8, time.strftime( ISOTIMEFORMAT, time.localtime()))

       # self.logger.info("port speed : %s", str(self.port_speed))

3.3 动态改变转发规则

network_monitor.py是dynamic_rules.py在运行时所需要用到的文件,两者必须放在同一文件夹下

# dynamic_rules.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.topology import event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_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 arp
from ryu.lib.packet import ipv4
from ryu.lib.packet import tcp
from ryu.topology.api import get_link
from ryu.lib.packet import ether_types
from ryu.app.wsgi import  WSGIApplication
from collections import defaultdict
from datetime import datetime,timedelta
import network_monitor
class dynamic_rules(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _CONTEXTS = {
        "Network_Monitor": network_monitor.Network_Monitor,
        "wsgi": WSGIApplication
    }
    def __init__(self, *args, **kwargs):
        super(dynamic_rules, self).__init__(*args, **kwargs)
        # 增加一些全局变量
        self.firstrequestFlag=False # 记录是否下发过流表
        self.firstrequestTime=datetime.now() # 记录第一次下发流表的时间

        self.mac_to_port = {}
        self.ip_to_mac = {}
        self.mac_to_dpid = {}  # {mac:(dpid,port)}

        self.datapaths = defaultdict(lambda: None)
        self.topology_api_app = self
        self.src_links = defaultdict(lambda: defaultdict(lambda: None))

        self.check_ip_dpid = defaultdict(list)

        self.qos_ip_bw_list = []

        self.network_monitor = kwargs["Network_Monitor"]

        
        self.ip_to_switch = {}
        self.port_name_to_num = {}

        self.ip_to_port = {}  #{ip:(dpid,port)}
        #promise me, use it well :)
        self.pathmod = 0
        self.path = None

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        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, buffer_id=None, idle_timeout=0, hard_timeout=0):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
        if buffer_id:
            mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
                                    priority=priority, match=match,
                                    idle_timeout=idle_timeout,
                                    hard_timeout=hard_timeout,
                                    instructions=inst)
        else:
            mod = 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):

        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)
        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]
        pkt_arp = pkt.get_protocol(arp.arp)
        pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
        pkt_tcp = pkt.get_protocol(tcp.tcp)

        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            return

        dst = eth.dst
        src = eth.src
        dpid = datapath.id

        # self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

        # in rest_topology, self.mac_to_port is for the find for host
        self.mac_to_port.setdefault(dpid, {})
        self.mac_to_port[dpid][src] = in_port

        # arp handle
        if pkt_arp and pkt_arp.opcode == arp.ARP_REQUEST:
            if pkt_arp.src_ip not in self.ip_to_mac:
                self.ip_to_mac[pkt_arp.src_ip] = src
                self.mac_to_dpid[src] = (dpid, in_port)
                self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)

            if pkt_arp.dst_ip in self.ip_to_mac:
                #self.logger.info("[PACKET] ARP packet_in.")
                self.handle_arpre(datapath=datapath, port=in_port,
                                  src_mac=self.ip_to_mac[pkt_arp.dst_ip],
                                  dst_mac=src, src_ip=pkt_arp.dst_ip, dst_ip=pkt_arp.src_ip)
            else:
                # to avoid flood when the dst ip not in the network
                if datapath.id not in self.check_ip_dpid[pkt_arp.dst_ip]:
                    self.check_ip_dpid[pkt_arp.dst_ip].append(datapath.id)
                    out_port = ofproto.OFPP_FLOOD
                    actions = [parser.OFPActionOutput(out_port)]
                    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)
            return

        elif pkt_arp and pkt_arp.opcode == arp.ARP_REPLY:
            if pkt_arp.src_ip not in self.ip_to_mac:
                self.ip_to_mac[pkt_arp.src_ip] = src
                self.mac_to_dpid[src] = (dpid, in_port)
                self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
            dst_mac = self.ip_to_mac[pkt_arp.dst_ip]
            (dst_dpid, dst_port) = self.mac_to_dpid[dst_mac]
            self.handle_arpre(datapath=self.datapaths[dst_dpid], port=dst_port, src_mac=src, dst_mac=dst_mac,
                              src_ip=pkt_arp.src_ip, dst_ip=pkt_arp.dst_ip)
            return

        if pkt_ipv4 and (self.ip_to_port.get(pkt_ipv4.dst)) and (self.ip_to_port.get(pkt_ipv4.src)):
            (src_dpid, src_port) = self.ip_to_port[pkt_ipv4.src]  # src dpid and port
            (dst_dpid, dst_port) = self.ip_to_port[pkt_ipv4.dst]  # dst dpid and port
            self.install_path(src_dpid=src_dpid, dst_dpid=dst_dpid, src_port=src_port, dst_port=dst_port,
                              ev=ev, src=src, dst=dst, pkt_ipv4=pkt_ipv4, pkt_tcp=pkt_tcp)
    def send_pkt(self, datapath, port, pkt):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        pkt.serialize()
        data = pkt.data
        actions = [parser.OFPActionOutput(port=port)]
        out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER,
                                  actions=actions, data=data)
        datapath.send_msg(out)

    def handle_arpre(self, datapath, port, src_mac, dst_mac, src_ip, dst_ip):
        pkt = packet.Packet()
        pkt.add_protocol(ethernet.ethernet(ethertype=0x0806, dst=dst_mac, src=src_mac))
        pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY, src_mac=src_mac, src_ip=src_ip, dst_mac=dst_mac, dst_ip=dst_ip))
        self.send_pkt(datapath, port, pkt)
    def install_path(self, src_dpid, dst_dpid, src_port, dst_port, ev, src, dst, pkt_ipv4, pkt_tcp):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        if not self.firstrequestFlag:
            self.firstrequestFlag=True
            self.firstrequestTime=datetime.now()
        nowTime=datetime.now()
        gap=(nowTime-self.firstrequestTime)//(timedelta(seconds=5)) # 时间差
        # print(self.short_path(src=1, dst=5))
        # print(self.short_path(src=5, dst=1))
        # print(self.long_path(src=1, dst=5))
        # print(self.long_path(src=5, dst=1))
        mid_path = None
        if gap%2==0:
            self.logger.info("short_path:")
            mid_path = self.short_path(src=src_dpid, dst=dst_dpid)
            #print(mid_path)
        else:
            self.logger.info("long_path:")
            mid_path=self.long_path(src=src_dpid, dst=dst_dpid)
            #print(mid_path)
        # mid_path = self.short_path(src=src_dpid, dst=dst_dpid)

        if mid_path is None:
            return
        self.path = None
        self.path = [(src_dpid, src_port)] + mid_path + [(dst_dpid, dst_port)]

        self.logger.info("path : %s", str(self.path))
        
        for i in range(len(self.path) - 2, -1, -2):
            datapath_path = self.datapaths[self.path[i][0]]
            match = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
                                    ipv4_src=pkt_ipv4.src, ipv4_dst=pkt_ipv4.dst)

            if i < (len(self.path) - 2):
                actions = [parser.OFPActionOutput(self.path[i + 1][1])]
            else:
                actions = [parser.OFPActionSetField(eth_dst=self.ip_to_mac.get(pkt_ipv4.dst)),
                            parser.OFPActionOutput(self.path[i + 1][1])]
            
            self.add_flow(datapath_path, 100, match, actions, idle_timeout=0, hard_timeout=5)
        # time_install = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        # self.logger.info("time_install: %s", time_install)

    def short_path(self, src, dst, bw=0):
        if src == dst:
            return []
        result = defaultdict(lambda: defaultdict(lambda: None))
        distance = defaultdict(lambda: None)

        # the node is checked
        seen = [src]

        # the distance to src
        distance[src] = 0

        w = 1  # weight
        while len(seen) < len(self.src_links):
            node = seen[-1]
            if node == dst:
                break
            for (temp_src, temp_dst) in self.src_links[node]:
                if temp_dst not in seen:
                    temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
                    temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
                    if (distance[temp_dst] is None) or ( distance[temp_dst] > distance[temp_src] + w ):
                        distance[temp_dst] = distance[temp_src] + w
                        # result = {"dpid":(link_src, src_port, link_dst, dst_port)}
                        result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
            min_node = None
            min_path = 999
            # get the min_path node
            for temp_node in distance:
                if (temp_node not in seen) and (distance[temp_node] is not None):
                    if distance[temp_node] < min_path:
                        min_node = temp_node
                        min_path = distance[temp_node]
            if min_node is None:
                break
            seen.append(min_node)

        path = []

        if dst not in result:
            return None

        while (dst in result) and (result[dst] is not None):
            path = [result[dst][2:4]] + path
            path = [result[dst][0:2]] + path
            dst = result[dst][0]
        #self.logger.info("path : %s", str(path))
        return path

    def long_path(self, src, dst, bw=0):
        if src == dst:
            return []
        if src==1 and dst==5:
            return [(1,2),(2,1),(2,2),(3,1),(3,2),(5,2)]
        if src==5 and dst==1:
            return [(5,2),(3,2),(3,1),(2,2),(2,1),(1,2)]
        result = defaultdict(lambda: defaultdict(lambda: None))
        distance = defaultdict(lambda: None)

        # the node is checked
        seen = [src]

        # the distance to src
        distance[src] = 0

        w = -1  # weight
        while len(seen) < len(self.src_links):
            node = seen[-1]
            if node == dst:
                break
            for (temp_src, temp_dst) in self.src_links[node]:
                if temp_dst not in seen:
                    temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
                    temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
                    if (distance[temp_dst] is None) or (distance[temp_dst] > distance[temp_src] + w):
                        distance[temp_dst] = distance[temp_src] + w
                        # result = {"dpid":(link_src, src_port, link_dst, dst_port)}
                        result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
            min_node = None
            min_path = 999
            # get the min_path node
            for temp_node in distance:
                if (temp_node not in seen) and (distance[temp_node] is not None):
                    if distance[temp_node] < min_path:
                        min_node = temp_node
                        min_path = distance[temp_node]
            if min_node is None:
                break
            seen.append(min_node)

        path = []

        if dst not in result:
            return None
        while (dst in result) and (result[dst] is not None):
            path = [result[dst][2:4]] + path
            path = [result[dst][0:2]] + path
            dst = result[dst][0]
        #self.logger.info("path : %s", str(path))
        return path
    # this function might be useful, but who knows anyway
    # def long_path(self, src, dst, bw=0):

    @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 datapath.id not in self.datapaths:
                self.datapaths[datapath.id] = datapath
        elif ev.state == DEAD_DISPATCHER:
            if datapath.id in self.datapaths:
                del self.datapaths[datapath.id]
        #self.logger.info("datapaths : %s", self.datapaths)
    
    @set_ev_cls([event.EventSwitchEnter, event.EventSwitchLeave, event.EventPortAdd, event.EventPortDelete,
        event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete])
    def get_topology(self, ev):
        links_list = get_link(self.topology_api_app, None)
        self.src_links.clear()
        for link in links_list:
            sw_src = link.src.dpid
            sw_dst = link.dst.dpid
            src_port = link.src.port_no
            dst_port = link.dst.port_no
            src_port_name = link.src.name
            dst_port_name = link.dst.name
            self.port_name_to_num[src_port_name] = src_port
            self.port_name_to_num[dst_port_name] = dst_port
            self.src_links[sw_src][(sw_src, sw_dst)] = (src_port, dst_port)
            self.src_links[sw_dst][(sw_dst, sw_src)] = (dst_port, src_port)
            #self.logger.info("****src_port_name : %s", str(src_port_name))
            #self.logger.info("src_links : %s", str(self.src_links))
            #self.logger.info("port_name_to_num : %s", str(self.port_name_to_num))

    # these two functions need to be coded in your own way
    #def delete_flow(self, datapath, priority, match, actions, idle_timeout=10, hard_timeout=60):

    #@set_ev_cls(ofp_event.EventOFPPortStatus, [CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_DISPATCHER])
    #def get_OFPPortStatus_msg(self, ev):

3.4 链路故障恢复功能

network_monitor.py是recovery_rules.py在运行时所需要用到的文件,两者必须放在同一文件夹下

# recovery_rules.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.topology import event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_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 arp
from ryu.lib.packet import ipv4
from ryu.lib.packet import tcp
from ryu.topology.api import get_link
from ryu.lib.packet import ether_types
from ryu.app.wsgi import  WSGIApplication
from collections import defaultdict
from datetime import datetime,timedelta
import network_monitor
class dynamic_rules(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _CONTEXTS = {
        "Network_Monitor": network_monitor.Network_Monitor,
        "wsgi": WSGIApplication
    }
    def __init__(self, *args, **kwargs):
        super(dynamic_rules, self).__init__(*args, **kwargs)
        self.mac_to_port = {}
        self.ip_to_mac = {}
        self.mac_to_dpid = {}  # {mac:(dpid,port)}

        self.datapaths = defaultdict(lambda: None)
        self.topology_api_app = self
        self.src_links = defaultdict(lambda: defaultdict(lambda: None))

        self.check_ip_dpid = defaultdict(list)

        self.qos_ip_bw_list = []

        self.network_monitor = kwargs["Network_Monitor"]

        
        self.ip_to_switch = {}
        self.port_name_to_num = {}

        self.ip_to_port = {}  #{ip:(dpid,port)}
        #promise me, use it well :)
        self.pathmod = 0 #0:short,1:long
        self.path = None # {(dpid,port)}
        # [(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
        # 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        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, buffer_id=None, idle_timeout=0, hard_timeout=0):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
        if buffer_id:
            mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
                                    priority=priority, match=match,
                                    idle_timeout=idle_timeout,
                                    hard_timeout=hard_timeout,
                                    instructions=inst)
        else:
            mod = 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):

        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)
        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]
        pkt_arp = pkt.get_protocol(arp.arp)
        pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
        pkt_tcp = pkt.get_protocol(tcp.tcp)

        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            return


        dst = eth.dst
        src = eth.src
        dpid = datapath.id
        #print ("pac in")

        # self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

        # in rest_topology, self.mac_to_port is for the find for host
        self.mac_to_port.setdefault(dpid, {})
        self.mac_to_port[dpid][src] = in_port

        # arp handle
        if pkt_arp and pkt_arp.opcode == arp.ARP_REQUEST:
            if pkt_arp.src_ip not in self.ip_to_mac:
                self.ip_to_mac[pkt_arp.src_ip] = src
                self.mac_to_dpid[src] = (dpid, in_port)
                self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)

            if pkt_arp.dst_ip in self.ip_to_mac:
                #self.logger.info("[PACKET] ARP packet_in.")
                self.handle_arpre(datapath=datapath, port=in_port,
                                  src_mac=self.ip_to_mac[pkt_arp.dst_ip],
                                  dst_mac=src, src_ip=pkt_arp.dst_ip, dst_ip=pkt_arp.src_ip)
            else:
                # to avoid flood when the dst ip not in the network
                if datapath.id not in self.check_ip_dpid[pkt_arp.dst_ip]:
                    self.check_ip_dpid[pkt_arp.dst_ip].append(datapath.id)
                    out_port = ofproto.OFPP_FLOOD
                    actions = [parser.OFPActionOutput(out_port)]
                    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)
            return

        elif pkt_arp and pkt_arp.opcode == arp.ARP_REPLY:
            if pkt_arp.src_ip not in self.ip_to_mac:
                self.ip_to_mac[pkt_arp.src_ip] = src
                self.mac_to_dpid[src] = (dpid, in_port)
                self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
            dst_mac = self.ip_to_mac[pkt_arp.dst_ip]
            (dst_dpid, dst_port) = self.mac_to_dpid[dst_mac]
            self.handle_arpre(datapath=self.datapaths[dst_dpid], port=dst_port, src_mac=src, dst_mac=dst_mac,
                              src_ip=pkt_arp.src_ip, dst_ip=pkt_arp.dst_ip)
            return

        if pkt_ipv4 and (self.ip_to_port.get(pkt_ipv4.dst)) and (self.ip_to_port.get(pkt_ipv4.src)):
            (src_dpid, src_port) = self.ip_to_port[pkt_ipv4.src]  # src dpid and port
            (dst_dpid, dst_port) = self.ip_to_port[pkt_ipv4.dst]  # dst dpid and port
            self.install_path(src_dpid=src_dpid, dst_dpid=dst_dpid, src_port=src_port, dst_port=dst_port,
                              ev=ev, src=src, dst=dst, pkt_ipv4=pkt_ipv4, pkt_tcp=pkt_tcp)

    def send_pkt(self, datapath, port, pkt):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        pkt.serialize()
        data = pkt.data
        actions = [parser.OFPActionOutput(port=port)]
        out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER,
                                  actions=actions, data=data)
        datapath.send_msg(out)

    def handle_arpre(self, datapath, port, src_mac, dst_mac, src_ip, dst_ip):
        pkt = packet.Packet()
        pkt.add_protocol(ethernet.ethernet(ethertype=0x0806, dst=dst_mac, src=src_mac))
        pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY, src_mac=src_mac, src_ip=src_ip, dst_mac=dst_mac, dst_ip=dst_ip))
        self.send_pkt(datapath, port, pkt)

    def install_path(self, src_dpid, dst_dpid, src_port, dst_port, ev, src, dst, pkt_ipv4, pkt_tcp):
        print ("install path")
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser    

        mid_path = self.short_path(src=src_dpid, dst=dst_dpid)

        if mid_path is None:
            return
        self.path = None
        self.path = [(src_dpid, src_port)] + mid_path + [(dst_dpid, dst_port)]

        self.logger.info("path : %s", str(self.path))
        #print (self.pathmod)
        
        for i in range(len(self.path) - 2, -1, -2):
            datapath_path = self.datapaths[self.path[i][0]]
            match = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
                                    ipv4_src=pkt_ipv4.src, ipv4_dst=pkt_ipv4.dst)

            if i < (len(self.path) - 2):
                actions = [parser.OFPActionOutput(self.path[i + 1][1])]
            else:
                actions = [parser.OFPActionSetField(eth_dst=self.ip_to_mac.get(pkt_ipv4.dst)),
                            parser.OFPActionOutput(self.path[i + 1][1])]
            
            self.add_flow(datapath_path, 100, match, actions, idle_timeout=0, hard_timeout=0)
        # time_install = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        # self.logger.info("time_install: %s", time_install)

    def short_path(self, src, dst, bw=0):
        if src == dst:
            return []
        result = defaultdict(lambda: defaultdict(lambda: None))
        distance = defaultdict(lambda: None)

        # the node is checked
        seen = [src]

        # the distance to src
        distance[src] = 0

        w = 1  # weight
        while len(seen) < len(self.src_links):
            node = seen[-1]
            if node == dst:
                break
            for (temp_src, temp_dst) in self.src_links[node]:
                if temp_dst not in seen:
                    temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
                    temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
                    if (distance[temp_dst] is None) or (distance[temp_dst] > distance[temp_src] + w):
                        distance[temp_dst] = distance[temp_src] + w
                        # result = {"dpid":(link_src, src_port, link_dst, dst_port)}
                        result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
            min_node = None
            min_path = 999
            # get the min_path node
            for temp_node in distance:
                if (temp_node not in seen) and (distance[temp_node] is not None):
                    if distance[temp_node] < min_path:
                        min_node = temp_node
                        min_path = distance[temp_node]
            if min_node is None:
                break
            seen.append(min_node)

        path = []

        if dst not in result:
            return None
        while (dst in result) and (result[dst] is not None):
            path = [result[dst][2:4]] + path
            path = [result[dst][0:2]] + path
            dst = result[dst][0]
        #self.logger.info("path : %s", str(path))
        return path

    # this function might be useful, but who knows anyway
    def long_path(self, src, dst, bw=0):
        if src == dst:
            return []

        result = defaultdict(lambda: defaultdict(lambda: None))
        if src==1 and dst == 5:
            fixed_path=[(1,2),(2,3),(3,5)]
        elif src == 5 and dst == 1:
            fixed_path=[(5,3),(3,2),(2,1)]

        for (temp_src, temp_dst) in fixed_path:
            if (temp_src, temp_dst) in self.src_links[temp_src]:
                temp_src_port = self.src_links[temp_src][(temp_src, temp_dst)][0]
                temp_dst_port = self.src_links[temp_src][(temp_src, temp_dst)][1]
                result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)

        path = []
        if dst not in result:
            return None
        while (dst in result) and (result[dst] is not None):
            path = [result[dst][2:4]] + path
            path = [result[dst][0:2]] + path
            dst = result[dst][0]
        #self.logger.info("path : %s", str(path))
        return path



    @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 datapath.id not in self.datapaths:
                self.datapaths[datapath.id] = datapath
        elif ev.state == DEAD_DISPATCHER:
            if datapath.id in self.datapaths:
                del self.datapaths[datapath.id]
        #self.logger.info("datapaths : %s", self.datapaths)
    
    @set_ev_cls([event.EventSwitchEnter, event.EventSwitchLeave, event.EventPortAdd, event.EventPortDelete,
        event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete])
    def get_topology(self, ev):
        links_list = get_link(self.topology_api_app, None)
        self.src_links.clear()
        for link in links_list:
            sw_src = link.src.dpid
            sw_dst = link.dst.dpid
            src_port = link.src.port_no
            dst_port = link.dst.port_no
            src_port_name = link.src.name
            dst_port_name = link.dst.name
            self.port_name_to_num[src_port_name] = src_port
            self.port_name_to_num[dst_port_name] = dst_port
            self.src_links[sw_src][(sw_src, sw_dst)] = (src_port, dst_port)
            self.src_links[sw_dst][(sw_dst, sw_src)] = (dst_port, src_port)
            # self.logger.info("****src_port_name : %s", str(src_port_name))
            # self.logger.info("src_links : %s", str(self.src_links))
            # self.logger.info("port_name_to_num : %s", str(self.port_name_to_num))


    # these two functions need to be coded in your own way 
    def delete_flow(self, datapath,match):
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        cookie = cookie_mask = 0
        table_id = 0
        priority=100 # 使用最短路,priority并没有什么用
        idle_timeout = 10
        hard_timeout = 60
        buffer_id = ofp.OFP_NO_BUFFER
        #match = ofp_parser.OFPMatch(in_port=3, eth_type=flow_info[0], ipv4_src="10.0.0.1", ipv4_dst="10.0.0.2")
        actions = []
        inst = []
        req = ofp_parser.OFPFlowMod(datapath, 
                                cookie, cookie_mask, table_id, 
                                ofp.OFPFC_DELETE, idle_timeout, 
                                hard_timeout, priority, buffer_id, 
                                ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM, 
                                match, inst)
        datapath.send_msg(req)


    @set_ev_cls(ofp_event.EventOFPPortStatus, [CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_DISPATCHER])
    def get_OFPPortStatus_msg(self, ev):
        msg=ev.msg
        datapath=ev.msg.datapath
        dpid = msg.datapath.id
        ofproto=datapath.ofproto
        parser = datapath.ofproto_parser 

        # self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port) #{ip:(dpid,port)}
        for ip in self.ip_to_port:
            # self.logger.info(ip, str(self.ip_to_port[ip]))
            if self.ip_to_port[ip] == self.path[-1]:
                dstip=ip
                dst=self.ip_to_mac[dstip]

        for ip in self.ip_to_port:
            if self.ip_to_port[ip] == self.path[0]:
                srcip=ip
                src=self.ip_to_mac[srcip]
        
        # for example,path=[(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
        # 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出
        for i in range(len(self.path) - 2, -1, -2):
            datapath_path = self.datapaths[self.path[i][0]]

            match1 = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
                                    ipv4_src=srcip, ipv4_dst=dstip)
            self.delete_flow(datapath=datapath_path,match=match1)
            match2 = parser.OFPMatch(in_port=self.path[i+1][1], eth_src=dst, eth_dst=src, eth_type=0x0800,
                                    ipv4_src=dstip, ipv4_dst=srcip)
            self.delete_flow(datapath=datapath_path,match=match2)
        
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值