1. 程式人生 > >Ryu中通過DIjkstra計算最短路徑

Ryu中通過DIjkstra計算最短路徑

最近學習了有關SDN中路由的選擇,發現Ryu中沒有像POX那樣給出預設的最短路徑,我就試著把POX中的l2_multi.py修改了一下移植到Ryu中,最終實現在Ryu中計算源點和目的節點的最短路徑。

原始碼:

# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_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 collections import defaultdict
from ryu.topology.api import get_switch,get_link
from ryu.topology import event,switches

ARP = arp.arp.__name__
ETHERNET = ethernet.ethernet.__name__
ETHERNET_MULTICAST = "ff:ff:ff:ff:ff:ff"

adjacency = defaultdict(lambda: defaultdict(lambda: None))
path_map = defaultdict(lambda: defaultdict(lambda: (None, None)))
sws = []
switches={}
mac_map={}

def _get_raw_path(src, dst):
    """
    Get a raw path (just a list of nodes to traverse)
    """
    if len(path_map) == 0: _dijkstra_paths()
    if src is dst:
        # We're here!
        return []

    if path_map[src][dst][0] is None:
        return None
    intermediate = path_map[src][dst][1]
    if intermediate is None:
        # Directly connected
        return []
    return _get_raw_path(src, intermediate) + [intermediate] + \
           _get_raw_path(intermediate, dst)

def _get_path(src, dst, first_port, final_port):
    """
    Gets a cooked path -- a list of (node,in_port,out_port)
    """
    # Start with a raw path...
    print src
    print dst
    if src == dst:
        path = [src]
    else:
        path = _get_raw_path(src, dst)
        if path is None: return None
        path = [src] + path + [dst]

    # Now add the ports
    r = []
    in_port = first_port
    for s1, s2 in zip(path[:-1], path[1:]):
        out_port = adjacency[s1][s2]
        r.append((s1, in_port, out_port))
        in_port = adjacency[s2][s1]
    r.append((dst, in_port, final_port))
    print 'R is %s' % r
    return r

def _dijkstra_paths():
    path_map.clear()
    for k in sws:
        for j, port in adjacency[k].iteritems():
            if port is None:
                continue
            path_map[k][j] = (1, None)
        path_map[k][k] = (0, None)
        print adjacency[k]

    for t in sws:
        final_point = []
        final_point.append(t)
        for i in range(len(sws) - 1):
            min_path = 999
            for p in sws:
                if p not in final_point:
                    if path_map[t][p][0] is not None and path_map[t][p][0] < min_path:
                        min_path = path_map[t][p][0]
                        temp = p
            final_point.append(temp)
            for m in sws:
                if m not in final_point:
                    if path_map[t][m][0] is None and path_map[t][temp][0] is not None and path_map[temp][m][
                        0] is not None:
                        path_map[t][m] = (path_map[t][temp][0] + path_map[temp][m][0], temp)
                    elif path_map[t][temp][0] is not None and path_map[temp][m][0] is not None and path_map[t][m][
                        0] is not None:
                        if path_map[t][temp][0] + path_map[temp][m][0] < path_map[t][m][0]:
                            path_map[t][m] = (path_map[t][temp][0] + path_map[temp][m][0], temp)
    print path_map


