示例#1
0
 def __init__(self, *args, **kwargs):
     super(Topology, self).__init__(*args, **kwargs)
     self.is_active = True
     self.explicit_drop = True
     self.ports = PortDataState()
     self.links = _LinkState()
     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))
 def __init__(self, *args, **kwargs):
     super(L2Switch, self).__init__(*args, **kwargs)
     self.name = 'switches'
     self.dps = {}  # datapath_id => Datapath class
     self.port_state = {}  # datapath_id => ports
     self.ports = PortDataState()  # Port class -> PortData class
     self.links = LinkState()  # Link class -> timestamp
     self.is_active = True
     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))
     self.link_discovery = True
示例#3
0
 def __init__(self, *args, **kwargs):
     super(L2Switch, self).__init__(*args, **kwargs)
     self.name = 'switches'
     self.dps = {}                 # datapath_id => Datapath class
     self.port_state = {}          # datapath_id => ports
     self.ports = PortDataState()  # Port class -> PortData class
     self.links = LinkState()      # Link class -> timestamp
     self.is_active = True
     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))
     self.link_discovery = True
示例#4
0
class Topology(LocalService):
    _EVENTS = {EventPortAdd,
               EventPortDelete,
               EventPortModify,
               EventLinkAdd,
               EventLinkDelete}
    """
    Ryu topology module ported to Local Controller
    """
    DEFAULT_TTL = 64
    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
    LLDP_PRIORITY = 0xFFFF

    def __init__(self, *args, **kwargs):
        super(Topology, self).__init__(*args, **kwargs)
        self.is_active = True
        self.explicit_drop = True
        self.ports = PortDataState()
        self.links = _LinkState()
        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))

    def _port_added(self, port):
        lldp_data = LLDPPacket.lldp_packet(
            port.dpid, port.port_no, port.hw_addr, self.DEFAULT_TTL)
        self.ports.add_port(port, lldp_data)

    def _switch_enter(self, dp):
        super(Topology, self)._switch_enter(dp)
        self._init_flows()
        ports = [Port(PortData(dp.id, port, dp.ofproto)) for port in
                 dp.ports.values()]
        for port in ports:
            if not port.is_reserved():
                self._port_added(port)
        self.ryuo.switch_enter(dp.id,
                               [port.port_data for port in self.ports.keys()])
        self.lldp_event.set()

    def _init_flows(self):
        self._logger.info('Init flow table.')
        self.ofctl.set_packet_in_flow(cookie=0, priority=self.LLDP_PRIORITY,
                                      eth_type=ETH_TYPE_LLDP,
                                      eth_dst=lldp.LLDP_MAC_NEAREST_BRIDGE)

    def _switch_leave(self):
        super(Topology, self)._switch_leave()
        self.links.clear()
        self.ports.clear()

    def _get_port(self, port_no):
        for port in self.ports.keys():
            if port.port_no == port_no:
                return port

    def _report_port_added(self, port):
        self.send_event_to_observers(EventPortAdd(port))
        self.ryuo.port_added(port.port_data)

    def _report_port_deleted(self, port):
        self.send_event_to_observers(EventPortDelete(port))
        self.ryuo.port_deleted(port.port_data)

    def _report_port_modified(self, port):
        self.send_event_to_observers(EventPortModify(port))
        self.ryuo.port_modified(port.port_data)

    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def _port_status_change(self, ev):
        if self.dp is None:
            return
        msg = ev.msg
        reason = msg.reason
        dp = msg.datapath
        ofpport = msg.desc
        ofp = dp.ofproto

        if reason == ofp.OFPPR_ADD:
            self._logger.info('Port %d added.', ofpport.port_no)
            port = Port(PortData(self.dp.id, ofpport, ofp))
            if not port.is_reserved():
                self._port_added(port)
                self._report_port_added(port)
                self.lldp_event.set()
        elif reason == ofp.OFPPR_DELETE:
            self._logger.info('Port %d deleted.', ofpport.port_no)
            port = self._get_port(ofpport.port_no)
            if port and not port.is_reserved():
                del self.ports[Port(PortData(self.dp.id, ofpport))]
                self._report_port_deleted(port)
                self._link_down(port)
                self.lldp_event.set()
        else:
            self._logger.info('Port %d modified.', ofpport.port_no)
            port = self._get_port(ofpport.port_no)
            port.lldp_reply = False
            if port and not port.is_reserved():
                port.modify(ofpport)
                self._report_port_modified(port)
                if self.ports.set_down(port):
                    self._link_down(port)
                self.lldp_event.set()

    def send_lldp_packet(self, port):
        try:
            port_data = self.ports.lldp_sent(port)
        except KeyError as e:
            # ports can be modified during our sleep in self.lldp_loop()
            self._logger.warning('Missing port %d, %s.', port.port_no, e)
            return
        if port_data.is_down:
            return

        dp = self.dp
        if dp is None:
            # datapath was already deleted
            self._logger.warning('Switch left.')
            return

        self.ofctl.send_packet_out(in_port=dp.ofproto.OFPP_CONTROLLER,
                                   output=port.port_no,
                                   data=port_data.lldp_data)

    def lldp_loop(self):
        while self.is_active:
            self._logger.debug('LLDP loop')
            self.lldp_event.clear()

            now = time.time()
            timeout = None
            ports_now = []
            ports = []
            for (key, data) in self.ports.items():
                if data.timestamp is None:
                    ports_now.append(key)
                    continue

                expire = data.timestamp + self.LLDP_SEND_PERIOD_PER_PORT
                if expire <= now:
                    ports.append(key)
                    continue

                if timeout is None or timeout > expire - now:
                    timeout = expire - now
                    # break

            for port in ports_now:
                self.send_lldp_packet(port)
                self._logger.debug('Sending LLDP to %d.%d',
                                   port.dpid,
                                   port.port_no)
            for port in ports:
                self.send_lldp_packet(port)
                self._logger.debug('Sending LLDP to %d.%d',
                                   port.dpid,
                                   port.port_no)
                hub.sleep(self.LLDP_SEND_GUARD)  # don't burst

            if timeout is not None and ports:
                timeout = 0  # We have already slept
            # LOG.debug('lldp sleep %s', timeout)
            self.lldp_event.wait(timeout=timeout)

    def _report_link_deleted(self, link):
        self.send_event_to_observers(EventLinkDelete(link))
        self.ryuo.link_deleted(link.src.port_data, link.dst.port_data)

    def _report_link_added(self, link):
        self.send_event_to_observers(EventLinkAdd(link))
        self.ryuo.link_added(link.src.port_data, link.dst.port_data)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        self._logger.debug('Packet in')
        msg = ev.msg
        ofp = msg.datapath.ofproto
        pkt = packet.Packet(msg.data)
        headers = dict((p.protocol_name, p)
                       for p in pkt.protocols if type(p) != str)
        src_mac = headers[ETHERNET].src
        try:
            src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
        except LLDPPacket.LLDPUnknownFormat as e:
            self._logger.debug('LLDPUnknownFormat %s', e)
            return
        dst_port_no = self.ofctl.get_packet_in_inport(msg)
        dst = self._get_port(dst_port_no)
        self._logger.debug('LLDP from %d.%d -> %d',
                           src_dpid,
                           src_port_no,
                           dst_port_no)
        if not dst:
            self._logger.warning('Dst not found.')
            return
        self.ports.lldp_received(dst)
        src = Port(PortData(src_dpid, port_no=src_port_no, hw_addr=src_mac))
        old_peer = self.links.get_peer(dst)
        need_update = False
        if old_peer and old_peer != src:
            self._logger.info('Peer changed.')
            self._report_link_deleted(Link(old_peer, dst))
            need_update = True
        link = Link(src, dst)
        if link not in self.links:
            need_update = True
            self._report_link_added(Link(src, dst))
            self.lldp_event.set()

        # Always return false, since we don't have the reverse link information
        if need_update:
            self.links.update_link(src, dst)
            self._logger.info('Update link %d.%d -> %d.%d',
                              src.dpid,
                              src.port_no,
                              dst.dpid,
                              dst.port_no)

    def link_loop(self):
        while self.is_active:
            self.link_event.clear()

            now = time.time()
            deleted = []
            for (link, timestamp) in self.links.items():
                # LOG.debug('%s timestamp %d (now %d)', link, timestamp, now)
                if timestamp + self.LINK_TIMEOUT < now:
                    src = link.src
                    if src in self.ports:
                        port_data = self.ports.get_port(src)
                        # LOG.debug('port_data %s', port_data)
                        if port_data.lldp_dropped() > self.LINK_LLDP_DROP:
                            deleted.append(link)

            for link in deleted:
                self.links.link_down(link)
                # LOG.debug('delete %s', link)
                self._report_link_deleted(link)

            self.link_event.wait(timeout=self.TIMEOUT_CHECK_PERIOD)

    def _link_down(self, port):
        try:
            src, rev_link_dst = self.links.dst_port_deleted(port)
            self._logger.info('Link down: %d.%d -> %d.%d',
                              src.dpid,
                              src.port_no,
                              port.dpid,
                              port.port_no)
        except KeyError:
            self._logger.info('Cannot find peer of %d.%d',
                              port.dpid,
                              port.port_no)
            return
        self._report_link_deleted(Link(src, port))

    def close(self):
        self.is_active = False
        self.lldp_event.set()
        self.link_event.set()
        super(Topology, self).close()
