예제 #1
0
class LSDB(object):

    BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))

    def __init__(self):
        self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        self.private_address_network = ip_network(CFG.get(DEFAULTSECT,
                                                  'private_net'))
        try:
            with open(CFG.get(DEFAULTSECT, 'private_ips'), 'r') as f:
                self.private_address_binding = json.load(f)
                self.router_private_address = {}
                for subnets in self.private_address_binding.itervalues():
                    for rid, ip in subnets.iteritems():
                        try:
                            iplist = self.router_private_address[rid]
                        except KeyError:
                            iplist = self.router_private_address[rid] = []
                        # Enable single private address as string
                        if isinstance(ip, str):
                            ip = [ip]
                        iplist.extend(ip)
        except Exception as e:
            log.warning('Incorrect private IP addresses binding file')
            log.warning(str(e))
            self.private_address_binding = {}
            self.router_private_address = {}
        self.last_line = ''
        self.leader_watchdog = None
        self.transaction = None
        self.graph = IGPGraph()
        self.routers = {}  # router-id : lsa
        self.networks = {}  # DR IP : lsa
        self.ext_networks = {}  # (router-id, dest) : lsa
        self.controllers = defaultdict(list)  # controller nr : ip_list
        self.listener = {}
        self.keep_running = True
        self.queue = Queue()
        self.processing_thread = Thread(target=self.process_lsa,
                                        name="lsa_processing_thread")
        self.processing_thread.setDaemon(True)
        self.processing_thread.start()

    def set_leader_watchdog(self, wd):
        self.leader_watchdog = wd

    def get_leader(self):
        return min(self.controllers.iterkeys()) if self.controllers else None

    def stop(self):
        for l in self.listener.values():
            l.session.stop()
        self.keep_running = False
        self.queue.put('')

    def lsdb(self, lsa):
        if lsa.TYPE == RouterLSA.TYPE:
            return self.routers
        elif lsa.TYPE == NetworkLSA.TYPE:
            return self.networks
        elif lsa.TYPE == ASExtLSA.TYPE:
            return self.ext_networks

    def register_change_listener(self, listener):
        try:
            del self.listener[listener]
            log.info('Shapeshifter disconnected.')
        except KeyError:
            log.info('Shapeshifter connected.')
            l = ProxyCloner(ShapeshifterProxy, listener)
            self.listener[listener] = l
            l.bootstrap_graph(graph=[(u, v, d.get('metric', -1))
                                     for u, v, d in self.graph.edges(data=True)
                                     ],
                              node_properties={n: data for n, data in
                                               self.graph.nodes_iter(data=True)
                                               })

    @staticmethod
    def extract_lsa_properties(lsa_part):
        d = {}
        for prop in lsa_part.split(SEP_INTER_FIELD):
            if not prop:
                continue
            key, val = prop.split(SEP_INTRA_FIELD)
            d[key] = val
        return d

    def commit_change(self, line):
        # Check that this is not a duplicate of a previous update ...
        if self.last_line == line:
            return
        self.queue.put(line)

    def forwarding_address_of(self, src, dst):
        """
        Return the forwarding address for a src, dst pair. If src is specified, return
        the private 'link-local' address of the src-dst link, otherwise return a 'public'
        IP belonging to dst
        :param src: the source node of the link towards the FA, possibly null
        :param dst: the node owning the forwarding address
        :return: forwarding address (str) or None if no compatible address was found
        """
        try:
            return self.graph[src][dst]['dst_address'] if src \
                else self.graph[dst][self.graph.neighbors(dst)[0]]['src_address']
        except KeyError:
            log.debug('%s-%s not found in graph', src, dst)
            return None

    def remove_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            del lsdb[lsa.key()]
        except KeyError:
            pass

    def add_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        lsdb[lsa.key()] = lsa

    def process_lsa(self):
        while self.keep_running:
            commit = False
            try:
                line = self.queue.get(timeout=5)
                if not line:
                    self.queue.task_done()
                    continue
                # Start parsing the LSA log
                action, lsa_info = line.split(SEP_ACTION)
                if action == BEGIN:
                    self.transaction = Transaction()
                elif action == COMMIT:
                    if self.transaction:
                        self.transaction.commit(self)
                        self.transaction = None
                        commit = True
                else:
                    lsa_parts = [self.extract_lsa_properties(part)
                                 for part in lsa_info.split(SEP_GROUP) if part]
                    lsa = LSA.parse(LSAHeader.parse(lsa_parts.pop(0)),
                                    lsa_parts)
                    log.debug('Parsed %s: %s', action, lsa)
                    if action == REM:
                        if not self.transaction:
                            self.remove_lsa(lsa)
                        else:
                            self.transaction.remove_lsa(lsa)
                    elif action == ADD:
                        if not self.transaction:
                            self.add_lsa(lsa)
                        else:
                            self.transaction.add_lsa(lsa)
                    if lsa.push_update_on_remove() or not action == REM:
                        commit = True
                self.queue.task_done()
            except Empty:
                if self.transaction:
                    log.debug('Splitting transaction due to timeout')
                    self.transaction.commit(self)
                    self.transaction = Transaction()
                    commit = True
            if commit:
                # Update graph accordingly
                new_graph = self.build_graph()
                # Compute graph difference and update it
                self.update_graph(new_graph)

    def __str__(self):
        strs = [str(lsa) for lsa in chain(self.routers.values(),
                                          self.networks.values(),
                                          self.ext_networks.values())]
        strs.insert(0, '* LSDB Content [%d]:' % len(strs))
        return '\n'.join(strs)

    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.values(),
                         self.networks.values(),
                         self.ext_networks.values()):
            lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for lsa in self.routers.values():
            lsa.contract_graph(new_graph, self.router_private_address.get(
                lsa.routerid, []))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask
                """
                id = (((int(addr) - int(self.BASE_NET.network_address)) >>
                       self.BASE_NET.max_prefixlen - controller_prefix) &
                      ((1 << controller_prefix) - 1))
                self.controllers[id].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph

    def update_graph(self, new_graph):
        self.leader_watchdog.check_leader(self.get_leader())
        added_edges = new_graph.difference(self.graph)
        removed_edges = self.graph.difference(new_graph)
        node_prop_diff = {n: data
                          for n, data in new_graph.nodes_iter(data=True)
                          if n not in self.graph or
                          (data.viewitems() - self.graph.node[n].viewitems())}
        # Propagate differences
        if added_edges or removed_edges or node_prop_diff:
            log.debug('Pushing changes')
            for u, v in added_edges:
                self.for_all_listeners('add_edge',
                                       u, v, metric=new_graph.metric(u, v))
            for u, v in removed_edges:
                self.for_all_listeners('remove_edge', u, v)
            if node_prop_diff:
                self.for_all_listeners('update_node_properties',
                                       **node_prop_diff)
            if CFG.getboolean(DEFAULTSECT, 'draw_graph'):
                new_graph.draw(CFG.get(DEFAULTSECT, 'graph_loc'))
            self.graph = new_graph
            log.info('LSA update yielded +%d -%d edges changes, '
                    '%d node property changes', len(added_edges),
                    len(removed_edges), len(node_prop_diff))
            self.for_all_listeners('commit')

    def for_all_listeners(self, funcname, *args, **kwargs):
        """Apply funcname to all listeners"""
        for i in self.listener.itervalues():
            getattr(i, funcname)(*args, **kwargs)

    def apply_secondary_addresses(self, graph):
        for subnet in self.private_address_binding.itervalues():
            for dst, ip in subnet.iteritems():
                for src in subnet.iterkeys():
                    if src == dst:
                        continue
                    try:
                        graph[src][dst]['dst_address'] = ip
                    except KeyError:
                        pass
예제 #2
0
class LSDB(object):

    def __init__(self):
        self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        self.private_addresses = PrivateAddressStore(CFG.get(DEFAULTSECT,
                                                             'private_ips'))
        self.last_line = ''
        self.leader_watchdog = None
        self.transaction = False
        self.uncommitted_changes = 0
        self.graph = IGPGraph()
        self._lsdb = {NetworkLSA.TYPE: {},
                      RouterLSA.TYPE: {},
                      ASExtLSA.TYPE: {}}
        self.controllers = defaultdict(list)  # controller nr : ip_list
        self.listener = {}
        self.keep_running = True
        self.queue = Queue()
        self.processing_thread = start_daemon_thread(
                target=self.process_lsa, name='lsa processing thread')

    @property
    def routers(self):
        return self._lsdb[RouterLSA.TYPE]

    @property
    def networks(self):
        return self._lsdb[NetworkLSA.TYPE]

    @property
    def ext_networks(self):
        return self._lsdb[ASExtLSA.TYPE]

    def set_leader_watchdog(self, wd):
        self.leader_watchdog = wd

    def get_leader(self):
        return min(self.controllers.iterkeys()) if self.controllers else None

    def stop(self):
        for l in self.listener.values():
            l.session.stop()
        self.keep_running = False
        self.queue.put('')

    def lsdb(self, lsa):
        return self._lsdb.get(lsa.TYPE, None)

    def register_change_listener(self, listener):
        try:
            del self.listener[listener]
            log.info('Shapeshifter disconnected.')
        except KeyError:
            log.info('Shapeshifter connected.')
            l = ProxyCloner(ShapeshifterProxy, listener)
            self.listener[listener] = l
            l.bootstrap_graph(graph=[(u, v, d)
                                     for u, v, d in self.graph.export_edges()
                                     ],
                              node_properties={n: data for n, data in
                                               self.graph.nodes_iter(data=True)
                                               })

    def commit_change(self, line):
        # Check that this is not a duplicate of a previous update ...
        if self.last_line == line or not line:
            return
        self.queue.put(line)

    def forwarding_address_of(self, src, dst):
        """
        Return the forwarding address for a src, dst pair.
        If src is specified, return the private 'link-local' address of
        the src-dst link, otherwise return a 'public' IP belonging to dst
        :param src: the source node of the link towards the FA, possibly null
        :param dst: the node owning the forwarding address
        :return: forwarding address (str)
                or None if no compatible address was found
        """
        # If we have a src address, we want the set of private IPs
        # Otherwise we want any IP of dst
        if src:
            try:
                return self.graph[src][dst]['dst_address']
            except KeyError as e:
                log.error("Couldn't resolve local forwarding of %s-%s, missing"
                          " key %s", src, dst, e)
        else:
            try:
                data = filter(lambda v: v is not None,
                              (self.graph[dst][succ].get('src_address', None)
                               for succ in self.graph.successors_iter(dst)))
                if data:
                    return min(data)
                log.error("Cannot use %s as nexthop as it has no physical "
                          "link to other routers!", dst)
            except KeyError:
                log.error("Couldn't find nexthop %s when resolving global "
                          "forwarding address", dst)
        return None

    def remove_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            del lsdb[lsa.key()]
        except (KeyError, TypeError):  # LSA not found, lsdb is None
            pass

    def add_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            lsdb[lsa.key()] = lsa
        except TypeError:  # LSDB is None
            pass

    def get_current_seq_number(self, lsa):
        try:
            return self.lsdb(lsa)[lsa.key()].seqnum
        except (KeyError, TypeError):  # LSA not found, LSDB is None
            return None

    def is_old_seqnum(self, lsa):
        """Return whether the lsa is older than the copy in the lsdb is any"""
        c_seqnum = self.get_current_seq_number(lsa)
        new_seqnum = lsa.seqnum
        return (c_seqnum and  # LSA already present in the LSDB
                not is_newer_seqnum(new_seqnum, c_seqnum) and
                # We allow duplicate as they are used to flush LSAs
                new_seqnum != c_seqnum)

    def handle_lsa_line(self, line):
        """We received a line describing an lsa, handle it"""
        action, lsa_info = line.split(SEP_ACTION)
        if action == BEGIN:
            self.start_transaction()
        elif action == COMMIT:
            self.reset_transaction()
        else:  # ADD/REM LSA messages
            lsa = parse_lsa(lsa_info)
            log.debug('Parsed %s: %s [%d]', action, lsa, lsa.seqnum)
            # Sanity checks
            if self.is_old_seqnum(lsa):
                log.debug("OLD seqnum for LSA, ignoring it...")
                action = None

            # Perform the update if it is still applicable
            if action == REM:
                self.remove_lsa(lsa)
                self.uncommitted_changes += 1
            elif action == ADD:
                self.add_lsa(lsa)
                self.uncommitted_changes += 1

    def process_lsa(self):
        """Parse new LSAs, and update the graph if needed"""
        while self.keep_running:
            try:
                line = self.queue.get(timeout=5)
                self.queue.task_done()
                if not line:
                    continue
                self.handle_lsa_line(line)
            except Empty:
                self.reset_transaction()
            if (self.queue.empty() and  # Try to empty the queue before update
                    not self.transaction and self.uncommitted_changes):
                self.commit()

    def reset_transaction(self):
        """Reset the transaction"""
        self.transaction = False

    def start_transaction(self):
        """Record a new LSA transaction"""
        self.transaction = True

    def commit(self):
        """Updates have been made on the LSDB, update the graph"""
        # Update graph accordingly
        new_graph = self.build_graph()
        # Compute graph difference and update it
        self.update_graph(new_graph)
        self.uncommitted_changes = 0

    def __str__(self):
        strs = [str(lsa) for lsa in chain(self.routers.values(),
                                          self.networks.values(),
                                          self.ext_networks.values())]
        strs.insert(0, '* LSDB Content [%d]:' % len(strs))
        return '\n'.join(strs)

    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.itervalues(),
                         self.networks.itervalues(),
                         self.ext_networks.itervalues()):

            if is_expired_lsa(lsa):
                log.debug("LSA %s is too old (%d) ignoring it!",
                          lsa, lsa.age)
            else:
                lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for rlsa in self.routers.itervalues():
            rlsa.contract_graph(new_graph,
                                self.private_addresses
                                .addresses_of(rlsa.routerid))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask"""
                cid = (((int(addr) - int(self.BASE_NET.network_address)) >>
                        self.BASE_NET.max_prefixlen - controller_prefix) &
                       ((1 << controller_prefix) - 1))
                self.controllers[cid].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph

    def update_graph(self, new_graph):
        self.leader_watchdog.check_leader(self.get_leader())
        added_edges = new_graph.difference(self.graph)
        removed_edges = self.graph.difference(new_graph)
        node_prop_diff = {n: data
                          for n, data in new_graph.nodes_iter(data=True)
                          if n not in self.graph or
                          (data.viewitems() - self.graph.node[n].viewitems())}
        # Propagate differences
        if added_edges or removed_edges or node_prop_diff:
            log.debug('Pushing changes')
            for u, v in added_edges:
                self.for_all_listeners('add_edge', u, v,
                                       new_graph.export_edge_data(u, v))
            for u, v in removed_edges:
                self.for_all_listeners('remove_edge', u, v)
            if node_prop_diff:
                self.for_all_listeners('update_node_properties',
                                       **node_prop_diff)
            if CFG.getboolean(DEFAULTSECT, 'draw_graph'):
                new_graph.draw(CFG.get(DEFAULTSECT, 'graph_loc'))
            self.graph = new_graph
            log.info('LSA update yielded +%d -%d edges changes, '
                      '%d node property changes', len(added_edges),
                      len(removed_edges), len(node_prop_diff))
            self.for_all_listeners('commit')

    def for_all_listeners(self, funcname, *args, **kwargs):
        """Apply funcname to all listeners"""
        f = methodcaller(funcname, *args, **kwargs)
        map(f, self.listener.itervalues())

    def apply_secondary_addresses(self, graph):
        for src, dst in graph.router_links:
            try:
                graph[src][dst]['dst_address'] = self.private_addresses\
                                                .addresses_of(dst, src)
            except KeyError:
                log.debug('%(src)-%(dst)s does not yet exists on the graph'
                          ', ignoring private addresses.', locals())
                pass
