def initialize_ecmp_deps(self): """Initialize ECMP dependencies""" for n, node in map( lambda x: (x[0], self.node(x[0])), filter(lambda x: x[1] > 1, self.dag.out_degree_iter())): if node.has_any_fake_node(): log.debug('%s does ECMP and has a fake node', n) self.ecmp[n].add(n) else: f = [] paths = self._p.default_path(n, self.dest) for p in paths: # Try to find the first fake node for each path for h in p[:-1]: if self.node(h).has_any_fake_node(): f.add(h) break if len(f) > 0 and len(f) < len(paths): log.warning( '%s does ECMP and has less downstream fake ' 'nodes than paths (%s < %s), forcing it to ' 'have a fake node.', n, len(f), len(paths)) node.fake_type = Node.GLOBAL elif f: log.debug('Registering ECMP depencies on %s: %s', n, f) for fake in f: self.ecmp[fake].add(f)
def __read_private_ips(self, filename): router_private_address = defaultdict(dict) ip_to_bd = defaultdict(list) try: with open(filename, 'r') as f: private_address_binding = json.load(f) for subnets in private_address_binding.itervalues(): # Log router id in broadcast domain sub = subnets.keys() for rid, ip in subnets.iteritems(): # Enable single private address as string if not is_container(ip): ip = [ip] # Log private addresses adjacencies other = sub[:] other.remove(rid) for s in other: router_private_address[rid][s] = ip for i in ip: # Register the broadcast domain for each ip ip_to_bd[i] = other except ValueError as e: log.error('Incorrect private IP addresses binding file') log.error(str(e)) ip_to_bd.clear() router_private_address.clear() except IOError as e: log.warning('Cannot read private address file') ip_to_bd.clear() router_private_address.clear() return router_private_address, ip_to_bd
def initialize_ecmp_deps(self): """Initialize ECMP dependencies""" for n, node in map(lambda x: (x[0], self.node(x[0])), filter(lambda x: x[1] > 1, self.dag.out_degree_iter())): if node.has_any_fake_node(): log.debug('%s does ECMP and has a fake node', n) self.ecmp[n].add(n) else: f = [] paths = self._p.default_path(n, self.dest) for p in paths: # Try to find the first fake node for each path for h in p[:-1]: if self.node(h).has_any_fake_node(): f.add(h) break if len(f) > 0 and len(f) < len(paths): log.warning('%s does ECMP and has less downstream fake ' 'nodes than paths (%s < %s), forcing it to ' 'have a fake node.', n, len(f), len(paths)) node.fake_type = Node.GLOBAL elif f: log.debug('Registering ECMP depencies on %s: %s', n, f) for fake in f: self.ecmp[fake].add(f)
def _get_proxy_routes(self, points): for prefix, parts in groupby(sorted(points, key=itemgetter(3)), key=itemgetter(3)): route = [] for p in parts: src, dst, cost = p[0], p[1], p[2] if cost >= 0: src = None fwd_addr = self.root.get_fwd_address(src, dst) # Can have multiple private addresses per interface, handle # here the selection ... if isinstance(fwd_addr, list): try: fwd_addr = fwd_addr[cost] except IndexError: log.warning('Required private forwarding address index' ' is out of bounds. Wanted index %s ' '- Have %s elements.', abs(cost), len(fwd_addr)) fwd_addr = fwd_addr[0] cost = 1 try: fwd_addr = str(ip_interface(fwd_addr).ip) except ValueError: log.debug('Forwarding address for %s-%s has no netmask: %s', src, dst, fwd_addr) route.append((fwd_addr, str(cost))) yield prefix, route
def testWeird(self): log.warning('Testing Weird') self._test(self.gadgets.weird, {'3_8': nx.DiGraph([('D', 'C'), ('C', 'B'), ('B', 'A')])}, 2)
def testTrapezoid(self): log.warning('Testing Trapezoid') self._test(self.gadgets.trap, {'1_8': nx.DiGraph([('R1', 'R2'), ('R2', 'E2'), ('E2', 'D')])}, 1)
def testSquareWithThreeConsecutiveChanges(self): log.warning('Testing SquareWithThreeConsecutiveChanges') self._test( self.gadgets.square, { '3_8': IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'), ('B2', 'D1')]) }, 3)
def testDiamond(self): log.warning('Testing Diamond') self._test( self.gadgets.diamond, { '3_8': IGPGraph([('A', 'Y1'), ('A', 'Y2'), ('Y2', 'X'), ('Y1', 'X'), ('X', 'D'), ('O', 'D')]) }, 2)
def testPaperGadget(self): log.warning('Testing PaperGadget') self._test( self.gadgets.paper_gadget, { '3_8': IGPGraph([('H1', 'X'), ('H2', 'X'), ('H3', 'X'), ('X', 'Y'), ('A1', 'Y'), ('A2', 'Y')]) }, 1)
def remove_lsa(self, *lsas): """Instructs the southbound controller to remove LSAs""" lsas = list(lsas) if lsas: self.quagga_manager.remove(lsas) self.advertized_lsa.difference_update(lsas) else: log.warning('Tried to remove an empty list of LSA')
def advertize_lsa(self, *lsas): """Instructs the southbound controller to announce LSAs""" lsas = list(lsas) if lsas: self.quagga_manager.add(lsas) self.advertized_lsa.update(lsas) else: log.warning('Tried to advertize an empty list of LSA')
def testDoubleDiamond(self): log.warning('Testing DoubleDiamond') self._test( self.gadgets.ddiamond, { '1_8': IGPGraph([('H1', 'Y1'), ('H1', 'Y2'), ('Y1', 'X'), ('Y2', 'X'), ('H2', 'X'), ('X', 'D')]) }, 3)
def testParallel(self): log.warning('Testing Parallel') self._test( self.gadgets.parallel, { '3_8': IGPGraph([('A2', 'B2'), ('B2', 'C2'), ('C2', 'D2'), ('D2', 'D1'), ('D1', 'C1'), ('C1', 'B1'), ('B1', 'A1'), ('A1', 'D')]) }, 4)
def testSquareWithThreeConsecutiveChanges(self): log.warning('Testing SquareWithThreeConsecutiveChanges') self._test(self.gadgets.square, {'3_8': nx.DiGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'), ('B2', 'D1')])}, 3)
def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(self): log.warning('Testing SquareWithThreeConsecutiveChanges' 'AndMultipleRequirements') dag = IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'), ('B2', 'D1')]) self._test(self.gadgets.square, { '3_8': dag, '8_3': dag.reverse(copy=True) }, 5)
def testDiamond(self): log.warning('Testing Diamond') self._test(self.gadgets.diamond, {'3_8': nx.DiGraph([('A', 'Y1'), ('A', 'Y2'), ('Y2', 'X'), ('Y1', 'X'), ('X', 'D'), ('O', 'D')])}, 2)
def testPaperGadget(self): log.warning('Testing PaperGadget') self._test(self.gadgets.paper_gadget, {'3_8': nx.DiGraph([('H1', 'X'), ('H2', 'X'), ('H3', 'X'), ('X', 'Y'), ('A1', 'Y'), ('A2', 'Y')])}, 1)
def testDoubleDiamond(self): log.warning('Testing DoubleDiamond') self._test(self.gadgets.ddiamond, {'1_8': nx.DiGraph([('H1', 'Y1'), ('H1', 'Y2'), ('Y1', 'X'), ('Y2', 'X'), ('H2', 'X'), ('X', 'D')])}, 3)
def testTrapezoidWithEcmp(self): log.warning('Testing TrapezoidWithEcmp') self._test(self.gadgets.trap, {'2_8': nx.DiGraph([('R1', 'R2'), ('R2', 'E2'), ('E2', 'D'), # ECMP on E1 ('E1', 'D'), ('E1', 'R1')])}, 3)
def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(self): log.warning('Testing SquareWithThreeConsecutiveChanges' 'AndMultipleRequirements') dag = nx.DiGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'), ('B2', 'D1')]) self._test(self.gadgets.square, {'3_8': dag, '8_3': dag.reverse(copy=True)}, 5)
def testParallel(self): log.warning('Testing Parallel') self._test(self.gadgets.parallel, {'3_8': nx.DiGraph([('A2', 'B2'), ('B2', 'C2'), ('C2', 'D2'), ('D2', 'D1'), ('D1', 'C1'), ('C1', 'B1'), ('B1', 'A1'), ('A1', 'D')])}, 4)
def solve(self, topo, requirement_dags): # a list of tuples with info on the node to be attracted, # the forwarding address, the cost to be set in the fake LSA, # and the respective destinations self.fake_ospf_lsas = [] self.reqs = requirement_dags self.igp_graph = topo self.igp_paths = ShortestPath(self.igp_graph) # process input forwarding DAGs, one at the time for dest, dag in requirement_dags.iteritems(): log.info('Solving DAG for dest %s', dest) self.dest, self.dag = dest, dag log.debug('Checking dest in dag') ssu.add_dest_to_graph(dest, dag) log.debug('Checking dest in igp graph') ssu.add_dest_to_graph(dest, topo, edges_src=dag.predecessors, spt=self.igp_paths, metric=self.new_edge_metric) ssu.complete_dag(dag, topo, dest, self.igp_paths, skip=self.reqs.keys()) # Add temporarily the destination to the igp graph and/or req dags if not ssu.solvable(dag, topo): log.warning('Skipping requirement for dest: %s', dest) continue for node in dag: nhs = self.nhs_for(node, dest, dag) if not nhs: continue for req_nh in nhs: log.debug('Placing a fake node for %s->%s', node, req_nh) for i in xrange(get_edge_multiplicity(dag, node, req_nh)): self.fake_ospf_lsas.append( ssu.LSA(node=node, nh=req_nh, cost=(-1 - i), dest=dest)) # Check whether we need to include one more fake node to handle # the case where we create a new route from scratch. for p in dag.predecessors_iter(dest): if not is_fake(topo, p, dest): continue log.debug( '%s is a terminal node towards %s but had no prior ' 'route to it! Adding a synthetic route', p, dest) self.fake_ospf_lsas.append( ssu.GlobalLie(dest, self.new_edge_metric, p)) return self.fake_ospf_lsas
def solve(self, graph, requirements): """Compute the augmented topology for a given graph and a set of requirements. :type graph: IGPGraph :type requirements: { dest: IGPGraph } :param requirements: the set of requirement DAG on a per dest. basis :return: list of fake LSAs""" self.reqs = requirements log.info('Preparing IGP graph') self.g = prepare_graph(graph, requirements) log.info('Computing SPT') self._p = ShortestPath(graph) lsa = [] for dest, dag in requirements.iteritems(): self.dest, self.dag = dest, dag self.ecmp.clear() log.info('Evaluating requirement %s', dest) log.info('Ensuring the consistency of the DAG') self.check_dest() ssu.complete_dag(self.dag, self.g, self.dest, self._p, skip=self.reqs.keys()) log.info('Computing original and required next-hop sets') for n, node in self.nodes(): node.forced_nhs = set(self.dag.successors(n)) node.original_nhs = set( [p[1] for p in self._p.default_path(n, self.dest)]) if not ssu.solvable(self.dag, self.g): log.warning('Consistency check failed, skipping %s', dest) continue log.info('Placing initial fake nodes') self.place_fake_nodes() log.info('Initializing fake nodes') self.initialize_fake_nodes() log.info('Propagating initial lower bounds') self.propagate_lb() log.debug('Fake node bounds: %s', [n for _, n in self.nodes() if n.has_any_fake_node()]) log.info('Reducing the augmented topology') self.merge_fake_nodes() self.remove_redundant_fake_nodes() log.info('Generating LSAs') lsas = self.create_fake_lsa() log.info('Solved the DAG for destination %s with LSA set: %s', self.dest, lsas) lsa.extend(lsas) return lsa
def testTrapezoidWithEcmp(self): log.warning('Testing TrapezoidWithEcmp') self._test( self.gadgets.trap, { '2_8': IGPGraph([ ('R1', 'R2'), ('R2', 'E2'), ('E2', 'D'), # ECMP on E1 ('E1', 'D'), ('E1', 'R1') ]) }, 3)
def solve(self, graph, requirements): """Compute the augmented topology for a given graph and a set of requirements. :type graph: IGPGraph :type requirements: { dest: IGPGraph } :param requirements: the set of requirement DAG on a per dest. basis :return: list of fake LSAs""" self.reqs = requirements log.info('Preparing IGP graph') self.g = prepare_graph(graph, requirements) log.info('Computing SPT') self._p = ShortestPath(graph) lsa = [] for dest, dag in requirements.iteritems(): self.dest, self.dag = dest, dag self.ecmp.clear() log.info('Evaluating requirement %s', dest) log.info('Ensuring the consistency of the DAG') self.check_dest() ssu.complete_dag(self.dag, self.g, self.dest, self._p, skip=self.reqs.keys()) log.info('Computing original and required next-hop sets') for n, node in self.nodes(): node.forced_nhs = set(self.dag.successors(n)) node.original_nhs = set([p[1] for p in self._p.default_path(n, self.dest)]) if not ssu.solvable(self.dag, self.g): log.warning('Consistency check failed, skipping %s', dest) continue log.info('Placing initial fake nodes') self.place_fake_nodes() log.info('Initializing fake nodes') self.initialize_fake_nodes() log.info('Propagating initial lower bounds') self.propagate_lb() log.debug('Fake node bounds: %s', [n for _, n in self.nodes() if n.has_any_fake_node()]) log.info('Reducing the augmented topology') self.merge_fake_nodes() self.remove_redundant_fake_nodes() log.info('Generating LSAs') lsas = self.create_fake_lsa() log.info('Solved the DAG for destination %s with LSA set: %s', self.dest, lsas) lsa.extend(lsas) return lsa
def create_fake_lsa(self): lsa = [] for n in self.dag: if n == self.dest: continue node = self.node(n) for nh in node.forced_nhs: if nh == self.dest: log.warning('Ignoring LSA towards nh == dest ?!?') continue log.debug('Creating LSA for %s -> %s', n, nh) lsa.append(ssu.LSA(node=n, nh=nh, cost=node.lb + 1 if node.fake == Node.GLOBAL else -1, dest=self.dest)) return lsa
def solve(self, topo, requirement_dags): # a list of tuples with info on the node to be attracted, # the forwarding address, the cost to be set in the fake LSA, # and the respective destinations self.fake_ospf_lsas = [] self.reqs = requirement_dags self.igp_graph = topo self.igp_paths = ShortestPath(self.igp_graph) # process input forwarding DAGs, one at the time for dest, dag in requirement_dags.iteritems(): log.info('Solving DAG for dest %s', dest) self.dest, self.dag = dest, dag log.debug('Checking dest in dag') ssu.add_dest_to_graph(dest, dag) log.debug('Checking dest in igp graph') ssu.add_dest_to_graph(dest, topo, edges_src=dag.predecessors, spt=self.igp_paths, metric=self.new_edge_metric) ssu.complete_dag(dag, topo, dest, self.igp_paths, skip=self.reqs.keys()) # Add temporarily the destination to the igp graph and/or req dags if not ssu.solvable(dag, topo): log.warning('Skipping requirement for dest: %s', dest) continue for node in dag: nhs = self.nhs_for(node, dest, dag) if not nhs: continue for req_nh in nhs: log.debug('Placing a fake node for %s->%s', node, req_nh) for i in xrange(get_edge_multiplicity(dag, node, req_nh)): self.fake_ospf_lsas.append(ssu.LSA(node=node, nh=req_nh, cost=(-1 - i), dest=dest)) # Check whether we need to include one more fake node to handle # the case where we create a new route from scratch. for p in dag.predecessors_iter(dest): if not is_fake(topo, p, dest): continue log.debug('%s is a terminal node towards %s but had no prior ' 'route to it! Adding a synthetic route', p, dest) self.fake_ospf_lsas.append( ssu.GlobalLie(dest, self.new_edge_metric, p)) return self.fake_ospf_lsas
def create_fake_lsa(self): lsa = [] for n in self.dag: if n == self.dest: continue node = self.node(n) for nh in node.forced_nhs: if nh == self.dest: log.warning('Ignoring LSA towards nh == dest ?!?') continue log.debug('Creating LSA for %s -> %s', n, nh) lsa.append( ssu.LSA(node=n, nh=nh, cost=node.lb + 1 if node.fake == Node.GLOBAL else -1, dest=self.dest)) return lsa
def solve(self, topo, requirement_dags): # a list of tuples with info on the node to be attracted, # the forwarding address, the cost to be set in the fake LSA, # and the respective destinations self.fake_ospf_lsas = [] self.reqs = requirement_dags self.igp_graph = topo self.igp_paths = ShortestPath(self.igp_graph) log.debug('Original SPT: %s', self.igp_paths) # process input forwarding DAGs, one at the time for dest, dag in requirement_dags.iteritems(): log.debug('Solving DAG for dest %s', dest) self.dest, self.dag = dest, dag log.debug('Checking dest in dag') ssu.add_dest_to_graph(dest, dag) log.debug('Checking dest in igp graph') ssu.add_dest_to_graph(dest, topo, edges_src=dag.predecessors, spt=self.igp_paths, metric=self.new_edge_metric) ssu.complete_dag(dag, topo, dest, self.igp_paths, skip=self.reqs.keys()) # Add temporarily the destination to the igp graph and/or req dags if not ssu.solvable(dag, topo): log.warning('Skipping requirement for dest: %s', dest) continue for node in nx.topological_sort(dag, reverse=True)[1:]: nhs, original_nhs = self.nhs_for(node, dag, dest) if not self.require_fake_node(nhs, original_nhs): log.debug('%s does not require a fake node (%s - %s)', node, nhs, original_nhs) continue for req_nh in nhs: log.debug('Placing a fake node for nh %s', req_nh) self.fake_ospf_lsas.append( ssu.LSA(node=node, nh=req_nh, cost=-1, dest=dest)) return self.fake_ospf_lsas
def solve(self, topo, requirement_dags): # a list of tuples with info on the node to be attracted, # the forwarding address, the cost to be set in the fake LSA, # and the respective destinations self.fake_ospf_lsas = [] self.reqs = requirement_dags self.igp_graph = topo self.igp_paths = ShortestPath(self.igp_graph) log.debug('Original SPT: %s', self.igp_paths) # process input forwarding DAGs, one at the time for dest, dag in requirement_dags.iteritems(): log.debug('Solving DAG for dest %s', dest) self.dest, self.dag = dest, dag log.debug('Checking dest in dag') ssu.add_dest_to_graph(dest, dag) log.debug('Checking dest in igp graph') ssu.add_dest_to_graph(dest, topo, edges_src=dag.predecessors, spt=self.igp_paths, metric=self.new_edge_metric) ssu.complete_dag(dag, topo, dest, self.igp_paths, skip=self.reqs.keys()) # Add temporarily the destination to the igp graph and/or req dags if not ssu.solvable(dag, topo): log.warning('Skipping requirement for dest: %s', dest) continue for node in nx.topological_sort(dag, reverse=True)[1:]: nhs, original_nhs = self.nhs_for(node, dag, dest) if not self.require_fake_node(nhs, original_nhs): log.debug('%s does not require a fake node (%s - %s)', node, nhs, original_nhs) continue for req_nh in nhs: log.debug('Placing a fake node for nh %s', req_nh) self.fake_ospf_lsas.append(ssu.LSA(node=node, nh=req_nh, cost=-1, dest=dest)) return self.fake_ospf_lsas
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()
"""This module provides a structure to represent an IGP topology""" import os import networkx as nx from fibbingnode import log # The draw_graph call will be remapped to 'nothing' if matplotlib (aka extra # packages) is not available try: import matplotlib.pyplot as plt except ImportError: log.warning('Missing packages to draw the network, disabling the fonction') def draw_graph(*_): pass else: def draw_graph(graph, output): """If matplotlib is available, draw the given graph to output file""" try: layout = spring_layout(graph) metrics = { (src, dst): data['metric'] for src, dst, data in graph.edges_iter(data=True) } nx.draw_networkx_edge_labels(graph, layout, edge_labels=metrics) nx.draw(graph, layout, node_size=20) nx.draw_networkx_labels(graph, layout, labels={n: n for n in graph}) if os.path.exists(output): os.unlink(output) plt.savefig(output) plt.close() log.debug('Graph of %d nodes saved in %s', len(graph), output) except:
import heapq import networkx as nx from itertools import count from fibbingnode import log import fibbingnode.algorithms.utils as ssu from fibbingnode.misc.utils import extend_paths_list, is_container # The draw_graph call will be remapped to 'nothing' if matplotlib (aka extra # packages) is not available try: import matplotlib matplotlib.use('PDF') import matplotlib.pyplot as plt except ImportError: log.warning('Missing packages to draw the network, disabling the fonction') def draw_graph(*_): """Can't draw without matplotlib""" pass else: def draw_graph(graph, output): """If matplotlib is available, draw the given graph to output file""" try: layout = nx.spring_layout(graph) metrics = { (src, dst): data['metric'] for src, dst, data in graph.edges_iter(data=True) } nx.draw_networkx_edge_labels(graph, layout, edge_labels=metrics) nx.draw(graph, layout, node_size=20) nx.draw_networkx_labels(graph, layout,
def log_test_name(self): # Get previous stack frame, 3rd part is func name test = inspect.stack()[1][3] log.warning('[%s] Testing %s with %s', self.__class__.__name__, test, self.solver_provider.__name__)
def testTrapezoid(self): log.warning('Testing Trapezoid') self._test( self.gadgets.trap, {'1_8': IGPGraph([('R1', 'R2'), ('R2', 'E2'), ('E2', 'D')])}, 1)
def testWeird(self): log.warning('Testing Weird') self._test(self.gadgets.weird, {'3_8': IGPGraph([('D', 'C'), ('C', 'B'), ('B', 'A')])}, 2)