class L2Switch(RyuApp):
    _EVENTS = [
        event.EventSwitchEnter, event.EventSwitchLeave, event.EventPortAdd,
        event.EventPortDelete, event.EventPortModify, event.EventLinkAdd,
        event.EventLinkDelete, event.EventArpReceived
    ]

    OFP_VERSIONS = [OFP_VERSION]

    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 = 15.
    LINK_TIMEOUT = TIMEOUT_CHECK_PERIOD * 2
    LINK_LLDP_DROP = 5

    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)
        self.name = 'switches'
        self.dps = {}  # datapath_id => Datapath class
        self.port_state = {}  # datapath_id => ports
        self.ports = PortDataState()  # Port class -> PortData class
        self.links = LinkState()  # Link class -> timestamp
        self.is_active = True
        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))
        self.link_discovery = True

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

    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():
                if data.timestamp is None:
                    ports_now.append(key)
                    continue

                expire = data.timestamp + self.LLDP_SEND_PERIOD_PER_PORT
                if expire <= now:
                    ports.append(key)
                    continue

                timeout = expire - now
                break
            for port in ports_now:
                self.send_lldp_packet(port.dpid, port.port_no, port.hw_addr)
            for port in ports:
                self.send_lldp_packet(port.dpid, port.port_no, port.hw_addr)
                hub.sleep(self.LLDP_SEND_GUARD)  # don't burst

            if timeout is not None and ports:
                timeout = 0  # We have already slept
            self.lldp_event.wait(timeout=timeout)

    def link_loop(self):
        while self.is_active:
            self.link_event.clear()

            now = time.time()
            deleted = []
            for (link, timestamp) in self.links.items():
                if timestamp + self.LINK_TIMEOUT < now:
                    src = link.src
                    if src in self.ports:
                        port_data = self.ports.get_port(src)
                        if port_data.lldp_dropped() > self.LINK_LLDP_DROP:
                            deleted.append(link)

            for link in deleted:
                self.links.link_down(link)
                self.send_event_to_observers(event.EventLinkDelete(link))

                dst = link.dst
                rev_link = Link(dst, link.src)
                if rev_link not in deleted:
                    # It is very likely that the reverse link is also
                    # disconnected. Check it early.
                    expire = now - self.LINK_TIMEOUT
                    self.links.rev_link_set_timestamp(rev_link, expire)
                    if dst in self.ports:
                        self.ports.move_front(dst)
                        self.lldp_event.set()

            self.link_event.wait(timeout=self.TIMEOUT_CHECK_PERIOD)

    def _get_switch(self, dpid):
        if dpid in self.dps:
            switch = Switch(self.dps[dpid])
            for ofpport in self.port_state[dpid].itervalues():
                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(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 _register(self, dp):
        assert dp.id is not None
        assert dp.id not in self.dps

        self.dps[dp.id] = dp
        self.port_state[dp.id] = PortState()
        for port in dp.ports.values():
            self.port_state[dp.id].add(port.port_no, port)

    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)

    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def port_status_handler(self, ev):
        msg = ev.msg
        reason = msg.reason
        dp = msg.datapath
        ofpport = msg.desc

        if reason == dp.ofproto.OFPPR_ADD:
            LOG.debug(
                'A port was added.' + '(datapath id = %s, port number = %s)',
                dp.id, ofpport.port_no)
            self.port_state[dp.id].add(ofpport.port_no, ofpport)
            self.send_event_to_observers(
                event.EventPortAdd(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                self._port_added(port)
                self.lldp_event.set()

        elif reason == dp.ofproto.OFPPR_DELETE:
            LOG.debug(
                'A port was deleted.' + '(datapath id = %s, port number = %s)',
                dp.id, ofpport.port_no)
            self.port_state[dp.id].remove(ofpport.port_no)
            self.send_event_to_observers(
                event.EventPortDelete(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                self.ports.del_port(port)
                self._link_down(port)
                self.lldp_event.set()

        else:
            assert reason == dp.ofproto.OFPPR_MODIFY
            LOG.debug(
                'A port was modified.' +
                '(datapath id = %s, port number = %s)', dp.id, ofpport.port_no)
            self.port_state[dp.id].modify(ofpport.port_no, ofpport)
            self.send_event_to_observers(
                event.EventPortModify(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                if self.ports.set_down(port):
                    self._link_down(port)
                    self.lldp_event.set()

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        """Handle switch features reply to install table miss flow entries."""
        datapath = ev.msg.datapath
        self.install_table_miss(datapath, 0)

    def create_match(self, parser, fields):
        """Create OFP match struct from the list of fields."""
        match = parser.OFPMatch()
        for a in fields:
            match.append_field(*a)
        return match

    def send_barrier_request(self, datapath):
        ofp_parser = datapath.ofproto_parser
        req = ofp_parser.OFPBarrierRequest(datapath)
        datapath.send_msg(req)

    def send_lldp_packet(self, datapath_id, port_no, dl_addr):
        datapath = self.dps.get(datapath_id, None)
        if datapath is None:
            #datapath was already deleted
            return
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        lldp_data = LLDPPacket.lldp_packet(datapath.id, port_no, dl_addr,
                                           self.DEFAULT_TTL)
        output_port = parser.OFPActionOutput(port_no, ofproto.OFPCML_NO_BUFFER)
        packet_out = parser.OFPPacketOut(datapath, ofproto.OFPP_ANY,
                                         ofproto.OFPP_CONTROLLER,
                                         [output_port], lldp_data)
        datapath.send_msg(packet_out)

    @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER)
    def barrier_reply_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        self._register(datapath)
        switch = self._get_switch(datapath.id)
        self.send_event_to_observers(event.EventSwitchEnter(switch))
        for port in switch.ports:
            if not port.is_reserved():
                self._port_added(port)
        self.lldp_event.set()

    def create_flow_mod(self, datapath, priority, table_id, match,
                        instructions):
        """Create OFP flow mod message."""
        ofproto = datapath.ofproto
        flow_mod = datapath.ofproto_parser.OFPFlowMod(
            datapath, 0, 0, table_id, ofproto.OFPFC_ADD, 0, 0, priority,
            ofproto.OFPCML_NO_BUFFER, ofproto.OFPP_ANY, OFPG_ANY, 0, match,
            instructions)
        return flow_mod

    def install_table_miss(self, datapath, table_id):
        """Create and install table miss flow entries."""
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = parser.OFPMatch()
        match.set_dl_type(ETH_TYPE_LLDP)
        output = parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                        self.LLDP_PACKET_LEN)
        write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS,
                                             [output])
        instructions = [write]
        flow_mod = self.create_flow_mod(datapath, 0, table_id, match,
                                        instructions)
        datapath.send_msg(flow_mod)
        self.send_barrier_request(datapath)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        """Handle packet_in events."""
        if not self.link_discovery:
            return
        msg = ev.msg
        in_port = msg.match['in_port']
        packet = Packet(msg.data)
        efm = packet.get_protocol(ethernet.ethernet)
        if efm.ethertype == ether.ETH_TYPE_ARP:
            self.send_event_to_observers(event.EventArpReceived(ev))

        try:
            src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
        except LLDPPacket.LLDPUnknownFormat as e:
            # This handler can receive all the packtes which can be
            # not-LLDP packet. Ignore it silently
            return
        else:
            dst_dpid = msg.datapath.id
            dst_port_no = in_port

            src = self._get_port(src_dpid, src_port_no)
            if not src or src.dpid == dst_dpid:
                return

            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)
                self.send_event_to_observers(event.EventLinkDelete(old_link))

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

            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
                try:
                    self.ports.lldp_received(dst)
                except KeyError as e:
                    # 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: KeyError %s', e)
                    pass
                else:
                    self.ports.move_front(dst)
                    self.lldp_event.set()
示例#6
0
class L2Switch(RyuApp):
    _EVENTS = [event.EventSwitchEnter, event.EventSwitchLeave,
               event.EventPortAdd, event.EventPortDelete,
               event.EventPortModify,
               event.EventLinkAdd, event.EventLinkDelete,
               event.EventArpReceived]

    OFP_VERSIONS = [OFP_VERSION]
    
    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 = 15.
    LINK_TIMEOUT = TIMEOUT_CHECK_PERIOD * 2
    LINK_LLDP_DROP = 5

    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)
        self.name = 'switches'
        self.dps = {}                 # datapath_id => Datapath class
        self.port_state = {}          # datapath_id => ports
        self.ports = PortDataState()  # Port class -> PortData class
        self.links = LinkState()      # Link class -> timestamp
        self.is_active = True
        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))
        self.link_discovery = True
    
    def close(self):
        self.is_active = False
        if self.link_discovery:
            self.lldp_event.set()
            self.link_event.set()
            hub.joinall(self.threads)

    def send_flow_stats_request(self, datapath):
        ofp_parser = datapath.ofproto_parser
        req = ofp_parser.OFPGroupDescStatsRequest(datapath, 0)
        if datapath.id == 3:
            datapath.send_msg(req)

    @set_ev_cls(ofp_event.EventOFPGroupDescStatsReply, MAIN_DISPATCHER)
    def flow_stats_reply_handler(self, ev):
        descs = []
        for stat in ev.msg.body:
            descs.append('length=%d type=%d group_id=%d '
                     'buckets=%s' %
                     (stat.length, stat.type, stat.group_id,
                      stat.buckets))
        LOG.debug('GroupDescStats: %s', descs)

    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():
                if data.timestamp is None:
                    ports_now.append(key)
                    continue

                expire = data.timestamp + self.LLDP_SEND_PERIOD_PER_PORT
                if expire <= now:
                    ports.append(key)
                    continue

                timeout = expire - now
                break
            for port in ports_now:
                self.send_lldp_packet(port.dpid, 
                                      port.port_no,
                                      port.hw_addr)
            for port in ports:
                self.send_lldp_packet(port.dpid,
                                      port.port_no,
                                      port.hw_addr)
                hub.sleep(self.LLDP_SEND_GUARD)      # don't burst

            if timeout is not None and ports:
                timeout = 0     # We have already slept
            self.lldp_event.wait(timeout=timeout)
  
    def link_loop(self):
        while self.is_active:
            self.link_event.clear()

            now = time.time()
            deleted = []
            for (link, timestamp) in self.links.items():
                if timestamp + self.LINK_TIMEOUT < now:
                    src = link.src
                    if src in self.ports:
                        port_data = self.ports.get_port(src)
                        if port_data.lldp_dropped() > self.LINK_LLDP_DROP:
                            deleted.append(link)
                #for dp in self.dps.values():
                    #self.send_flow_stats_request(dp)

            for link in deleted:
                self.links.link_down(link)
                self.send_event_to_observers(event.EventLinkDelete(link))

                dst = link.dst
                rev_link = Link(dst, link.src)
                if rev_link not in deleted:
                    # It is very likely that the reverse link is also
                    # disconnected. Check it early.
                    expire = now - self.LINK_TIMEOUT
                    self.links.rev_link_set_timestamp(rev_link, expire)
                    if dst in self.ports:
                        self.ports.move_front(dst)
                        self.lldp_event.set()

            self.link_event.wait(timeout=self.TIMEOUT_CHECK_PERIOD)

    def _get_switch(self, dpid):
        if dpid in self.dps:
            switch = Switch(self.dps[dpid])
            for ofpport in self.port_state[dpid].itervalues():
                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(
            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 _register(self, dp):
        assert dp.id is not None
        assert dp.id not in self.dps

        self.dps[dp.id] = dp
        self.port_state[dp.id] = PortState()
        for port in dp.ports.values():
            self.port_state[dp.id].add(port.port_no, port)
        
    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)

    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def port_status_handler(self, ev):
        msg = ev.msg
        reason = msg.reason
        dp = msg.datapath
        ofpport = msg.desc

        if reason == dp.ofproto.OFPPR_ADD:
            LOG.debug('A port was added.' +
                      '(datapath id = %s, port number = %s)',
                      dp.id, ofpport.port_no)
            self.port_state[dp.id].add(ofpport.port_no, ofpport)
            self.send_event_to_observers(
                event.EventPortAdd(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                self._port_added(port)
                self.lldp_event.set()

        elif reason == dp.ofproto.OFPPR_DELETE:
            LOG.debug('A port was deleted.' +
                      '(datapath id = %s, port number = %s)',
                      dp.id, ofpport.port_no)
            self.port_state[dp.id].remove(ofpport.port_no)
            self.send_event_to_observers(
                event.EventPortDelete(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                self.ports.del_port(port)
                self._link_down(port)
                self.lldp_event.set()

        else:
            assert reason == dp.ofproto.OFPPR_MODIFY
            LOG.debug('A port was modified.' +
                      '(datapath id = %s, port number = %s)',
                      dp.id, ofpport.port_no)
            self.port_state[dp.id].modify(ofpport.port_no, ofpport)
            self.send_event_to_observers(
                event.EventPortModify(Port(dp.id, dp.ofproto, ofpport)))

            if not self.link_discovery:
                return

            port = self._get_port(dp.id, ofpport.port_no)
            if port and not port.is_reserved():
                if self.ports.set_down(port):
                    self._link_down(port)
                    self.lldp_event.set()

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        """Handle switch features reply to install table miss flow entries."""
        datapath = ev.msg.datapath
        self.install_table_miss(datapath, 0)

    def create_match(self, parser, fields):
        """Create OFP match struct from the list of fields."""
        match = parser.OFPMatch()
        for a in fields:
            match.append_field(*a)
        return match

    def send_barrier_request(self, datapath):
        ofp_parser = datapath.ofproto_parser
        req = ofp_parser.OFPBarrierRequest(datapath)
        datapath.send_msg(req)

    def send_lldp_packet(self, datapath_id, port_no, dl_addr):
        datapath = self.dps.get(datapath_id, None)
        if datapath is None:
            #datapath was already deleted
            return
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        lldp_data = LLDPPacket.lldp_packet(datapath.id,
                        port_no,
                        dl_addr,
                        self.DEFAULT_TTL)
        output_port = parser.OFPActionOutput(port_no,
                                            ofproto.OFPCML_NO_BUFFER)
        packet_out = parser.OFPPacketOut(datapath, ofproto.OFPP_ANY,
                                          ofproto.OFPP_CONTROLLER,
                                          [output_port], lldp_data)
        datapath.send_msg(packet_out)

    @set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER)
    def barrier_reply_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        self._register(datapath)
        switch = self._get_switch(datapath.id)
        self.send_event_to_observers(event.EventSwitchEnter(switch))
        for port in switch.ports:
            if not port.is_reserved():
                self._port_added(port)
        self.lldp_event.set()
        
    def create_flow_mod(self, datapath, priority,
                        table_id, match, instructions):
        """Create OFP flow mod message."""
        ofproto = datapath.ofproto
        flow_mod = datapath.ofproto_parser.OFPFlowMod(datapath, 0, 0, table_id,
                                                      ofproto.OFPFC_ADD, 0, 0,
                                                      priority,
                                                      ofproto.OFPCML_NO_BUFFER,
                                                      ofproto.OFPP_ANY,
                                                      OFPG_ANY, 0,
                                                      match, instructions)
        return flow_mod
    
    def install_table_miss(self, datapath, table_id):
        """Create and install table miss flow entries."""
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = parser.OFPMatch()
        match.set_dl_type(ETH_TYPE_LLDP)
        output = parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                        self.LLDP_PACKET_LEN)
        write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS,
                                             [output])
        instructions = [write]
        flow_mod = self.create_flow_mod(datapath, 0, table_id,
                                        match, instructions)
        datapath.send_msg(flow_mod)
        self.send_barrier_request(datapath)
    
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        """Handle packet_in events."""
        if not self.link_discovery:
            return
        msg = ev.msg
        in_port = msg.match['in_port']
        packet = Packet(msg.data)
        efm = packet.next()
        if efm.ethertype == ether.ETH_TYPE_ARP:
            self.send_event_to_observers(event.EventArpReceived(ev))

        try:
            src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
        except LLDPPacket.LLDPUnknownFormat as e:
            # This handler can receive all the packtes which can be
            # not-LLDP packet. Ignore it silently
            return
        else:
            dst_dpid = msg.datapath.id
            dst_port_no = in_port

            src = self._get_port(src_dpid, src_port_no)
            if not src or src.dpid == dst_dpid:
                return

            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)
                self.send_event_to_observers(event.EventLinkDelete(old_link))

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

            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
                try:
                    self.ports.lldp_received(dst)
                except KeyError as e:
                    # 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: KeyError %s', e)
                    pass
                else:
                    self.ports.move_front(dst)
                    self.lldp_event.set()