Esempio n. 1
0
    def __init__(self, *args, **kwargs):
        super(MultipathController, self).__init__(*args, **kwargs)

        # Random ethertype to evaluate latency
        self.PROBE_ETHERTYPE = 0x07C7

        # Set to true once there is enoough informatio
        # to start multipath computation
        self.network_is_measured = False

        # Maximum delay imbalance to use a reordering buffer
        # Example 25ms,50ms = |(25/75)-0.5| = 0.16
        self.MDI_REORDERING_THRESHOLD = 0.2

        # Maximum delay imbalance threshold for not using multipath
        self.MDI_DROP_THRESHOLD = 0.25

        # Minimum available capacity a path needs to be used in B/s
        self.MIN_MULTIPATH_CAPACITY = 1000

        # Maximum paths allowed for a multipath flow
        self.MAX_PATHS_PER_MULTIPATH_FLOW = 2

        # Recalculates bucket only on addition or failures in the topology
        self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False

        # Used for REST APIs
        wsgi = kwargs['wsgi']
        wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self})

        # Holds the topology data and structure
        self.topo_shape = NetworkTopology(self)
Esempio n. 2
0
    def __init__(self, *args, **kwargs):
        super(MultipathController, self).__init__(*args, **kwargs)

        # Random ethertype to evaluate latency
        self.PROBE_ETHERTYPE = 0x07C7

        # Set to true once there is enoough informatio
        # to start multipath computation
        self.network_is_measured = False

        # Maximum delay imbalance to use a reordering buffer
        # Example 25ms,50ms = |(25/75)-0.5| = 0.16
        self.MDI_REORDERING_THRESHOLD = 0.2

        # Maximum delay imbalance threshold for not using multipath
        self.MDI_DROP_THRESHOLD = 0.25

        # If this value is changed in the configuration, the path setup
        # algorithm is halted if a path has more hops to the destination
        # than this limit.
        # If left -1, MDI_DROP will be used instead!
        # It is suited for L2 Networks where link delays will be
        # very similar
        self.MAX_HOP_DIFFERENCE = -1

        # Minimum available capacity a path needs to be used in B/s
        self.MIN_MULTIPATH_CAPACITY = 100

        # Maximum paths allowed for a multipath flow
        self.MAX_PATHS_PER_MULTIPATH_FLOW = 2

        # Recalculates bucket only on addition or failures in the topology
        self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False

        # Recomputes forwarding continuously reardless of congestion/failures
        self.UPDATE_FORWARDING_CONTINOUSLY = False

        # High priority
        self.PRIORITY_PROBE_PACKETS = 65000

        # Monitoring frequency for port stats.
        self.MONITORING_PORT_STATS_FREQUENCY = 5

        self.MONITORING_PORT_STATS = False

        # Used for REST APIs
        wsgi = kwargs['wsgi']
        wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self})

        # Holds the topology data and structure
        self.topo_shape = NetworkTopology(self)
    def __init__(self, *args, **kwargs):
        super(MultipathController, self).__init__(*args, **kwargs)

        # Random ethertype to evaluate latency
        self.PROBE_ETHERTYPE = 0x07C7

        # Set to true once there is enoough informatio
        # to start multipath computation
        self.network_is_measured = False

        # Maximum delay imbalance to use a reordering buffer
        # Example 25ms,50ms = |(25/75)-0.5| = 0.16
        self.MDI_REORDERING_THRESHOLD = 0.2

        # Maximum delay imbalance threshold for not using multipath
        self.MDI_DROP_THRESHOLD = 0.25

        # Minimum available capacity a path needs to be used in B/s
        self.MIN_MULTIPATH_CAPACITY = 1000

        # Maximum paths allowed for a multipath flow
        self.MAX_PATHS_PER_MULTIPATH_FLOW = 2

        # Recalculates bucket only on addition or failures in the topology
        self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False

        # Used for REST APIs
        wsgi = kwargs['wsgi']
        wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self})

        # Holds the topology data and structure
        self.topo_shape = NetworkTopology(self)
Esempio n. 4
0
 def topology(self):
     network = str(self.txtNetwork_tab3.text())
     dem = str(self.txtDEM_tab3.text())
     NetworkTopology(network, dem)
     self.close()
