Ryu源碼之拓撲發現原理分析


參考:Ryu拓撲發現原理分析(ryu/topology/switches.py),通過對該文件的分析,可以更好的了解數據平面中設備信息

一:拓撲成員類分析

(一)Port類

class Port(object):
    # This is data class passed by EventPortXXX
    def __init__(self, dpid, ofproto, ofpport):
        super(Port, self).__init__()

        self.dpid = dpid
        self._ofproto = ofproto
        self._config = ofpport.config
        self._state = ofpport.state

        self.port_no = ofpport.port_no
        self.hw_addr = ofpport.hw_addr
        self.name = ofpport.name

    def is_reserved(self):
        return self.port_no > self._ofproto.OFPP_MAX

    def is_down(self):
        return (self._state & self._ofproto.OFPPS_LINK_DOWN) > 0 \
            or (self._config & self._ofproto.OFPPC_PORT_DOWN) > 0

    def is_live(self):
        # NOTE: OF1.2 has OFPPS_LIVE state
        #       return (self._state & self._ofproto.OFPPS_LIVE) > 0
        return not self.is_down()

    def to_dict(self):
        return {'dpid': dpid_to_str(self.dpid),
                'port_no': port_no_to_str(self.port_no),
                'hw_addr': self.hw_addr,
                'name': self.name.decode('utf-8')}

    # for Switch.del_port()
    def __eq__(self, other):
        return self.dpid == other.dpid and self.port_no == other.port_no

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self.dpid, self.port_no))

    def __str__(self):
        LIVE_MSG = {False: 'DOWN', True: 'LIVE'}
        return 'Port<dpid=%s, port_no=%s, %s>' % \
            (self.dpid, self.port_no, LIVE_MSG[self.is_live()])
class Port(object):

存儲端口相關信息,數據成員有:

self.dpid = dpid
self._ofproto = ofproto
self._config = ofpport.config
self._state = ofpport.state
self.port_no = ofpport.port_no
self.hw_addr = ofpport.hw_addr
self.name = ofpport.name

其中要特別注意的是dpid和port_no,即交換機ID和端口號,這兩個信息在下發流表項時很重要。

(二)Switch類

class Switch(object):
    # This is data class passed by EventSwitchXXX
    def __init__(self, dp):
        super(Switch, self).__init__()

        self.dp = dp
        self.ports = []

    def add_port(self, ofpport):
        port = Port(self.dp.id, self.dp.ofproto, ofpport)
        if not port.is_reserved():
            self.ports.append(port)

    def del_port(self, ofpport):
        self.ports.remove(Port(ofpport))

    def to_dict(self):
        d = {'dpid': dpid_to_str(self.dp.id),
             'ports': [port.to_dict() for port in self.ports]}
        return d

    def __str__(self):
        msg = 'Switch<dpid=%s, ' % self.dp.id
        for port in self.ports:
            msg += str(port) + ' '

        msg += '>'
        return msg
class Switch(object):

存儲交換機相關信息,數據成員有:

self.dp = dp
self.ports = []

其中dp是Datapath類的實例,該類定義在在ryu/controller/controller.py,主要屬性有:

self.socket = socket
self.address = address
self.is_active = True
self.id = None  # datapath_id is unknown yet
self.ports = None

ports是一個由Port類實例組成的列表,存儲該交換機的端口。

(三)Link類:

class Link(object):
    # This is data class passed by EventLinkXXX
    def __init__(self, src, dst):
        super(Link, self).__init__()
        self.src = src
        self.dst = dst

    def to_dict(self):
        d = {'src': self.src.to_dict(),
             'dst': self.dst.to_dict()}
        return d

    # this type is used for key value of LinkState
    def __eq__(self, other):
        return self.src == other.src and self.dst == other.dst

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self.src, self.dst))

    def __str__(self):
        return 'Link: %s to %s' % (self.src, self.dst)
class Link(object):

保存的是源端口和目的端口(都是Port類實例),數據成員有:

self.src = src
self.dst = dst

(四)PortState類