예제 #3
0
class LSDB(object):
    def __init__(self):
        self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        self.private_addresses = PrivateAddressStore(
            CFG.get(DEFAULTSECT, 'private_ips'))
        self.last_line = ''
        self.leader_watchdog = None
        self.transaction = None
        self.graph = IGPGraph()
        self.routers = {}  # router-id : lsa
        self.networks = {}  # DR IP : lsa
        self.ext_networks = {}  # (router-id, dest) : lsa
        self.controllers = defaultdict(list)  # controller nr : ip_list
        self.listener = {}
        self.keep_running = True
        self.queue = Queue()
        self.processing_thread = Thread(target=self.process_lsa,
                                        name="lsa_processing_thread")
        self.processing_thread.setDaemon(True)
        self.processing_thread.start()

    def set_leader_watchdog(self, wd):
        self.leader_watchdog = wd

    def get_leader(self):
        return min(self.controllers.iterkeys()) if self.controllers else None

    def stop(self):
        for l in self.listener.values():
            l.session.stop()
        self.keep_running = False
        self.queue.put('')

    def lsdb(self, lsa):
        if lsa.TYPE == RouterLSA.TYPE:
            return self.routers
        elif lsa.TYPE == NetworkLSA.TYPE:
            return self.networks
        elif lsa.TYPE == ASExtLSA.TYPE:
            return self.ext_networks

    def register_change_listener(self, listener):
        try:
            del self.listener[listener]
            log.info('Shapeshifter disconnected.')
        except KeyError:
            log.info('Shapeshifter connected.')
            l = ProxyCloner(ShapeshifterProxy, listener)
            self.listener[listener] = l
            l.bootstrap_graph(
                graph=[(u, v, d) for u, v, d in self.graph.export_edges()],
                node_properties={
                    n: data
                    for n, data in self.graph.nodes_iter(data=True)
                })

    @staticmethod
    def extract_lsa_properties(lsa_part):
        d = {}
        for prop in lsa_part.split(SEP_INTER_FIELD):
            if not prop:
                continue
            key, val = prop.split(SEP_INTRA_FIELD)
            d[key] = val
        return d

    def commit_change(self, line):
        # Check that this is not a duplicate of a previous update ...
        if self.last_line == line:
            return
        self.queue.put(line)

    def forwarding_address_of(self, src, dst):
        """
        Return the forwarding address for a src, dst pair.
        If src is specified, return the private 'link-local' address of
        the src-dst link, otherwise return a 'public' IP belonging to dst
        :param src: the source node of the link towards the FA, possibly null
        :param dst: the node owning the forwarding address
        :return: forwarding address (str)
                or None if no compatible address was found
        """
        # If we have a src address, we want the set of private IPs
        # Otherwise we want any IP of dst
        u, v, key = ((src, dst, 'dst_address') if src else
                     (dst, self.graph.neighbors(dst)[0], 'src_address'))
        try:
            edge = self.graph[u][v]
        except KeyError:
            log.error(
                '%s-%s not found in graph when resolving '
                'forwarding address of (%s,%s)', u, v, src, dst)
            return None
        try:
            return edge[key]
        except KeyError:
            log.error(
                '%s not found in the properties of edge %s-%s '
                'when resolving forwarding address of (%s, %s)\n%s', key, u, v,
                src, dst, edge)
            return None

    def remove_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            del lsdb[lsa.key()]
        except KeyError:
            pass
        except TypeError:
            pass  # LSDB is None

    def add_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            lsdb[lsa.key()] = lsa
        except TypeError:
            pass  # LSDB is None

    def process_lsa(self):
        while self.keep_running:
            commit = False
            try:
                line = self.queue.get(timeout=5)
                if not line:
                    self.queue.task_done()
                    continue
                # Start parsing the LSA log
                action, lsa_info = line.split(SEP_ACTION)
                if action == BEGIN:
                    self.transaction = Transaction()
                elif action == COMMIT:
                    if self.transaction:
                        self.transaction.commit(self)
                        self.transaction = None
                        commit = True
                else:
                    lsa_parts = [
                        self.extract_lsa_properties(part)
                        for part in lsa_info.split(SEP_GROUP) if part
                    ]
                    lsa = LSA.parse(LSAHeader.parse(lsa_parts.pop(0)),
                                    lsa_parts)
                    log.debug('Parsed %s: %s', action, lsa)
                    provider = self.transaction if self.transaction else self
                    if action == REM:
                        provider.remove_lsa(lsa)
                    elif action == ADD:
                        provider.add_lsa(lsa)
                    if lsa.push_update_on_remove() or not action == REM:
                        commit = True
                self.queue.task_done()
            except Empty:
                if self.transaction:
                    log.debug('Splitting transaction due to timeout')
                    self.transaction.commit(self)
                    self.transaction = Transaction()
                    commit = True
            if commit:
                # Update graph accordingly
                new_graph = self.build_graph()
                # Compute graph difference and update it
                self.update_graph(new_graph)

    def __str__(self):
        strs = [
            str(lsa)
            for lsa in chain(self.routers.values(), self.networks.values(),
                             self.ext_networks.values())
        ]
        strs.insert(0, '* LSDB Content [%d]:' % len(strs))
        return '\n'.join(strs)

    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.itervalues(), self.networks.itervalues(),
                         self.ext_networks.itervalues()):
            lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for rlsa in self.routers.itervalues():
            rlsa.contract_graph(
                new_graph, self.private_addresses.addresses_of(rlsa.routerid))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask"""
                cid = (((int(addr) - int(self.BASE_NET.network_address)) >>
                        self.BASE_NET.max_prefixlen - controller_prefix) &
                       ((1 << controller_prefix) - 1))
                self.controllers[cid].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph

    def update_graph(self, new_graph):
        self.leader_watchdog.check_leader(self.get_leader())
        added_edges = new_graph.difference(self.graph)
        removed_edges = self.graph.difference(new_graph)
        node_prop_diff = {
            n: data
            for n, data in new_graph.nodes_iter(data=True)
            if n not in self.graph or (data.viewitems() -
                                       self.graph.node[n].viewitems())
        }
        # Propagate differences
        if added_edges or removed_edges or node_prop_diff:
            log.debug('Pushing changes')
            for u, v in added_edges:
                self.for_all_listeners('add_edge', u, v,
                                       new_graph.export_edge_data(u, v))
            for u, v in removed_edges:
                self.for_all_listeners('remove_edge', u, v)
            if node_prop_diff:
                self.for_all_listeners('update_node_properties',
                                       **node_prop_diff)
            if CFG.getboolean(DEFAULTSECT, 'draw_graph'):
                new_graph.draw(CFG.get(DEFAULTSECT, 'graph_loc'))
            self.graph = new_graph
            log.info(
                'LSA update yielded +%d -%d edges changes, '
                '%d node property changes', len(added_edges),
                len(removed_edges), len(node_prop_diff))
            self.for_all_listeners('commit')

    def for_all_listeners(self, funcname, *args, **kwargs):
        """Apply funcname to all listeners"""
        for i in self.listener.itervalues():
            getattr(i, funcname)(*args, **kwargs)

    def apply_secondary_addresses(self, graph):
        for src, dst in graph.router_links:
            try:
                graph[src][dst]['dst_address'] = self.private_addresses\
                                                .addresses_of(dst, src)
            except KeyError:
                log.debug(
                    '%(src)-%(dst)s does not yet exists on the graph'
                    ', ignoring private addresses.', locals())
                pass