class MultipathController(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _CONTEXTS = {'wsgi': WSGIApplication}

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

        # Random ethertype to evaluate latency
        self.PROBE_ETHERTYPE = 0x07C7

        # Set to true once there is enoough informatio
        # to start multipath computation
        self.network_is_measured = False

        # Maximum delay imbalance to use a reordering buffer
        # Example 25ms,50ms = |(25/75)-0.5| = 0.16
        self.MDI_REORDERING_THRESHOLD = 0.2

        # Maximum delay imbalance threshold for not using multipath
        self.MDI_DROP_THRESHOLD = 0.25

        # Minimum available capacity a path needs to be used in B/s
        self.MIN_MULTIPATH_CAPACITY = 1000

        # Maximum paths allowed for a multipath flow
        self.MAX_PATHS_PER_MULTIPATH_FLOW = 2

        # Recalculates bucket only on addition or failures in the topology
        self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False

        # Used for REST APIs
        wsgi = kwargs['wsgi']
        wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self})

        # Holds the topology data and structure
        self.topo_shape = NetworkTopology(self)

    ##########################################
    #              UTILITY FUNCTIONS         #
    ##########################################

    def run(self):
        '''
        Called to start the monitoring/computation in the controller
        It can be called with a GET to the API:
        /multipath/start_path_computation
        '''

        # Network monitor module
        hub.spawn_after(3, self.network_monitor)

        # Multipath computation module
        hub.spawn_after(5, self.multipath_computation)

    def add_flow(self, datapath, priority, match, actions, buffer_id=None):
        ''' Adds a flow to a datapath '''
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
        if buffer_id:
            mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
                                    priority=priority, match=match,
                                    instructions=inst)
        else:
            mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                    match=match, instructions=inst)
        datapath.send_msg(mod)

    def _send_packet(self, datapath, port, pkt):
        ''' Instructs a datapath to output a packet to one of his ports '''
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        pkt.serialize()
        self.logger.debug('packet-out %s' % (pkt,))
        data = pkt.data
        actions = [parser.OFPActionOutput(port=port)]
        out = parser.OFPPacketOut(datapath=datapath,
                                  buffer_id=ofproto.OFP_NO_BUFFER,
                                  in_port=ofproto.OFPP_CONTROLLER,
                                  actions=actions,
                                  data=data)
        datapath.send_msg(out)

    def set_edge_ports(self):

        # Setting the edge port [port connected to host networks]
        # of a switch to be the highest in number
        for dp_id, switch in self.topo_shape.dpid_to_switch.iteritems():
            switch.edge_port = len(switch.ports.keys())

    def multipath_computation(self):

        self.set_edge_ports()

        while True:
            if not self.topo_shape.is_empty() and self.network_is_measured:
                computation_start = time.time()
                self.logger.info('Starting multipath computation sub-routine')
                self.topo_shape.multipath_computation()
                self.logger.info(
                    'Multipath computation finished in %f seconds',
                    time.time() - computation_start
                )
            hub.sleep(10)

    ##########################################
    #      NETWORK DISCOVERY HANDLERS        #
    ##########################################

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        '''
        Switch features handler callback
        '''
        self.logger.debug('EventOFPSwitchFeatures')
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)]
        self.add_flow(datapath, 0, match, actions)

        # Installing the flow rules to send latency probe packets
        match = parser.OFPMatch(eth_type=self.PROBE_ETHERTYPE)
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)]
        self.add_flow(datapath, 65000, match, actions)

    @set_ev_cls(event.EventSwitchEnter, MAIN_DISPATCHER)
    def switch_enter_handler(self, event):
        self.logger.info('EventSwitchEnter')
        self.topo_shape.add_switch(event.switch)

    @set_ev_cls(event.EventSwitchLeave, MAIN_DISPATCHER)
    def switch_leave_handler(self, event):
        self.logger.info('EventSwitchLeave')
        self.topo_shape.remove_switch(event.switch)

    @set_ev_cls(event.EventLinkAdd, MAIN_DISPATCHER)
    def link_add_handler(self, event):
        self.logger.info('EventLinkAdd')
        self.topo_shape.add_link(event)

    
    @set_ev_cls(event.EventLinkDelete, MAIN_DISPATCHER)
    def link_delete_handler(self, event):
        self.logger.info('EventLinkDelete %s' % event)
        # It seems to be called at the wrong moment for a bug
        # in ryu topology discovery module
        # self.topo_shape.remove_link(event)

    @set_ev_cls(event.EventPortAdd, MAIN_DISPATCHER)
    def port_add_handler(self, event):
        self.logger.info('EventPortAdd')
        self.topo_shape.add_port(event.port)

    @set_ev_cls(event.EventPortDelete, MAIN_DISPATCHER)
    def port_delete_handler(self, event):
        self.logger.info('EventPortDelete')
        self.topo_shape.remove_port(event.port)

    ##########################################
    #              NETWORK MONITORING        #
    ##########################################

    def network_monitor(self):
        ''' Monitors network RTT and Congestion '''

        self.logger.info('Starting monitoring sub-routine')
        # First, we get an estimation of the link benchmark_network_capacity
        # in a state where the network will be idle.
        self.benchmark_network_capacity()

        # Then we start the periodic measurement of RTT times and port
        # utilization

        counter = 0
        while True:
            if not self.topo_shape.is_empty():
                self.logger.info('Requesting port stats to '
                                 'measure utilization')
                for dpid, s in self.topo_shape.dpid_to_switch.iteritems():

                    s.port_stats_request_time = time.time()

                    # Requesting portstats to calculate controller
                    # to switch delay and congeston
                    self._request_port_stats(s)

                    # Calculating peering switches RTT (once every 10 portstats
                    # so ~10 secs)
                    if counter % 10 == 0:
                        self.send_latency_probe_packet(s)
                counter += 1

            hub.sleep(1)

    def benchmark_network_capacity(self):
        '''
        This mechanism is left for future work.
        It can send a high rate UDP from hosts to destination to and measure
        on the receiving switch the drop rate of the packets, to infer the link
        capacity
        Currently the max capacity is set through REST APIs
        '''

        pass

    def send_latency_probe_packet(self, switch):
        '''
        Injects latency probe packets in the network
        '''
        self.logger.info('Injecting latency probe packets')

        for peer_switch, peer_port in switch.peer_to_local_port.iteritems():

            self.logger.debug('Sending probe packet from %s to %s through '
                              ' port %s',
                              switch, peer_switch, peer_port
                              )

            actions = [switch.dp.ofproto_parser.OFPActionOutput(peer_port)]

            pkt = packet.Packet()
            pkt.add_protocol(ethernet.ethernet(ethertype=self.PROBE_ETHERTYPE,
                                               dst=0x000000000001,
                                               src=0x000000000000)
                             )

            pkt.serialize()
            payload = '%d;%d;%f' % (
                switch.dp.id, peer_switch.dp.id, time.time())
            data = pkt.data + payload

            out = switch.dp.ofproto_parser.OFPPacketOut(
                datapath=switch.dp,
                buffer_id=switch.dp.ofproto.OFP_NO_BUFFER,
                data=data,
                in_port=switch.dp.ofproto.OFPP_CONTROLLER,
                actions=actions
            )

            switch.dp.send_msg(out)

    def probe_packet_handler(self, pkt):
        '''
        Handles a latency probe packets and computes the
        delay between two switches
        '''
        try:
            receive_time = time.time()
            # Ignoring 14 bytes of ethernet header
            data = pkt.data[14:].split(';')
            send_dpid = int(data[0])
            recv_dpid = int(data[1])
            inc_time = float(data[2])
            sample_delay = receive_time - inc_time
            self.topo_shape.dpid_to_switch[send_dpid].calculate_delay_to_peer(
                self.topo_shape.dpid_to_switch[recv_dpid], sample_delay)
            self.network_is_measured = True
        except:
            traceback.print_exc()
            self.logger.error('Unable to parse incoming probe packet')

    @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
    def _port_stats_reply_handler(self, ev):
        '''
        Handles a PORT STATS response from a switch
        '''

        ports = ev.msg.body
        port_stats_reply_time = time.time()

        # Calculate switch-controller RTT
        switch = self.topo_shape.dpid_to_switch[ev.msg.datapath.id]
        switch.calculate_delay_to_controller(port_stats_reply_time)

        sorted_port_table = sorted(ports, key=lambda l: l.port_no)
        for stat in sorted_port_table:
            if stat.port_no not in switch.ports:
                continue
            port = switch.ports[stat.port_no]
            utilization = stat.tx_bytes + stat.rx_bytes

            if port.last_request_time:
                timedelta = port_stats_reply_time - port.last_request_time
                datadelta = utilization - port.last_utilization_value

                utilization_bps = datadelta / timedelta
                port.capacity = port.max_capacity - utilization_bps
                self.logger.debug('p %d s %s utilization %f real_capacity %f',
                                  stat.port_no, switch, datadelta, port.capacity)

            port.last_request_time = port_stats_reply_time
            port.last_utilization_value = utilization

    @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
    def _flow_stats_reply_handler(self, ev):
        '''
        Handles a FLOW STATS reply
        '''

        self.logger.debug(
            'Receive flow stats response from: %016x at t: %f',
            ev.msg.datapath.id,
            time.time()
        )

        body = ev.msg.body

        self.logger.debug('datapath         '
                          'in-port  eth-dst         '
                          'out-port packets  bytes')

        self.logger.debug('---------------- '
                          '-------- ----------------- '
                          '-------- -------- --------')

        self.logger.debug(ev.msg.body)
        for stat in sorted(
            [flow for flow in body if flow.priority == 1],
            key=lambda flow: (flow.match['in_port'], flow.match['eth_dst'])
        ):
            self.logger.info(
                '%016x %8x %17s %8x %8d %8d',
                ev.msg.datapath.id,
                stat.match['in_port'], stat.match['eth_dst'],
                stat.instructions[0].actions[0].port,
                stat.packet_count, stat.byte_count
            )

    def _request_flow_stats(self, switch):
        '''
        Requests flow stats for a switch
        '''
        self.logger.debug(
            'Request flow stats for: %016x at t: %f',
            switch.dp.id, time.time()
        )
        parser = switch.dp.ofproto_parser
        req = parser.OFPFlowStatsRequest(switch.dp)
        switch.dp.send_msg(req)

    def _request_port_stats(self, switch):
        '''
        Request port statistic to a switch
        '''
        self.logger.debug(
            'Request port stats for dp %s at t: %f',
            switch.dp.id, time.time()
        )
        ofproto = switch.dp.ofproto
        parser = switch.dp.ofproto_parser
        req = parser.OFPPortStatsRequest(switch.dp, 0, ofproto.OFPP_ANY)
        switch.dp.send_msg(req)

    ##########################################
    #             PACKET IN HANDLER          #
    ##########################################

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        msg = ev.msg
        pkt = packet.Packet(msg.data)

        pkt_ethernet = pkt.get_protocols(ethernet.ethernet)[0]
        datapath = msg.datapath
        port = msg.match['in_port']

        # Checking if it's the probe packet for RTT estimation
        if pkt_ethernet.ethertype == self.PROBE_ETHERTYPE:
            self.probe_packet_handler(pkt)
            return

        # Ignoring LLDP Packets
        try:
            LLDPPacket.lldp_parse(msg.data)
            self.logger.debug('Received LLDP Packet')
            return
        except:
            pass

        self.logger.debug('EventOFPPacketIn %s' % pkt)

        # Handling ARP
        pkt_arp = pkt.get_protocol(arp.arp)
        if pkt_arp:
            self._handle_arp(msg, datapath, port, pkt, pkt_arp)
            return

        pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
        pkt_icmp = pkt.get_protocol(icmp.icmp)

        # Handling ICMPv4
        if pkt_icmp:
            self._handle_icmpv4(
                msg, datapath, port, pkt_ethernet, pkt_ipv4, pkt_icmp)
            return

        # Handing IPv4
        if pkt_ipv4:
            self._handle_ipv4(datapath, port, pkt_ethernet, pkt_ipv4)
            return

        pkt_ipv6 = pkt.get_protocol(ipv6.ipv6)
        pkt_icmp = pkt.get_protocol(icmpv6.icmpv6)

        # Handling ICMPv6
        if pkt_icmp:
            self._handle_icmpv6(
                datapath, port, pkt_ethernet, pkt_ipv6, pkt_icmp)
            return

        # Handing IPv6
        if pkt_ipv4:
            self._handle_ipv6(datapath, port, pkt_ethernet, pkt_ipv4)
            return

        # Unhandled
        self.logger.debug('Unknown packet %s' % str(pkt))

    def _handle_arp(self, msg, datapath, in_port_no, pkt, pkt_arp):

        self.logger.debug('ARP Packet %s' % pkt_arp)

    def _handle_icmpv4(self, msg, datapath, in_port_no, pkt_ethernet, pkt_ipv4,
                       pkt_icmp):

        self.logger.debug('Handling ICMP packet %s', pkt_icmp)

    def _handle_ipv4(self, datapath, port, pkt_ethernet, pkt_ipv4):
        self.logger.debug(
            'Handling ip packet  from port %d - %s' % (port, pkt_ipv4)
        )

    def _handle_ipv6(self, datapath, port, pkt_ethernet, pkt_ipv6):
        self.logger.debug('Handling ipv6 packet')

    def _handle_icmpv6(self, datapath, port, pkt_ethernet, pkt_ipv6, pkt_icmp):
        self.logger.debug('Handling ICMPv6 Packet')