class PortState(dict):
    # dict: int port_no -> OFPPort port
    # OFPPort is defined in ryu.ofproto.ofproto_v1_X_parser
    def __init__(self):
        super(PortState, self).__init__()

    def add(self, port_no, port):
        self[port_no] = port

    def remove(self, port_no):
        del self[port_no]

    def modify(self, port_no, port):
        self[port_no] = port
class PortState(dict):

該類繼承自dict,保存了從port_no(int型)到port(OFPPort類實例)的映射。

該類主要用作self.port_state字典的值(鍵是dpid),用於存儲dpid對應的交換機的所有端口情況。
OFPPort類定義在ryu/ofproto目錄下對應的ofproto_v1_X_parser.py中(X代表版本號),繼承自一個namedtuple,保存有port_no等信息。

(五)PortData類

class PortData(object):
    def __init__(self, is_down, lldp_data):
        super(PortData, self).__init__()
        self.is_down = is_down
        self.lldp_data = lldp_data
        self.timestamp = None
        self.sent = 0

    def lldp_sent(self):
        self.timestamp = time.time()
        self.sent += 1

    def lldp_received(self):
        self.sent = 0

    def lldp_dropped(self):
        return self.sent

    def clear_timestamp(self):
        self.timestamp = None

    def set_down(self, is_down):
        self.is_down = is_down

    def __str__(self):
        return 'PortData<live=%s, timestamp=%s, sent=%d>' \
            % (not self.is_down, self.timestamp, self.sent)
class PortData(object):

保存每個端口與對應的LLDP報文數據,數據成員有:

self.is_down = is_down
self.lldp_data = lldp_data(這是LLDP報文的數據)
self.timestamp = None
self.sent = 0

每調用一次lldp_sent函數,便會把self.timestamp置為當前的時間(time.time()),並將self.sent加1;每調用一次lldp_received函數,便會把self.sent置為0。

(六)PortDataState類

class PortDataState(dict):
    # dict: Port class -> PortData class
    # slimed down version of OrderedDict as python 2.6 doesn't support it.
    _PREV = 0
    _NEXT = 1
    _KEY = 2

    def __init__(self):
        super(PortDataState, self).__init__()
        self._root = root = []  # sentinel node
        root[:] = [root, root, None]  # [_PREV, _NEXT, _KEY] doubly linked list
        self._map = {}

    def _remove_key(self, key):
        link_prev, link_next, key = self._map.pop(key)
        link_prev[self._NEXT] = link_next
        link_next[self._PREV] = link_prev

    def _append_key(self, key):
        root = self._root
        last = root[self._PREV]
        last[self._NEXT] = root[self._PREV] = self._map[key] = [last, root,
                                                                key]

    def _prepend_key(self, key):
        root = self._root
        first = root[self._NEXT]
        first[self._PREV] = root[self._NEXT] = self._map[key] = [root, first,
                                                                 key]

    def _move_last_key(self, key):
        self._remove_key(key)
        self._append_key(key)

    def _move_front_key(self, key):
        self._remove_key(key)
        self._prepend_key(key)

    def add_port(self, port, lldp_data):
        if port not in self:
            self._prepend_key(port)
            self[port] = PortData(port.is_down(), lldp_data) #為端口添加LLDP報文
        else:
            self[port].is_down = port.is_down()

    def lldp_sent(self, port):
        port_data = self[port] #獲取了PortData類實例
        port_data.lldp_sent() #設置了timestamp和sent標識+1
        self._move_last_key(port) #循環列表,前面移動到后面
        return port_data #返回PortData類實例

    def lldp_received(self, port):
        self[port].lldp_received()

    def move_front(self, port):
        port_data = self.get(port, None)
        if port_data is not None:
            port_data.clear_timestamp()
            self._move_front_key(port)

    def set_down(self, port):
        is_down = port.is_down()
        port_data = self[port]
        port_data.set_down(is_down)
        port_data.clear_timestamp()
        if not is_down:
            self._move_front_key(port)
        return is_down

    def get_port(self, port):
        return self[port]

    def del_port(self, port):
        del self[port]
        self._remove_key(port)

    def __iter__(self):
        root = self._root
        curr = root[self._NEXT]
        while curr is not root:
            yield curr[self._KEY]
            curr = curr[self._NEXT]

    def clear(self):
        for node in self._map.values():
            del node[:]
        root = self._root
        root[:] = [root, root, None]
        self._map.clear()
        dict.clear(self)

    def items(self):
        'od.items() -> list of (key, value) pairs in od'
        return [(key, self[key]) for key in self]

    def iteritems(self):
        'od.iteritems -> an iterator over the (key, value) pairs in od'
        for k in self:
            yield (k, self[k])