class SimpleSwitch13(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(SimpleSwitch13, self).__init__(*args, **kwargs)
        self.mac_to_port = {}
        self.arp_table = {}
        self.sw = {}
        self.port_tx = {}
        self.datapaths = {}
        self.datapath_list={}
        self.topology_api_app=self

    @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
        switches[datapath.id]=datapath

        # install table-miss flow entry
        #
        # We specify NO BUFFER to max_len of the output action due to
        # OVS bug. At this moment, if we specify a lesser number, e.g.,
        # 128, OVS will send Packet-In with invalid buffer_id and
        # truncated packet data. In that case, we cannot output packets
        # correctly.  The bug has been fixed in OVS v2.1.0.
        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):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]

        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                hard_timeout=20,
                                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)
        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]
		#avoid broadcast from LLDP
        if eth.ethertype==35020:
            return
			
        dst = eth.dst
        src = eth.src

        loc=('00-00-00-00-00-0'+str(datapath.id),in_port)
        oldloc=mac_map.get(src)
        if oldloc is None:
            mac_map[src]=loc
        elif src not in mac_map:
            mac_map[src]=loc

        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        header_list = dict(
            (p.protocol_name, p) for p in pkt.protocols if type(p) != str)
        if ARP in header_list:
            self.arp_table[header_list[ARP].src_ip] = src  # ARP learning

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

        # learn a mac address to avoid FLOOD next time.
        if src not in self.mac_to_port[dpid]:  #record only one in_port
            self.mac_to_port[dpid][src] = in_port

        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
            temp_src=mac_map[src]
            temp_dst=mac_map[dst]
            self.install_path(temp_src[0],temp_dst[0], temp_src[1], temp_dst[1], ev)
            self.logger.info("out_port: %s", out_port)
        else:
            out_port = ofproto.OFPP_FLOOD
            print"flood!"

        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)            

        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)

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

    def install_path(self,src_sw, dst_sw, in_port, last_port, ev):
        """
        Attempts to install a path between this switch and some destination
        """
        p = _get_path(src_sw, dst_sw, in_port, last_port)
        self._install_path(p, ev)
        # Now reverse it and install it backwards
        # (we'll just assume that will work)
        p = [(sw, out_port, in_port) for sw, in_port, out_port in p]
        self._install_path(p, ev)

    def _install_path(self, p, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        for sw, in_port, out_port in p:
            match = parser.OFPMatch(in_port=in_port)
            actions = [parser.OFPActionOutput(out_port)]
            ID=int(sw[-1:])
            datapath=self.datapath_list[ID]
            self.add_flow(datapath, 1, match, actions)

    @set_ev_cls(event.EventSwitchEnter)
	
    def get_topology(self,ev):
        switch_list=get_switch(self.topology_api_app,None)
        global sws
		# assign mac for swtich to easy read
        sws=['00-00-00-00-00-0'+ str(switch.dp.id) for switch in switch_list]
        links_list=get_link(self.topology_api_app,None)
        for link in links_list:
            sw_src='00-00-00-00-00-0'+ str(link.src.dpid)
            sw_dst='00-00-00-00-00-0'+ str(link.dst.dpid)
            adjacency[sw_src][sw_dst]=link.src.port_no
實驗環境:Ryu 3.15 + Openflow1.3 +Openvswitch 2.1.3

部分程式碼說明:

1.

    @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)
                         :
                         :
if eth.ethertype==35020:  return 是為了防止因鏈路發現協議導致網路癱瘓。

mac_map儲存的是host和與它相連線的switch的對映。

在此函式中要對arp廣播包進行處理避免由廣播風暴造成網路癱瘓。

2.

    @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 == 1:
                self.datapaths[datapath.id] = datapath
            if not datapath.id in self.datapath_list:
                self.datapath_list[datapath.id]=datapath
        elif ev.state == DEAD_DISPATCHER:
            if datapath.id in self.datapaths:
                del self.datapaths[datapath.id]
datapath_list儲存的是網路中所有的datapath,為了將流表安裝到指定的交換機。

3.

    @set_ev_cls(event.EventSwitchEnter)
    def get_topology(self,ev):
        switch_list=get_switch(self.topology_api_app,None)
        global sws
		# assign mac for swtich to easy read
        sws=['00-00-00-00-00-0'+ str(switch.dp.id) for switch in switch_list]
        links_list=get_link(self.topology_api_app,None)
        for link in links_list:
            sw_src='00-00-00-00-00-0'+ str(link.src.dpid)
            sw_dst='00-00-00-00-00-0'+ str(link.dst.dpid)
            adjacency[sw_src][sw_dst]=link.src.port_no
此函式是發現網路拓撲結構:交換機、鏈路、埠,adjacency儲存的是整個網路的拓撲。

啟動模組時要新增--observe-links,否則鏈路發現模組不起作用。