Esempio n. 6
0
class MultipathController(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _CONTEXTS = {'wsgi': WSGIApplication}

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

        # Random ethertype to evaluate latency
        self.PROBE_ETHERTYPE = 0x07C7

        # Set to true once there is enoough informatio
        # to start multipath computation
        self.network_is_measured = False

        # Maximum delay imbalance to use a reordering buffer
        # Example 25ms,50ms = |(25/75)-0.5| = 0.16
        self.MDI_REORDERING_THRESHOLD = 0.2

        # Maximum delay imbalance threshold for not using multipath
        self.MDI_DROP_THRESHOLD = 0.25

        # If this value is changed in the configuration, the path setup
        # algorithm is halted if a path has more hops to the destination
        # than this limit.
        # If left -1, MDI_DROP will be used instead!
        # It is suited for L2 Networks where link delays will be
        # very similar
        self.MAX_HOP_DIFFERENCE = -1

        # Minimum available capacity a path needs to be used in B/s
        self.MIN_MULTIPATH_CAPACITY = 100

        # Maximum paths allowed for a multipath flow
        self.MAX_PATHS_PER_MULTIPATH_FLOW = 2

        # Recalculates bucket only on addition or failures in the topology
        self.UPDATE_FORWARDING_ON_TOPOLOGY_CHANGE_ONLY = False

        # Recomputes forwarding continuously reardless of congestion/failures
        self.UPDATE_FORWARDING_CONTINOUSLY = False

        # High priority
        self.PRIORITY_PROBE_PACKETS = 65000

        # Monitoring frequency for port stats.
        self.MONITORING_PORT_STATS_FREQUENCY = 5

        self.MONITORING_PORT_STATS = False

        # Used for REST APIs
        wsgi = kwargs['wsgi']
        wsgi.register(MultipathRestController, {API_INSTANCE_NAME: self})

        # Holds the topology data and structure
        self.topo_shape = NetworkTopology(self)

    ##########################################
    #              UTILITY FUNCTIONS         #
    ##########################################

    def run(self):
        '''
        Called to start the monitoring/computation in the controller
        It can be called with a GET to the API:
        /multipath/start_path_computation
        '''

        # Network monitor module
        self.monitoringhub = hub.spawn(self.network_monitor)

        # Multipath computation module
        hub.spawn_after(5, self.multipath_computation)

    def add_flow(self, datapath, priority, match, actions, buffer_id=None):
        ''' Adds a flow to a datapath '''
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [
            parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)
        ]
        if buffer_id:
            mod = parser.OFPFlowMod(datapath=datapath,
                                    buffer_id=buffer_id,
                                    priority=priority,
                                    match=match,
                                    instructions=inst)
        else:
            mod = parser.OFPFlowMod(datapath=datapath,
                                    priority=priority,
                                    match=match,
                                    instructions=inst)
        datapath.send_msg(mod)

    def _send_packet(self, datapath, port, pkt):
        ''' Instructs a datapath to output a packet to one of his ports '''
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        pkt.serialize()
        self.logger.debug('packet-out %s' % (pkt, ))
        data = pkt.data
        actions = [parser.OFPActionOutput(port=port)]
        out = parser.OFPPacketOut(datapath=datapath,
                                  buffer_id=ofproto.OFP_NO_BUFFER,
                                  in_port=ofproto.OFPP_CONTROLLER,
                                  actions=actions,
                                  data=data)
        datapath.send_msg(out)

    def set_edge_ports(self):
        # Setting the edge port [port connected to host networks]
        # of a switch to be the highest in number
        # (In mininet it corresponds to the host port when they are
        # added at the end of the topology creation)
        for dp_id, switch in self.topo_shape.dpid_to_switch.iteritems():
            switch.edge_port = len(switch.ports.keys())

    def multipath_computation(self):
        while True:
            if not self.topo_shape.is_empty() and self.network_is_measured:
                computation_start = time.time()
                self.logger.info('Starting multipath computation sub-routine')
                self.topo_shape.multipath_computation()
                self.logger.info(
                    'Multipath computation finished in %f seconds',
                    time.time() - computation_start)

            self.MONITORING_PORT_STATS = True
            if self.UPDATE_FORWARDING_CONTINOUSLY:
                hub.sleep(10)
            else:
                break

    def add_default_flows(self, datapath):
        '''
        Adds default unknown flows to the controller
        '''

        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)]
        self.add_flow(datapath, 1, match, actions)

        # Installing the flow rules to send latency probe packets
        match = parser.OFPMatch(eth_type=self.PROBE_ETHERTYPE)
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)]
        self.add_flow(datapath, self.PRIORITY_PROBE_PACKETS, match, actions)

    def add_default_for_all(self):
        for dpid, s in self.topo_shape.dpid_to_switch:
            self.add_default_flows(s.dp)

    def stop_monitoring(self):
        self.keep_monitoring = False

    ##########################################
    #      NETWORK DISCOVERY HANDLERS        #
    ##########################################

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        '''
        Switch features handler callback
        '''
        self.logger.debug('EventOFPSwitchFeatures')
        self.add_default_flows(ev.msg.datapath)

    @set_ev_cls(event.EventSwitchEnter, MAIN_DISPATCHER)
    def switch_enter_handler(self, event):
        self.logger.info('EventSwitchEnter')
        self.topo_shape.add_switch(event.switch)

    @set_ev_cls(event.EventSwitchLeave, MAIN_DISPATCHER)
    def switch_leave_handler(self, event):
        self.logger.debug('EventSwitchLeave')

        # Redundant for Mininet emulated network since
        # you cannot remove switches/link once the topology
        # has started

        # self.topo_shape.remove_switch(event.switch)

    @set_ev_cls(event.EventLinkAdd, MAIN_DISPATCHER)
    def link_add_handler(self, event):
        self.logger.info('EventLinkAdd')
        self.topo_shape.add_link(event)

    @set_ev_cls(event.EventLinkDelete, MAIN_DISPATCHER)
    def link_delete_handler(self, event):
        self.logger.debug('EventLinkDelete %s' % event)
        # Redundant for Mininet emulated network since
        # you cannot remove switches/link once the topology
        # has started

        # self.topo_shape.remove_link(event)

    @set_ev_cls(event.EventPortAdd, MAIN_DISPATCHER)
    def port_add_handler(self, event):
        self.logger.info('EventPortAdd')
        self.topo_shape.add_port(event.port)

    @set_ev_cls(event.EventPortDelete, MAIN_DISPATCHER)
    def port_delete_handler(self, event):
        self.logger.debug('EventPortDelete')

        # Redundant for Mininet emulated network since
        # you cannot remove switches/link once the topology
        # has started

        # self.topo_shape.remove_port(event.port)

    ##########################################
    #              NETWORK MONITORING        #
    ##########################################

    def network_monitor(self):
        ''' Monitors network RTT and Congestion '''

        self.logger.info('Starting monitoring sub-routine')
        # First, we get an estimation of the link benchmark_network_capacity
        # in a state where the network will be idle.
        self.benchmark_network_capacity()

        self.keep_monitoring = True
        # Then we start the periodic measurement of RTT times and port
        # utilization

        counter = 0
        while self.keep_monitoring:
            if not self.topo_shape.is_empty():
                self.logger.debug('Requesting port stats to '
                                  'measure utilization')
                self.logger.info('\n------------------')
                for dpid, s in self.topo_shape.dpid_to_switch.iteritems():

                    s.port_stats_request_time = time.time()

                    # Requesting portstats to calculate controller
                    # to switch delay and congeston
                    # if self.MONITORING_PORT_STATS:
                    self._request_port_stats(s)

                    # Calculating peering switches RTT (once every 10 portstats
                    # so ~10 secs)
                    if counter % 30 == 0:
                        self.send_latency_probe_packet(s)

                counter += 1

            hub.sleep(self.MONITORING_PORT_STATS_FREQUENCY)

        self.logger.info('Stopping monitor')

    def benchmark_network_capacity(self):
        '''
        This mechanism is left for future work.
        It can send a high rate UDP from hosts to destination to and measure
        on the receiving switch the drop rate of the packets, to infer the link
        capacity
        Currently the max capacity is set through REST APIs
        '''

        pass

    def send_latency_probe_packet(self, switch):
        '''
        Injects latency probe packets in the network
        '''
        self.logger.info('Injecting latency probe packets')

        for peer_switch, peer_port in switch.peer_to_local_port.iteritems():

            self.logger.debug(
                'Sending probe packet from %s to %s through '
                ' port %s', switch, peer_switch, peer_port)

            actions = [switch.dp.ofproto_parser.OFPActionOutput(peer_port)]

            pkt = packet.Packet()
            pkt.add_protocol(
                ethernet.ethernet(ethertype=self.PROBE_ETHERTYPE,
                                  dst=0x000000000001,
                                  src=0x000000000000))

            pkt.serialize()
            payload = '%d;%d;%f' % (switch.dp.id, peer_switch.dp.id,
                                    time.time())
            data = pkt.data + payload

            out = switch.dp.ofproto_parser.OFPPacketOut(
                datapath=switch.dp,
                buffer_id=switch.dp.ofproto.OFP_NO_BUFFER,
                data=data,
                in_port=switch.dp.ofproto.OFPP_CONTROLLER,
                actions=actions)

            switch.dp.send_msg(out)

    def probe_packet_handler(self, pkt):
        '''
        Handles a latency probe packets and computes the
        delay between two switches
        '''
        try:
            receive_time = time.time()
            # Ignoring 14 bytes of ethernet header
            data = pkt.data[14:].split(';')
            send_dpid = int(data[0])
            recv_dpid = int(data[1])
            inc_time = float(data[2])
            sample_delay = receive_time - inc_time
            self.topo_shape.dpid_to_switch[send_dpid].calculate_delay_to_peer(
                self.topo_shape.dpid_to_switch[recv_dpid], sample_delay)
            self.network_is_measured = True
        except:
            traceback.print_exc()
            self.logger.error('Unable to parse incoming probe packet')

    @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
    def _port_stats_reply_handler(self, ev):
        '''
        Handles a PORT STATS response from a switch
        '''

        ports = ev.msg.body
        port_stats_reply_time = time.time()

        # Calculate switch-controller RTT
        switch = self.topo_shape.dpid_to_switch[ev.msg.datapath.id]
        switch.calculate_delay_to_controller(port_stats_reply_time)

        sorted_port_table = sorted(ports, key=lambda l: l.port_no)
        for stat in sorted_port_table:
            if stat.port_no not in switch.ports:
                continue
            port = switch.ports[stat.port_no]
            utilization = stat.tx_bytes + stat.rx_bytes

            if port.last_request_time:
                timedelta = port_stats_reply_time - port.last_request_time
                datadelta = utilization - port.last_utilization_value

                utilization_bps = datadelta / timedelta
                port.capacity = port.max_capacity - utilization_bps

                self.logger.info(
                    's[%s] p[%d] utilization %2.f max_capacity %s',
                    switch.dp.id, stat.port_no, utilization_bps,
                    port.max_capacity)
            port.last_request_time = port_stats_reply_time
            port.last_utilization_value = utilization

    @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
    def _flow_stats_reply_handler(self, ev):
        '''
        Handles a FLOW STATS reply
        '''

        self.logger.debug('Receive flow stats response from: %016x at t: %f',
                          ev.msg.datapath.id, time.time())

        body = ev.msg.body

        self.logger.debug('datapath         '
                          'in-port  eth-dst         '
                          'out-port packets  bytes')

        self.logger.debug('---------------- '
                          '-------- ----------------- '
                          '-------- -------- --------')

        self.logger.debug(ev.msg.body)
        for stat in sorted([flow for flow in body if flow.priority == 1],
                           key=lambda flow:
                           (flow.match['in_port'], flow.match['eth_dst'])):
            self.logger.info('%016x %8x %17s %8x %8d %8d', ev.msg.datapath.id,
                             stat.match['in_port'], stat.match['eth_dst'],
                             stat.instructions[0].actions[0].port,
                             stat.packet_count, stat.byte_count)

    def _request_flow_stats(self, switch):
        '''
        Requests flow stats for a switch
        '''
        self.logger.debug('Request flow stats for: %016x at t: %f',
                          switch.dp.id, time.time())
        parser = switch.dp.ofproto_parser
        req = parser.OFPFlowStatsRequest(switch.dp)
        switch.dp.send_msg(req)

    def _request_port_stats(self, switch):
        '''
        Request port statistic to a switch
        '''
        self.logger.debug('Request port stats for dp %s at t: %f',
                          switch.dp.id, time.time())
        ofproto = switch.dp.ofproto
        parser = switch.dp.ofproto_parser
        req = parser.OFPPortStatsRequest(switch.dp, 0, ofproto.OFPP_ANY)
        switch.dp.send_msg(req)

    ##########################################
    #             PACKET IN HANDLER          #
    ##########################################

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        msg = ev.msg
        pkt = packet.Packet(msg.data)

        pkt_ethernet = pkt.get_protocols(ethernet.ethernet)[0]
        datapath = msg.datapath
        port = msg.match['in_port']

        # Checking if it's the probe packet for RTT estimation
        if pkt_ethernet.ethertype == self.PROBE_ETHERTYPE:
            self.probe_packet_handler(pkt)
            return

        # Ignoring LLDP Packets
        try:
            LLDPPacket.lldp_parse(msg.data)
            self.logger.debug('Received LLDP Packet')
            return
        except:
            pass

        self.logger.debug('EventOFPPacketIn %s' % pkt)

        # Handling ARP
        pkt_arp = pkt.get_protocol(arp.arp)
        if pkt_arp:
            self._handle_arp(msg, datapath, port, pkt, pkt_arp)
            return

        pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
        pkt_icmp = pkt.get_protocol(icmp.icmp)

        # Handling ICMPv4
        if pkt_icmp:
            self._handle_icmpv4(msg, datapath, port, pkt_ethernet, pkt_ipv4,
                                pkt_icmp)
            return

        # Handing IPv4
        if pkt_ipv4:
            self._handle_ipv4(datapath, port, pkt_ethernet, pkt_ipv4)
            return

        pkt_ipv6 = pkt.get_protocol(ipv6.ipv6)
        pkt_icmp = pkt.get_protocol(icmpv6.icmpv6)

        # Handling ICMPv6
        if pkt_icmp:
            self._handle_icmpv6(datapath, port, pkt_ethernet, pkt_ipv6,
                                pkt_icmp)
            return

        # Handing IPv6
        if pkt_ipv4:
            self._handle_ipv6(datapath, port, pkt_ethernet, pkt_ipv4)
            return

        # Unhandled
        self.logger.debug('Unknown packet %s' % str(pkt))

    def _handle_arp(self, msg, datapath, in_port_no, pkt, pkt_arp):

        self.logger.debug('ARP Packet %s' % pkt_arp)

    def _handle_icmpv4(self, msg, datapath, in_port_no, pkt_ethernet, pkt_ipv4,
                       pkt_icmp):

        self.logger.debug('Handling ICMP packet %s', pkt_icmp)

    def _handle_ipv4(self, datapath, port, pkt_ethernet, pkt_ipv4):
        self.logger.debug('Handling ip packet  from port %d - %s' %
                          (port, pkt_ipv4))

    def _handle_ipv6(self, datapath, port, pkt_ethernet, pkt_ipv6):
        self.logger.debug('Handling ipv6 packet')

    def _handle_icmpv6(self, datapath, port, pkt_ethernet, pkt_ipv6, pkt_icmp):
        self.logger.debug('Handling ICMPv6 Packet')