class PortDataState(dict):

  繼承自dict類,保存從Port類到PortData類的映射。該類維護了一個類似雙向循環鏈表的數據結構,並重寫了__iter__(),使得遍歷該類的實例(self.ports)時,會按照該雙向循環鏈表從哨兵節點(self._root)后一個節點開始遍歷。

  包含一個add_port函數,傳入port和lldp_data,port作鍵,構建的PortData類實例作為值。
  包含一個lldp_sent(self,port)函數,根據傳入的port(Port類實例)獲得對應的PortData類實例port_data,然后調用port_data.lldp_sent()(該函數會設置時間戳),再調用self._move_last_key(port),把該port移到類似雙向循環鏈表的數據結構中哨兵節點的前面(相當於下次遍歷的末尾);最后返回port_data。

(七)LinkState類

class LinkState(dict):
    # dict: Link class -> timestamp
    def __init__(self):
        super(LinkState, self).__init__()
        self._map = {}

    def get_peer(self, src):
        return self._map.get(src, None)

    def update_link(self, src, dst):
        link = Link(src, dst)

        self[link] = time.time()
        self._map[src] = dst

        # return if the reverse link is also up or not
        rev_link = Link(dst, src)
        return rev_link in self

    def link_down(self, link):
        del self[link]
        del self._map[link.src]

    def rev_link_set_timestamp(self, rev_link, timestamp):
        # rev_link may or may not in LinkSet
        if rev_link in self:
            self[rev_link] = timestamp

    def port_deleted(self, src):
        dst = self.get_peer(src)
        if dst is None:
            raise KeyError()

        link = Link(src, dst)
        rev_link = Link(dst, src)
        del self[link]
        del self._map[src]
        # reverse link might not exist
        self.pop(rev_link, None)
        rev_link_dst = self._map.pop(dst, None)

        return dst, rev_link_dst
class LinkState(dict):

繼承自dict,保存從Link類到時間戳的映射。數據成員self._map字典用於存儲Link兩端互相映射的關系。

(八)LLDPPacket類

