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 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 add_edge(self, source, destination, metric): # metric is added twice to support backward-compat. self.igp_graph.add_edge(source, destination, weight=int(metric), metric=int(metric)) log.debug('Added edge: %s-%s@%s', source, destination, metric) # Only trigger an update if the link is bidirectional self.dirty = self.igp_graph.has_edge(destination, source)
def add_edge(self, source, destination, properties={'metric': 1}): properties = sanitize_edge_data(properties) # metric is added twice to support backward-compat. self.igp_graph.add_edge(source, destination, properties) log.debug('Added edge: %s-%s@%s', source, destination, properties) # Only trigger an update if the link is bidirectional self.dirty = self.igp_graph.has_edge(destination, source)
def remove_redundant_fake_nodes(self): """Remove fake nodes that are useless (typically a path of redundant fake nodes that eventually got merged up to the penultimates nodes in the DAG).""" visited = set() # Start from destination and go back up the leaves to_visit = set(self.dag.predecessors_iter(self.dest)) while to_visit: n = to_visit.pop() if n in visited: continue visited.add(n) node = self.node(n) # If we have a fake node if node.has_fake_node(subtype=Node.GLOBAL): # Is the LB redundant with the original SP ? succ = self.dag.successors(n) succ_dest_cost = self._p.default_cost(succ[0], self.dest) n_succ_cost = self._p.default_cost(n, succ[0]) if node.lb + 1 == succ_dest_cost + n_succ_cost and\ node.original_nhs == set(succ): log.debug('Removing %s as it is redundant with the ' 'original path [lb: %s, succ cost: %s, ' 'n-succ cost: %s, succ: %s, orig succ: %s]', n, node.lb, succ_dest_cost, n_succ_cost, node.original_nhs, succ) node.remove_fake_node() else: log.debug('Keeping %s [lb: %s, succ cost: %s, ' 'n-succ cost: %s, succ: %s, orig succ: %s]', n, node.lb, succ_dest_cost, n_succ_cost, node.original_nhs, succ) else: to_visit |= set(self.dag.predecessors_iter(n))
def del_ip(self, ip): """ Remove an IP address from this port :param ip: an IPV4Address """ log.debug('Removing %s from %s ip''s', ip, self.id) self.node.call('ip', 'addr', 'delete', ip.with_prefixlen)
def combine_ranges(self, n, s): """Attempt to combine the lb,ub interval between the two nodes""" node, succ = self.node(n), self.node(s) cost = self._p.default_cost(n, s) new_ub = min(node.ub - cost, succ.ub) new_lb = max(node.lb - cost, succ.lb) # Log these errors which should never happen # as propagation should prevent this if new_lb > succ.lb: log.error( 'Merging %s into %s resulted in a LB increase from ' '%s to %s (%s' 's LB: %s, spt cost: %s)', n, s, succ.lb, new_lb, n, node.lb, cost) elif new_lb < succ.lb: log.error( 'Merging %s into %s resulted in a LB decrease from ' '%s to %s (%s' 's LB: %s, spt cost: %s)', n, s, succ.lb, new_lb, n, node.lb, cost) # Report unfeasible merge if not self.valid_range(s, new_lb, new_ub): log.debug( 'Merging %s into %s would lead to bounds of ' ']%s, %s[, aborting', n, s, new_lb, new_ub) return None return new_lb, new_ub
def _test(self, igp_topo, fwd_dags, expected_lsa_count): solver = self.solver_provider() lsas = solver.solve(igp_topo, fwd_dags) log.debug('solved reqs with LSAs: %s', lsas) self.assertTrue(check_fwd_dags(fwd_dags, igp_topo, lsas, solver)) log.debug('lsa count: %s, expected: %s', len(lsas), expected_lsa_count) self.assertTrue(len(lsas) == expected_lsa_count)
def compute_initial_ub(self): for n, node in self.nodes(Node.GLOBAL): if node.ub != DEFAULT_UB: log.debug('%s already has its UB set to %s', n, node.ub) continue node.ub = self._p.default_cost(n, self.dest) log.debug('Initial ub of %s set to %s', n, node.ub)
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 gen_physical_ports(port_list): """ Find all enabled physical interfaces of this :param port_list: The list of all physical ports that should be analyzed :return: A list of Tuple (interface name, ip address) for each active physical interface """ ports = [] for port_name in port_list: try: ip = ip_interface(CFG.get(port_name, 'ip')) ports.append((port_name, ip)) except ConfigError: try: out = subprocess.check_output(['ip', 'a', 'show', port_name]) for line in out.splitlines(): if 'inet ' in line: line = line.strip(' \t\n') port_addr = ip_interface(line.split(' ')[1]) log.debug('Added physical port %s@%s', port_name, port_addr) ports.append((port_name, port_addr)) break # TODO support multiple IP/interface? except subprocess.CalledProcessError as e: log.exception(e) return ports
def advertize(self, prefix): """ Advertize this fibbing point """ log.debug('%s advertizes %s via %s', self.node.id, prefix, self.address) self.node.advertize(prefix.with_prefixlen, via=self.address, metric=self.metric) self.advertized = True
def gen_physical_ports(port_list): """ Find all enabled physical interfaces of this :param port_list: The list of all physical ports that should be analyzed :return: A list of Tuple (interface name, ip address) for each active physical interface """ ports = [] for port_name in port_list: try: out = subprocess.check_output(['ip', 'a', 'show', port_name]) for line in out.splitlines(): if 'inet ' in line: line = line.strip(' \t\n') # inet 130.104.228.87/25 brd 130.104.228.127 \ # scope global dynamic eno1 port_addr = ip_interface(line.split(' ')[1]) log.debug('Added physical port %s@%s', port_name, port_addr) ports.append((port_name, port_addr)) break # TODO support multiple IP/interface? except subprocess.CalledProcessError as e: log.exception(e) return ports
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_diff_lsas(self): new_lsas = self.refresh_augmented_topo() log.debug('New LSA set: %s', new_lsas) to_add = new_lsas.difference(self.advertized_lsa) to_rem = self.advertized_lsa.difference(new_lsas) log.debug('Removing LSA set: %s', to_rem) self.advertized_lsa = new_lsas return to_add, to_rem
def add_separate_destination_to_sinks(destination, input_topo, dag, cost=1): if destination in input_topo: destination = "Dest_" + destination for node in find_sink(dag): log.debug("Connecting %s to %s with cost %d", destination, node, cost) input_topo.add_edge(node, destination, weight=cost) dag.add_edge(node, destination) return destination
def create_ns(self): if os.path.exists(NSDIR) and ' %s ' % self.name in os.listdir(NSDIR): self.delete() err = _netns('add', self.name) if err != 0: log.error('Failed to create namespace %s', self.name) else: log.debug('Created namespace %s', self.name)
def advertize(self, prefix): """ Advertize this fibbing point """ log.debug('%s advertizes %s via %s', self.node.id, prefix, self.address) self.node.advertize(prefix.with_prefixlen, via=self.address, metric=self.metric, ttl=self.ttl) self.advertized = True
def boostrap_graph(self, graph): self.igp_graph.clear() for u, v, metric in graph: self.igp_graph.add_edge(u, v, weight=int(metric)) log.debug('Bootstrapped graph with edges: %s', self.igp_graph.edges()) log.debug('Sending initial lsa''s') if self.additional_routes: self.quagga_manager.add_static(self.additional_routes) self._refresh_lsas()
def add_separate_destination_to_sinks(destination, input_topo, dag, cost=1): if destination in input_topo: destination = "Dest_" + destination for node in find_sink(dag): log.debug("Connecting %s to %s with cost %d", destination, node, cost) input_topo.add_edge(node, destination, metric=cost) dag.add_edge(node, destination) return destination
def default_cost(self, u, v=None): """Return the cost of the pure IGP shortest path if Fibbing was not in use on the current network, between u and v or a dict of cost if v is None""" try: return self._get(self._default_dist, u, v) except KeyError as e: log.debug('%s had no path to %s (lookup key: %s)', u, v, e) return sys.maxint
def retract(self, address, advertize): try: point = self.attraction_points.pop(address) if advertize: point.retract(self.prefix) return point.node except KeyError: log.debug('Unkown attraction point %s for prefix %s', address, self.prefix) return None
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
def endpoints(self, lsdb): other_routers = [] netdb = lsdb.lsdb(NetworkLSA) try: netlsa = netdb[self.dr_ip] except KeyError: log.debug('Cannot resolve network lsa for %s yet', self.dr_ip) else: other_routers.extend(netlsa.attached_routers) return other_routers
def create_link(self): """ Create a veth link between the source and the destination ports """ cmd = ['ip', 'link', 'add', self.src.id, 'type', 'veth', 'peer', 'name', self.dst.id] log.debug('Creating link: %s', cmd) err = subprocess.call(cmd) if err != 0: log.error('Failed to create veth link: %s', cmd) sys.exit(1)
def add_edge(self, source, destination, metric): self.igp_graph.add_edge(source, destination, weight=int(metric)) log.debug('Added edge: %s-%s@%s', source, destination, metric) try: self.igp_graph[destination][source] except KeyError: # Only trigger an update if the link is bidirectional pass else: self.dirty = True
def bootstrap_graph(self, graph, node_properties): self.igp_graph.clear() self.igp_graph.add_edges_from(graph) for _, _, d in self.igp_graph.edges_iter(data=True): sanitize_edge_data(d) self.update_node_properties(**node_properties) log.debug('Bootstrapped graph with edges: %s and properties: %s', self.igp_graph.edges(data=True), node_properties) self.received_initial_graph() self.graph_changed()
def check_dest(self): """Check that the destination is present in the DAG and the graph""" log.debug('Checking dest in dag') ssu.add_dest_to_graph(self.dest, self.dag) log.debug('Checking dest in graph') ssu.add_dest_to_graph(self.dest, self.g, edges_src=self.dag.predecessors, spt=self._p, metric=self.new_edge_metric, node_data_gen=self.__new_dest)
def apply(self, graph, lsdb): if ip_address(self.routerid) in lsdb.exclude_net and \ CFG.getboolean(DEFAULTSECT, 'exclude_fake_lsa'): log.debug('Skipping AS-external Fake LSA %s via %s', self.address, [self.resolve_fwd_addr(r.fwd_addr) for r in self.routes]) return for route in self.routes: graph.add_edge(self.resolve_fwd_addr(route.fwd_addr), self.prefix, metric=route.metric)
def addresses_of(self, rid, f=None): """Return the list of private ip addresses for router id if f is None, else the list of forwarding addresses from f to rid""" try: return ([i for l in self._address_bindings[rid].itervalues() for i in l] if not f else self._address_bindings[rid][f]) except KeyError: log.debug('No private address for %s from %s', rid, f)
def bootstrap_graph(self, graph, node_properties): self.igp_graph.clear() for u, v, metric in graph: self.igp_graph.add_edge(u, v, weight=int(metric)) for n, data in node_properties.iteritems(): self.igp_graph.node[n] = data log.debug('Bootstrapped graph with edges: %s and properties: %s', self.igp_graph.edges(data=True), node_properties) self.received_initial_graph() self.graph_changed()
def remove_route(self, network): """ Remove a route :param network: The prefix to remove """ net = ip_network(network) try: route = self.routes[net] self.remove_route_part(net, *[p for p in route]) except KeyError: log.debug('No route for network %s', net)
def link(self, src, dst): """ Create a veth Link between two nodes :param src: The source node of the link :param dst: The destination node of the link """ log.debug('Linking %s to %s', src.id, dst.id) l = Link(src, dst) # Register the new link self.links.append(l) return l
def place_fake_nodes(self): for n in self.dag.nodes_iter(): if self.g.out_degree(n) > 1: node = self.node(n) if self.needs_fake_node(node.original_nhs, node.forced_nhs): node.add_fake_node() log.debug('Adding a fake node on %s', n) else: node.forced_nhs.clear() log.debug('Skipping %s has it keeps the same successors', n)
def place_fake_nodes(self): penultimate_nodes = self.dag.predecessors(self.dest) for n in self.dag.nodes_iter(): if self.g.out_degree(n) > 1: # Skip sinks node = self.node(n) node.add_fake_node() log.debug('Adding a fake node on %s', n) if n in penultimate_nodes: node.lb = self._p.default_cost(n, self.dest) - 1 node.ub = node.lb + 2 log.debug('%s is a penultimate node, LB = cost to dest', n)
def prepare_graph(g, req): """Copy the given graph and preset nodes attribute :type g: DiGraph :return: DiGraph :type req: {dest: fwd_req} :param req: The requirements for that graph""" log.debug('Copying graph') graph = g.copy() for n in graph.nodes(): graph.node[n]['data'] = {key: Node(name=n) for key in req} return graph
def set_ip(self, ip): """ Set this port's IP address :param ip: an IPV4Address """ if self.ip_interface: # Remove the previous address if any self.del_ip(self.ip_interface) self.ip_interface = ip log.debug('Assigning %s to %s', ip, self.id) self.node.call('ip', 'addr', 'add', ip.with_prefixlen, 'dev', self.id)
def refresh_lsas(self): """Refresh the set of LSAs that needs to be sent in the IGP, and instructs the southbound controller to update it if changed""" (to_add, to_rem) = self._get_diff_lsas() if not to_add and not to_rem: log.debug('Nothing to do for the current topology') return if to_rem: self.remove_lsa(*to_rem) if to_add: self.advertize_lsa(*to_add)
def pipe(self, *args, **kwargs): cmd = ['ip', 'netns', 'exec', self.name] cmd.extend(args) log.debug(str(cmd)) if 'stdin' not in kwargs: kwargs['stdin'] = subprocess.PIPE if 'stdout' not in kwargs: kwargs['stdout'] = subprocess.PIPE if 'stderr' not in kwargs: kwargs['stderr'] = subprocess.STDOUT return subprocess.Popen(cmd, **kwargs)
def refresh_augmented_topo(self): log.info('Solving topologies') if not self.json_proxy.alive() or not self.has_initial_topo: log.debug('Skipping as we do not yet have a topology') return self.advertized_lsa try: self.optimizer.solve(self.igp_graph, self.fwd_dags) except Exception as e: log.exception(e) return self.advertized_lsa else: return set(self.optimizer.get_fake_lsas())
def merge(self, n, succ, nh): """Try to merge n into its successor fake node, along the given path""" log.debug('Trying to merge %s into %s', n, succ) if not self.dag_include_spt(n, succ): return # at least one IGP SP is not included in the DAG try: new_lb, new_ub = self.combine_ranges(n, succ) log.debug( 'Merging %s into %s would result in bounds in %s set to ' ']%s, %s[', n, succ, succ, new_lb, new_ub) self.apply_merge(n, succ, new_lb, new_ub, nh) except TypeError: # Couldn't find a valid range, skip return