SDN實驗---Ryu的應用開發(二)Learning Switch

一:自學習交換機(二層MAC交換機)的編程思路

(一)明確問題

如何實現軟件定義的自學習交換機?

(二)設計解決方案

經過控制器來實現自學習交換算法,而後指導數據平面實現交換機操做

(三)肯定具體的技術方案

控制器選用Ryu,數據平面經過Mininet模擬

(四)部署實施

在控制器上編程開發交換機應用,建立實驗網絡爲驗證方案作準備

(五)驗證方案

運行程序,調試程序,驗證程序

(六)優化

驗證成功後,優化程序

二:自學習交換機原理 

(一)普通交換機實現

 

 

 

交換機MAC地址表記錄了統一網段中的各個主機對應交換機的端口主機的MAC地址
當主機A要和主機B通訊時,初始交換機MAC表是空的,會先記錄主機A的MAC地址和對應的交換機端口,而後查找交換機MAC中是否有目標MAC地址,沒有找到,會向其餘全部端口泛洪查找

 

泛洪,通知其餘主機。主機C接收到數據包,發現不是本身的,則不處理,丟棄數據包。當主機B接收後,發現是找本身的,則能夠進行消息通訊。交換機先進行MAC學習,記錄主機B的MAC信息,再進行查錶轉發,單播發送給主機A

(二)SDN中交換機實現

SDN中交換機不存儲MAC表,(datapath)只存在流表。其地址學習操做由控制器(控制器中包含MAC 地址表)實現,以後控制器下發流表項給交換機 

1.主機A向主機B發送信息,流表中只存在默認流表,告訴交換機將數據包發送給控制器。

2.控制器先進行MAC地址學習,記錄主機A的MAC地址和其對應交換機端口,而後查詢MAC地址表,查找主機B信息。沒有則下發流表項告訴交換機先泛洪試試

3.泛洪後,主機C接收後丟棄數據包,不處理。主機B發現是尋找本身的,則進行消息回送,因爲交換機流表中沒有處理主機B到主機A的信息的流表項,因此只能向控制器發送數據包。控制器先學習主機B的MAC地址和對應交換機端口,以後查詢MAC地址表,找到主機A的MAC信息,下發流表項,告訴交換機如何處理主機B->主機A的消息

4.注意:這裏交換機的流表項中只存在主機B->主機A的流表項處理方案,不存在主機A->主機B的處理流表項(可是控制器MAC地址表中是存在主機B的信息),因此會在下一次數據傳送中,控制器下發響應的流表項。可是其實能夠實現(在3中一次下發兩個流表項)

三:代碼實現

(一)所有代碼

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

class SelfLearnSwitch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]    #set openflow protocol version while we support

    def __init__(self,*args,**kwargs):
        super(SelfLearnSwitch,self).__init__(*args,**kwargs)
        #set a data construction to save MAC Address Table
        self.Mac_Port_Table={}

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures)
    def switch_features_handler(self,ev):
        '''
        manage the initial link, from switch to controller
        '''
        #first parse event to get datapath and openflow protocol 
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        self.logger.info("datapath: %s link to controller",datapath.id)

        #secondly set match and action
        match = ofp_parser.OFPMatch()    #all data message match successful
        actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]    #set receive port and buffer for switch

        #add flow and send it to switch in add_flow
        self.add_flow(datapath,0,match,actions,"default flow entry")

    def add_flow(self,datapath,priority,match,actions,extra_info):
        """
        add flow entry to switch
        """

        #get open flow protocol infomation
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        #set instruction infomation from openflow protocol 1.3
        inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]

        #set flow entry mod
        mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)

        print("send "+extra_info)
        #send flow entry to switch
        datapath.send_msg(mod)

    @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
    def packet_in_handler(self,ev):
        '''
        manage infomation from switch
        '''

        #first parser openflow protocol
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        #get datapath id from datapath, and save dpid into MAC table (default)
        dpid = datapath.id
        self.Mac_Port_Table.setdefault(dpid, {})

        #analysize packet, get ethernet data, get host MAC info
        pkt = packet.Packet(msg.data)
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        dst = eth_pkt.dst
        src = eth_pkt.src

        #get switch port where host packet send in
        in_port = msg.match['in_port']

        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)
    
        #save src data into dictionary---MAC address table
        self.Mac_Port_Table[dpid][src] = in_port

        #query MAC address table to get destinction host`s port from current datapath
        #---first: find port to send packet
        #---second: not find port,so send packet by flood
        if dst in self.Mac_Port_Table[dpid]:
            Out_Port = self.Mac_Port_Table[dpid][dst]
        else:
            Out_Port = ofproto.OFPP_FLOOD

        #set match-action from above status
        actions = [ofp_parser.OFPActionOutput(Out_Port)]

        #add a new flow entry to switch by add_flow
        if Out_Port != ofproto.OFPP_FLOOD:    #if Out_port == ofproto.OFPP_FLOOD ---> flow entry == default flow entry, it already exist
            match = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)
            self.add_flow(datapath, 1, match, actions,"a new flow entry by specify port")
            self.logger.info("send packet to switch port: %s",Out_Port)

        #finally send the packet to datapath, to achive self_learn_switch
        Out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,
                                in_port=in_port,actions=actions,data=msg.data)

        datapath.send_msg(Out)