class LLDPPacket(object):
    # make a LLDP packet for link discovery.

    CHASSIS_ID_PREFIX = 'dpid:'
    CHASSIS_ID_PREFIX_LEN = len(CHASSIS_ID_PREFIX)
    CHASSIS_ID_FMT = CHASSIS_ID_PREFIX + '%s'

    PORT_ID_STR = '!I'      # uint32_t
    PORT_ID_SIZE = 4

    class LLDPUnknownFormat(RyuException):
        message = '%(msg)s'

    @staticmethod
    def lldp_packet(dpid, port_no, dl_addr, ttl):
        pkt = packet.Packet() #生成數據包

        dst = lldp.LLDP_MAC_NEAREST_BRIDGE
        src = dl_addr
        ethertype = ETH_TYPE_LLDP
        eth_pkt = ethernet.ethernet(dst, src, ethertype)
        pkt.add_protocol(eth_pkt) #構造數據

        tlv_chassis_id = lldp.ChassisID(
            subtype=lldp.ChassisID.SUB_LOCALLY_ASSIGNED,
            chassis_id=(LLDPPacket.CHASSIS_ID_FMT %
                        dpid_to_str(dpid)).encode('ascii'))

        tlv_port_id = lldp.PortID(subtype=lldp.PortID.SUB_PORT_COMPONENT,
                                  port_id=struct.pack(
                                      LLDPPacket.PORT_ID_STR,
                                      port_no))

        tlv_ttl = lldp.TTL(ttl=ttl)
        tlv_end = lldp.End()

        tlvs = (tlv_chassis_id, tlv_port_id, tlv_ttl, tlv_end)
        lldp_pkt = lldp.lldp(tlvs)
        pkt.add_protocol(lldp_pkt)

        pkt.serialize()
        return pkt.data

    @staticmethod
    def lldp_parse(data):
        pkt = packet.Packet(data)
        i = iter(pkt)
        eth_pkt = six.next(i)
        assert type(eth_pkt) == ethernet.ethernet

        lldp_pkt = six.next(i)
        if type(lldp_pkt) != lldp.lldp:
            raise LLDPPacket.LLDPUnknownFormat()

        tlv_chassis_id = lldp_pkt.tlvs[0]
        if tlv_chassis_id.subtype != lldp.ChassisID.SUB_LOCALLY_ASSIGNED:
            raise LLDPPacket.LLDPUnknownFormat(
                msg='unknown chassis id subtype %d' % tlv_chassis_id.subtype)
        chassis_id = tlv_chassis_id.chassis_id.decode('utf-8')
        if not chassis_id.startswith(LLDPPacket.CHASSIS_ID_PREFIX):
            raise LLDPPacket.LLDPUnknownFormat(
                msg='unknown chassis id format %s' % chassis_id)
        src_dpid = str_to_dpid(chassis_id[LLDPPacket.CHASSIS_ID_PREFIX_LEN:])

        tlv_port_id = lldp_pkt.tlvs[1]
        if tlv_port_id.subtype != lldp.PortID.SUB_PORT_COMPONENT:
            raise LLDPPacket.LLDPUnknownFormat(
                msg='unknown port id subtype %d' % tlv_port_id.subtype)
        port_id = tlv_port_id.port_id
        if len(port_id) != LLDPPacket.PORT_ID_SIZE:
            raise LLDPPacket.LLDPUnknownFormat(
                msg='unknown port id %d' % port_id)
        (src_port_no, ) = struct.unpack(LLDPPacket.PORT_ID_STR, port_id)

        return src_dpid, src_port_no
class LLDPPacket(object):

靜態方法lldp_packet(dpid,port_no,dl_addr,ttl)用於構造LLDP報文,靜態方法lldp_parse(data)用於解析LLDP包,並返回源DPID和源端口號。

二:分析Switches類拓撲發現

(一)類成員分析

該類是Ryu拓撲發現的核心所在。Switches類是app_manager.RyuApp類的子類,當運行switches應用時會被實例化,其__init__函數主要包括:

class Switches(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION, ofproto_v1_2.OFP_VERSION,
                    ofproto_v1_3.OFP_VERSION, ofproto_v1_4.OFP_VERSION]
    _EVENTS = [event.EventSwitchEnter, event.EventSwitchLeave,
               event.EventSwitchReconnected,
               event.EventPortAdd, event.EventPortDelete,
               event.EventPortModify,
               event.EventLinkAdd, event.EventLinkDelete,
               event.EventHostAdd]

    DEFAULT_TTL = 120  # unused. ignored.
    LLDP_PACKET_LEN = len(LLDPPacket.lldp_packet(0, 0, DONTCARE_STR, 0))

    LLDP_SEND_GUARD = .05
    LLDP_SEND_PERIOD_PER_PORT = .9
    TIMEOUT_CHECK_PERIOD = 5.
    LINK_TIMEOUT = TIMEOUT_CHECK_PERIOD * 2
    LINK_LLDP_DROP = 5

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

        self.name = 'switches' self.dps = {} #self.dps字典用於保存dpid到Datapath類實例的映射,會在_register函數中添加新成員,_unregister函數中刪除成員。遍歷該字典可以得到連接的所有交換機。 self.port_state = {} #字典中鍵為dpid,值為PortState類型。遍歷該字典可以得到所有交換機對應的端口情況。當交換機連接時,會檢查交換機的id是否在self.port_state中,不在則創建PortState類實例,把交換機的所有端口號和端口存儲到該實例中;交換機斷開時,會從self.port_state中刪除。 self.ports = PortDataState() #self.ports是PortDataState類的實例,保存每個端口(Port類型)對應的LLDP報文數據(保存在PortData類實例中),遍歷self.ports用於發送LLDP報文。 self.links = LinkState() #self.links是LinkState類的實例,保存所有連接(Link類型)到時間戳的映射。遍歷self.links的鍵即可得到所有交換機之間的連接情況。
        self.hosts = HostState()      # mac address -> Host class list
        self.is_active = True

        self.link_discovery = self.CONF.observe_links
        if self.link_discovery: #如果ryu-manager啟動時加了--observe-links參數,則下面的self.link_discovery將為真,從而執行if下面的語句:
            self.install_flow = self.CONF.install_lldp_flow
            self.explicit_drop = self.CONF.explicit_drop
            self.lldp_event = hub.Event()
            self.link_event = hub.Event() self.threads.append(hub.spawn(self.lldp_loop)) self.threads.append(hub.spawn(self.link_loop))