(二)代碼講解(一) 

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

class SelfLearnSwitch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]    #set openflow protocol version while we support

    def __init__(self,*args,**kwargs):
        super(SelfLearnSwitch,self).__init__(*args,**kwargs)
        #set a data construction to save MAC Address Table
        self.Mac_Port_Table={}

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures)
    def switch_features_handler(self,ev):
        '''
        manage the initial link, from switch to controller
        '''
        #first parse event to get datapath and openflow protocol 
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        self.logger.info("datapath: %s link to controller",datapath.id)

        #secondly set match and action
        match = ofp_parser.OFPMatch()    #all data message match successful
        actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]    #set receive port and buffer for switch

        #add flow and send it to switch in add_flow
        self.add_flow(datapath,0,match,actions,"default flow entry")

    def add_flow(self,datapath,priority,match,actions,extra_info):
        """
        add flow entry to switch
        """

        #get open flow protocol infomation
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        #set instruction infomation from openflow protocol 1.3
        inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]

        #set flow entry mod
        mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)

        print("send "+extra_info)
        #send flow entry to switch
        datapath.send_msg(mod)

以上代碼同SDN實驗---Ryu的應用開發(一)Hub實現,實現了設備與控制器初始鏈接,下發默認流表項,使得默認狀況下,交換機在沒法匹配到流表項時,直接去找控制器。一個一個公共函數add_flow實現流表下發。注意:在__init__方法中實現了數據結構《字典》去存儲MAC地址表,爲下面作準備

(三)代碼講解(二)

    @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
    def packet_in_handler(self,ev):
        '''
        manage infomation from switch
        '''
 #first parser openflow protocol    先解析OpenFlow協議信息
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        #get datapath id from datapath, and save dpid into MAC table (default)   獲取datapath(虛擬交換機的id),用dpid初始化一個鍵值
        dpid = datapath.id
        self.Mac_Port_Table.setdefault(dpid, {})

 #analysize packet, get ethernet data, get host MAC info  分析packert數據包,由於轉發的包,都是基於以太網協議的,因此咱們須要用到以太網協議進行解析,獲取源MAC和目的MAC
        pkt = packet.Packet(msg.data)
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        dst = eth_pkt.dst
        src = eth_pkt.src

        #get switch port where host packet send in  獲取datapath的數據輸入端口
        in_port = msg.match['in_port']

 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)  #打印調試信息  #save src data into dictionary---MAC address table  將源MAC地址保存,學習,放入MAC表中
        self.Mac_Port_Table[dpid][src] = in_port

        #query MAC address table to get destinction host`s port from current datapath  查詢MAC表,是否有目標MAC地址的鍵值  #---first: find port to send packet  若是找到,咱們則按照該端口發送 #---second: not find port,so send packet by flood  若是沒有找到,咱們須要泛洪發送給下一個(或者下幾個)交換機,依次查詢 if dst in self.Mac_Port_Table[dpid]:
            Out_Port = self.Mac_Port_Table[dpid][dst]
        else:
            Out_Port = ofproto.OFPP_FLOOD

        #set match-action from above status  開始設置match-actions匹配動做
        actions = [ofp_parser.OFPActionOutput(Out_Port)]

        #add a new flow entry to switch by add_flow  進行對應的流表項下發  《重點》 if Out_Port != ofproto.OFPP_FLOOD:   
            match = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)
            self.add_flow(datapath, 1, match, actions,"a new flow entry by specify port")
            self.logger.info("send packet to switch port: %s",Out_Port)

        #finally send the packet to datapath, to achive self_learn_switch  最後咱們將以前交換機發送上來的數據,從新發給交換機
        Out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,
                                in_port=in_port,actions=actions,data=msg.data)  #咱們必須加上這個data,才能夠將packet數據包發送回去《重點》否則會出錯××××××

        datapath.send_msg(Out)

(四)實驗演示

1.啓動Ryu控制器

2.啓動mininet

3.Ryu進行響應

注意:這裏我一啓動Mininet,就已經獲取了全部的MAC信息,應該是主機接入網絡後發送某些數據包,致使控制器得到了MAC表(須要使用wireshark抓包工具進行分析....後面進行補充)