綜上所述,該初始化函數__init__()主要是創建用於存儲相關信息的數據結構,創建兩個事件,然后調用hub.spawn創建兩個新線程執行self.lldp_loop和self.link_loop兩個函數。

(二)其他成員方法

    def close(self):
        self.is_active = False
        if self.link_discovery:
            self.lldp_event.set()
            self.link_event.set()
            hub.joinall(self.threads)

 def _register(self, dp):
        assert dp.id is not None
 self.dps[dp.id] = dp if dp.id not in self.port_state: self.port_state[dp.id] = PortState() for port in dp.ports.values(): self.port_state[dp.id].add(port.port_no, port) #遍歷dp.ports.values,將所有port(OFPPort類型)添加到該PortState實例中。 
    def _unregister(self, dp):
        if dp.id in self.dps:
            if (self.dps[dp.id] == dp):
                del self.dps[dp.id]
                del self.port_state[dp.id]

    def _get_switch(self, dpid):
        if dpid in self.dps:
            switch = Switch(self.dps[dpid])
            for ofpport in self.port_state[dpid].values():
                switch.add_port(ofpport)
            return switch

    def _get_port(self, dpid, port_no):
        switch = self._get_switch(dpid)
        if switch:
            for p in switch.ports:
                if p.port_no == port_no:
                    return p

 def _port_added(self, port): lldp_data = LLDPPacket.lldp_packet( #調用靜態方法,構建LLDP數據報文 port.dpid, port.port_no, port.hw_addr, self.DEFAULT_TTL) self.ports.add_port(port, lldp_data) #將數據添加到端口中
        # LOG.debug('_port_added dpid=%s, port_no=%s, live=%s',
        #           port.dpid, port.port_no, port.is_live())

    def _link_down(self, port):
        try:
            dst, rev_link_dst = self.links.port_deleted(port)
        except KeyError:
            # LOG.debug('key error. src=%s, dst=%s',
            #           port, self.links.get_peer(port))
            return
        link = Link(port, dst)
        self.send_event_to_observers(event.EventLinkDelete(link))
        if rev_link_dst:
            rev_link = Link(dst, rev_link_dst)
            self.send_event_to_observers(event.EventLinkDelete(rev_link))
        self.ports.move_front(dst)

    def _is_edge_port(self, port):
        for link in self.links:
            if port == link.src or port == link.dst:
                return False

        return True

(三)交換機之間鏈路連接信息探測:發送(1)lldp_loop方法---由初始化函數中線程方法進行管理調用

    def lldp_loop(self):
        while self.is_active:
            self.lldp_event.clear()

            now = time.time() #當前時刻
            timeout = None
            ports_now = []
            ports = []
            for (key, data) in self.ports.items(): #遍歷self.ports(PortDataState類的實例),獲得key(Port類實例)和data(PortData類實例)  if data.timestamp is None: #如果data.timestamp為None(該端口還沒發送過LLDP報文) ports_now.append(key) #則將key(端口)加入ports_now列表; continue #------------ expire = data.timestamp + self.LLDP_SEND_PERIOD_PER_PORT #否則,計算下次應該發送LLDP報文的時間expire if expire <= now: #如果已經超時,則放到ports列表-----表示本來就應該發送下一次LLDP了 ports.append(key) continue #------------ timeout = expire - now #獲取timeout時間,表示還差多久到達期望的時間expire,進行LLDP數據包發送 break #否則就是還沒到發送時間,停止遍歷 #(發送LLDP報文時是按序發的,找到第一個未超時的端口,后面的端口肯定更沒有超時, #因為后面端口上次發送LLDP是在前一端口之后,前一個都沒超時后面的自然也沒超時) #注意:列表中都是按序的,先發ports_now中還沒有發送過LLDP數據的Port實例(data.timestamp為None), #然后查看已經發送過,但是已經超時(及本來應該進行下一次發送的)ports中的Port實例for port in ports_now: #遍歷ports_now列表,對每個端口調用self.send_lldp_packet(port),發送LLDP報文 self.send_lldp_packet(port) #--------發送LLDP報文 for port in ports: #遍歷ports列表,對每個端口調用self.send_lldp_packet(port),發送LLDP報文 self.send_lldp_packet(port) #--------發送LLDP報文 hub.sleep(self.LLDP_SEND_GUARD) # don't burst 設置睡眠時間,防止回送數據太多,導致出現等待時延 
 if timeout is not None and ports: #timeout!=None表示:還有端口Port實例沒有到達下一次發送LLDP報文的時機 Ports表示:本來應該發生LLDP報文的Port實例(超過本來周期)--上面發送過 timeout = 0 # We have already slept ---- ??設置為0---主要為了避免None在下面wait中出錯
            # LOG.debug('lldp sleep %s', timeout)
            self.lldp_event.wait(timeout=timeout) #事件等待timeout時刻

(四)交換機之間鏈路連接信息探測:發送(2)send_lldp_packet方法---由lldp_loop函數調用

    def send_lldp_packet(self, port):
        try:
  #!!!--------!!!重點:在發送時我們會將當前時刻保存在self.timestamp = time.time()中,后面我們接收到LLDP數據包時, #!!!--------!!!重點:到對應端口中獲取timestamp,然后現在時刻-timestamp獲得LLDP測量的C-S-S-C時間!!!   port_data = self.ports.lldp_sent(port) #調用PortDataState類的lldp_sent函數,該函數會設置時間戳, #移動相應端口在雙向循環鏈表中的位置,最后返回PortData類實例port_data。
        except KeyError:
            # ports can be modified during our sleep in self.lldp_loop()
            # LOG.debug('send_lld error', exc_info=True)
            return
        if port_data.is_down: #如果該端口已經down掉,直接返回,否則執行下一步
            return

        dp = self.dps.get(port.dpid, None) #獲取了datapath實例
        if dp is None: #根據port.dpid得到對應的Datapath類實例dp,如果不存在,則直接返回,否則執行下一步
            # datapath was already deleted
            return

        # LOG.debug('lldp sent dpid=%s, port_no=%d', dp.id, port.port_no)
        # TODO:XXX
 #發送LLDP報文。具體地: #(1)生成actions:從port.port_no端口發出消息; #(2)生成PacketOut消息:datapath指定為上一步得到的dp,actions為前面的,data為步驟a中返回的port_data的lldp_data if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
            actions = [dp.ofproto_parser.OFPActionOutput(port.port_no)]
            dp.send_packet_out(actions=actions, data=port_data.lldp_data)
        elif dp.ofproto.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
            actions = [dp.ofproto_parser.OFPActionOutput(port.port_no)] #設置actions out = dp.ofproto_parser.OFPPacketOut( #下發LLDP數據
                datapath=dp, in_port=dp.ofproto.OFPP_CONTROLLER,
                buffer_id=dp.ofproto.OFP_NO_BUFFER, actions=actions,
                data=port_data.lldp_data) #PortData類實例的lldp_data數據
            dp.send_msg(out)
        else:
            LOG.error('cannot send lldp packet. unsupported version. %x',
                      dp.ofproto.OFP_VERSION)

(五)交換機之間鏈路連接信息探測:接收(1)state_change_handler方法---由事件觸發函數調用(下發流表!!!)

    @set_ev_cls(ofp_event.EventOFPStateChange,
                [MAIN_DISPATCHER, DEAD_DISPATCHER]) #該函數用於處理EventOFPStateChange事件,當交換機連接或者斷開時會觸發該事件。
    def state_change_handler(self, ev):
        dp = ev.datapath
        assert dp is not None
        LOG.debug(dp)

        if ev.state == MAIN_DISPATCHER: #如果狀態是MAIN_DISPATCHER:
            dp_multiple_conns = False
            if dp.id in self.dps: #(1)從ev.datapath獲得Datapath類實例dp,如果該dp的dpid已經在self.dps里有,則報出重復鏈接的警告。
                LOG.warning('Multiple connections from %s', dpid_to_str(dp.id))
                dp_multiple_conns = True
                (self.dps[dp.id]).close()

 #(2)調用_register(),將dp.id和dp添加到self.dps中; #如果該dp.id不在self.port_state中,則創建該dp.id對應的PortState實例, #並遍歷dp.ports.values,將所有port(OFPPort類型)添加到該PortState實例中。
            self._register(dp)
 #(3)調用_get_switch(),如果dp.id在self.dps中,則創建一個Switch類實例, #並把self.port_state中對應的端口都添加到該實例中,最終返回該實例。 switch = self._get_switch(dp.id)
            LOG.debug('register %s', switch)

            if not dp_multiple_conns: #(4)如果交換機沒有重復連接,觸發EventSwitchEnter事件。
                self.send_event_to_observers(event.EventSwitchEnter(switch))
            else:
                evt = event.EventSwitchReconnected(switch)
                self.send_event_to_observers(evt)

            if not self.link_discovery: #(5)如果沒設置self.link_discovery,返回;否則執行下一步。 return #(6)如果設置了self.install_flow,則根據OpenFlow版本生成相應流表項, #使得收到的LLDP報文(根據目的MAC地址匹配)上報給控制器。 if self.install_flow:
                ofproto = dp.ofproto
                ofproto_parser = dp.ofproto_parser

                # TODO:XXX need other versions
                if ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
                    rule = nx_match.ClsRule()
                    rule.set_dl_dst(addrconv.mac.text_to_bin(
                                    lldp.LLDP_MAC_NEAREST_BRIDGE))
                    rule.set_dl_type(ETH_TYPE_LLDP)
                    actions = [ofproto_parser.OFPActionOutput(
                        ofproto.OFPP_CONTROLLER, self.LLDP_PACKET_LEN)]
                    dp.send_flow_mod(
                        rule=rule, cookie=0, command=ofproto.OFPFC_ADD,
                        idle_timeout=0, hard_timeout=0, actions=actions,
                        priority=0xFFFF)
                elif ofproto.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
 match = ofproto_parser.OFPMatch( eth_type=ETH_TYPE_LLDP, eth_dst=lldp.LLDP_MAC_NEAREST_BRIDGE) #重點:設置match---交換機獲取得到以太網LLDP數據包(由另一個交換機轉發而來)!!!!!
                    # OFPCML_NO_BUFFER is set so that the LLDP is not
                    # buffered on switch
                    parser = ofproto_parser
                    actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                                      ofproto.OFPCML_NO_BUFFER
                                                      )] #設置actions,當第二個交換機獲取到LLDP數據后,不需要緩存LLDP數據,全部上交給控制器
                    inst = [parser.OFPInstructionActions(
                            ofproto.OFPIT_APPLY_ACTIONS, actions)]
                    mod = parser.OFPFlowMod(datapath=dp, match=match,
                                            idle_timeout=0, hard_timeout=0,
                                            instructions=inst,
                                            priority=0xFFFF) # 標識流表的優先級,范圍為0-65535,值越大,優先級越高
                    dp.send_msg(mod) #發送給datapath else:
                    LOG.error('cannot install flow. unsupported version. %x',
                              dp.ofproto.OFP_VERSION)

            # Do not add ports while dp has multiple connections to controller.
            if not dp_multiple_conns:
                for port in switch.ports:
                    if not port.is_reserved():
                        self._port_added(port)

            self.lldp_event.set()

        elif ev.state == DEAD_DISPATCHER:
            # dp.id is None when datapath dies before handshake
            if dp.id is None:
                return

            switch = self._get_switch(dp.id)
            if switch:
                if switch.dp is dp:
                    self._unregister(dp)
                    LOG.debug('unregister %s', switch)
                    evt = event.EventSwitchLeave(switch)
                    self.send_event_to_observers(evt)

                    if not self.link_discovery:
                        return

                    for port in switch.ports:
                        if not port.is_reserved():
                            self.ports.del_port(port)
                            self._link_down(port)
                    self.lldp_event.set()