網絡可達,說明實現自學習交換機

四:補充知識

(一)pkt = packet.Packet(msg.data)  一個類,在Ryu/lib/packet/模塊下,用於包的解碼/編碼

class Packet(StringifyMixin):
    """A packet decoder/encoder class.

    An instance is used to either decode or encode a single packet.

    *data* is a bytearray to describe a raw datagram to decode.  data是一個未加工的報文數據, 即msg.data直接從事件的msg中獲取的數據
    When decoding, a Packet object is iteratable.
    Iterated values are protocol (ethernet, ipv4, ...) headers and the payload.
    Protocol headers are instances of subclass of packet_base.PacketBase.
    The payload is a bytearray.  They are iterated in on-wire order.

    *data* should be omitted when encoding a packet.
    """

    # Ignore data field when outputting json representation.
    _base_attributes = ['data']

    def __init__(self, data=None, protocols=None, parse_cls=ethernet.ethernet):  協議解析,默認是按照以太網協議
        super(Packet, self).__init__()  
        self.data = data
        if protocols is None:
            self.protocols = []
        else:
            self.protocols = protocols
        if self.data:
            self._parser(parse_cls)

(二)eth_pkt = pkt.get_protocol(ethernet.ethernet)  返回與指定協議匹配的協議列表。從packet包中獲取協議信息(協議包含咱們須要的dst,src等,如三中所示)

class Packet(StringifyMixin):

    def add_protocol(self, proto):
        """Register a protocol *proto* for this packet.

        This method is legal only when encoding a packet.

        When encoding a packet, register a protocol (ethernet, ipv4, ...)
        header to add to this packet.
        Protocol headers should be registered in on-wire order before calling
        self.serialize.
        """

        self.protocols.append(proto)

    def get_protocols(self, protocol):
        """Returns a list of protocols that matches to the specified protocol.
        """
        if isinstance(protocol, packet_base.PacketBase):
            protocol = protocol.__class__
        assert issubclass(protocol, packet_base.PacketBase)
        return [p for p in self.protocols if isinstance(p, protocol)]

(三)eth_pkt = pkt.get_protocol(ethernet.ethernet)  一個類,也在Ryu/lib/packet/模塊下,用於以太網報頭編碼器/解碼器類。

class ethernet(packet_base.PacketBase):
    """Ethernet header encoder/decoder class.

    An instance has the following attributes at least.
    MAC addresses are represented as a string like '08:60:6e:7f:74:e7'.
    __init__ takes the corresponding args in this order.

    ============== ==================== ===================== Attribute Description Example  ============== ==================== =====================
    dst            destination address  'ff:ff:ff:ff:ff:ff'
    src            source address       '08:60:6e:7f:74:e7'
    ethertype      ether type           0x0800
    ============== ==================== =====================
    """

    _PACK_STR = '!6s6sH'
    _MIN_LEN = struct.calcsize(_PACK_STR)
    _MIN_PAYLOAD_LEN = 46
    _TYPE = {
        'ascii': [
            'src', 'dst'
        ]
    }

    def __init__(self, dst='ff:ff:ff:ff:ff:ff', src='00:00:00:00:00:00',
                 ethertype=ether.ETH_TYPE_IP):
        super(ethernet, self).__init__()
        self.dst = dst
        self.src = src
        self.ethertype = ethertype

    @classmethod
    def parser(cls, buf):
        dst, src, ethertype = struct.unpack_from(cls._PACK_STR, buf)
        return (cls(addrconv.mac.bin_to_text(dst),
                    addrconv.mac.bin_to_text(src), ethertype),
                ethernet.get_packet_type(ethertype),
                buf[ethernet._MIN_LEN:])

    def serialize(self, payload, prev):
        # Append padding if the payload is less than 46 bytes long
        pad_len = self._MIN_PAYLOAD_LEN - len(payload)
        if pad_len > 0:
            payload.extend(b'\x00' * pad_len)

        return struct.pack(ethernet._PACK_STR,
                           addrconv.mac.text_to_bin(self.dst),
                           addrconv.mac.text_to_bin(self.src),
                           self.ethertype)

    @classmethod
    def get_packet_type(cls, type_):
        """Override method for the ethernet IEEE802.3 Length/Type
        field (self.ethertype).

        If the value of Length/Type field is less than or equal to
        1500 decimal(05DC hexadecimal), it means Length interpretation
        and be passed to the LLC sublayer."""
        if type_ <= ether.ETH_TYPE_IEEE802_3:
            type_ = ether.ETH_TYPE_IEEE802_3
        return cls._TYPES.get(type_)
相關文章
相關標籤/搜索