(六)交換機之間鏈路連接信息探測:接收(2)lldp_packet_in_handler方法---由事件觸發函數調用

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def lldp_packet_in_handler(self, ev):
        if not self.link_discovery:
            return

        msg = ev.msg
        try:
 src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data) #解析lldp數據包,獲取src_dpid和src_port_no
        except LLDPPacket.LLDPUnknownFormat:
            # This handler can receive all the packets which can be
            # not-LLDP packet. Ignore it silently
            return dst_dpid = msg.datapath.id #獲取目的datapath的dst_dpid if msg.datapath.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
            dst_port_no = msg.in_port #獲取目的datapath的dst_port_no
        elif msg.datapath.ofproto.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
            dst_port_no = msg.match['in_port'] else:
            LOG.error('cannot accept LLDP. unsupported version. %x',
                      msg.datapath.ofproto.OFP_VERSION)

        src = self._get_port(src_dpid, src_port_no)
        if not src or src.dpid == dst_dpid:
            return
        try:
            self.ports.lldp_received(src)
        except KeyError:
            # There are races between EventOFPPacketIn and
            # EventDPPortAdd. So packet-in event can happend before
            # port add event. In that case key error can happend.
            # LOG.debug('lldp_received error', exc_info=True)
            pass

        dst = self._get_port(dst_dpid, dst_port_no)
        if not dst:
            return

        old_peer = self.links.get_peer(src)
        # LOG.debug("Packet-In")
        # LOG.debug("  src=%s", src)
        # LOG.debug("  dst=%s", dst)
        # LOG.debug("  old_peer=%s", old_peer)
        if old_peer and old_peer != dst:
            old_link = Link(src, old_peer)
            del self.links[old_link]
            self.send_event_to_observers(event.EventLinkDelete(old_link))

        link = Link(src, dst)
        if link not in self.links:
            self.send_event_to_observers(event.EventLinkAdd(link))

            # remove hosts if it's not attached to edge port
            host_to_del = []
            for host in self.hosts.values():
                if not self._is_edge_port(host.port):
                    host_to_del.append(host.mac)

            for host_mac in host_to_del:
                del self.hosts[host_mac]

        if not self.links.update_link(src, dst):
            # reverse link is not detected yet.
            # So schedule the check early because it's very likely it's up
            self.ports.move_front(dst)
            self.lldp_event.set()
        if self.explicit_drop:
            self._drop_packet(msg)

重點:LLDP報文發送時,會將時間戳timestamp存放在端口實例中;如果我們想要獲取得到LLDP報文在網絡中的傳輸時間,則在控制器再次獲取得到LLDP報文時,會獲取得到當前時刻now_time,然后獲取對應端口在發送LLDP時保存的原始時間戳timestamp信息,通過now_time-timestamp獲取得到LLDP在網絡中的傳輸時間!!!

只分析到這一步!!!,我們可以獲取得到LLDP報文傳輸時間!!!,想要了解其他的,請參考:Ryu拓撲發現原理分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM