class RouteTable: """ The route table, also known as routing information base (RIB). """ def __init__(self, address_family, fib, log, log_id): assert fib.address_family == address_family self.address_family = address_family self.destinations = PyTricia() self.fib = fib self._log = log self._log_id = log_id def debug(self, msg, *args): if self._log: self._log.debug("[%s] %s" % (self._log_id, msg), *args) def get_route(self, prefix, owner): assert_prefix_address_family(prefix, self.address_family) prefix = ip_prefix_str(prefix) if self.destinations.has_key(prefix): return self.destinations.get(prefix).get_route(owner) return None def put_route(self, rib_route): assert_prefix_address_family(rib_route.prefix, self.address_family) rib_route.stale = False self.debug("Put %s", rib_route) prefix = rib_route.prefix prefix_str = ip_prefix_str(prefix) if not self.destinations.has_key(prefix_str): destination = Destination(self, prefix) self.destinations.insert(prefix_str, destination) else: destination = self.destinations.get(prefix_str) best_changed = destination.put_route(rib_route) if best_changed: child_prefix_strs = self.destinations.children(prefix_str) self.update_fib(prefix, child_prefix_strs) def del_route(self, prefix, owner): assert_prefix_address_family(prefix, self.address_family) prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations.get(prefix_str) child_prefix_strs = self.destinations.children(prefix_str) deleted, best_changed = destination.del_route(owner) # If that was the last route for the destination, delete the destination itself if not destination.rib_routes: del self.destinations[prefix_str] else: deleted = False best_changed = False if deleted: self.debug("Delete %s", prefix) else: self.debug("Attempted delete %s (not present)", prefix) if best_changed: self.update_fib(prefix, child_prefix_strs) return deleted def update_fib(self, prefix, child_prefix_strs): # The child_prefix_strs have to be passed as a parameter, because they need to be collected # before the parent is potentially deleted by the calling function. # Locate the best RIB route for the prefix (None if there is no RIB route for the prefix). prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations.get(prefix_str) rib_route = destination.best_route() else: rib_route = None # Determine the next-hops for the corresponding FIB route. If the next-hops is an empty # list [] it means it is a discard route. If the next-hops is None it means there should not # be a FIB route. if rib_route: fib_next_hops = rib_route.compute_fib_next_hops() else: fib_next_hops = None # Update the FIB route accordingly. if fib_next_hops is not None: fib_route = FibRoute(prefix, fib_next_hops) self.fib.put_route(fib_route) else: self.fib.del_route(prefix) # Recursively update the FIB for all child routes: their negative next-hops, if any, may # have to be recomputed if a parent route changed. for child_prefix_str in child_prefix_strs: child_destination = self.destinations[child_prefix_str] child_rib_route = child_destination.best_route() child_prefix = child_rib_route.prefix grand_child_prefix_strs = self.destinations.children( child_prefix_str) self.update_fib(child_prefix, grand_child_prefix_strs) def all_routes(self): for prefix in self.destinations: destination = self.destinations.get(prefix) for rib_route in destination.rib_routes: yield rib_route def all_prefix_routes(self, prefix): assert_prefix_address_family(prefix, self.address_family) prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations[prefix_str] for rib_route in destination.rib_routes: yield rib_route def cli_table(self): table = Table() table.add_row(RibRoute.cli_summary_headers()) for rib_route in self.all_routes(): table.add_row(rib_route.cli_summary_attributes()) return table def mark_owner_routes_stale(self, owner): # Mark all routes of a given owner as stale. Returns number of routes marked. count = 0 for rib_route in self.all_routes(): if rib_route.owner == owner: rib_route.stale = True count += 1 return count def del_stale_routes(self): # Delete all routes still marked as stale. Returns number of deleted routes. # Cannot delete routes while iterating over routes, so prepare a delete list routes_to_delete = [] for rib_route in self.all_routes(): if rib_route.stale: routes_to_delete.append((rib_route.prefix, rib_route.owner)) # Now delete the routes in the prepared list count = len(routes_to_delete) if count > 0: self.debug("Delete %d remaining stale routes", count) for (prefix, owner) in routes_to_delete: self.del_route(prefix, owner) return count def nr_destinations(self): return len(self.destinations) def nr_routes(self): count = 0 for prefix in self.destinations: destination = self.destinations.get(prefix) count += len(destination.rib_routes) return count
class Rib: def __init__(self, fib): self._fib = fib # 1. The real production code for the RIB (but not the FIB) needs Destination objects and # Route objects to support multiple routes to the same destination (prefix): one per owner # (e.g. south-SPF and north-SPF). This prototype only supports a single route per # destination (prefix) to keep things simple and avoid distracting attention from the # negative disaggregation algorithm. # 2. The PyTricia code takes prefixes as strings. Not sure if this is the best choice # for production code. I suspect it is better to accept prefixes as Prefix objects that # have an internal binary representation. self._routes = PyTricia() def put_route(self, prefix, positive_nexthops, negative_nexthops=None): # Put the route in the RIB. if negative_nexthops is None: negative_nexthops = [] route = RibRoute(prefix, positive_nexthops, negative_nexthops) self._routes[prefix] = route # Gather the entire subtree of prefixes (children, grandchildren, ...) below the given # prefix (including the prefix itself). We rely on the fact that PyTricia.children always # returns parent aggregate routes before child more specific routes (although the PyTricia # documentation doesn't guarantee this). subtree_prefixes = [prefix] + self._routes.children(prefix) # Recompute the computed nexthops of child routes in the subtree and update the FIB # accordingly. self._recompute_subtree_nexthops(subtree_prefixes) def del_route(self, prefix): # If the route is not present in the RIB (this is legal), we have nothing to do. if not self._routes.has_key(prefix): return # Gather the entire subtree of prefixes (children, grandchildren, ...) below the given # prefix (excluding the prefix itself). We rely on the fact that PyTricia.children always # returns parent aggregate routes before child more specific routes (although the PyTricia # documentation doesn't guarantee this). subtree_prefixes = self._routes.children(prefix) # Delete the route from the RIB. del self._routes[prefix] # Delete corresponding route from the FIB. self._fib.del_route(prefix) # Recompute the computed nexthops of child routes in the subtree and update the FIB # accordingly. self._recompute_subtree_nexthops(subtree_prefixes) def get_route(self, prefix): if self._routes.has_key(prefix): return self._routes[prefix] return None def __str__(self): rep_str = "" for prefix in self._routes: rep_str += f"{self._routes[prefix]}\n" return rep_str def _recompute_subtree_nexthops(self, subtree_prefixes): for prefix in subtree_prefixes: route = self._routes[prefix] self._recompute_route_nexthops(route) self._fib.put_route(prefix, route.computed_nexthops) def _recompute_route_nexthops(self, route): # If the route does not have any negative nexthops, there is no disaggregation to be done. if not route.negative_nexthops: route.set_computed_nexthops(route.positive_nexthops) return # If the route does not have a parent route, there is no disaggregation to be done. parent_prefix = self._routes.parent(route.prefix) if parent_prefix is None: route.set_computed_nexthops(route.positive_nexthops) return # Compute the complementary nexthops of the negative nexthops. parent_route = self._routes[parent_prefix] complementary_nexthops = parent_route.computed_nexthops.difference( route.negative_nexthops) # Combine the complementary nexthops with the positive nexthops. computed_nexthops = route.positive_nexthops.union( complementary_nexthops) # Store the computed nexthops in the route route.set_computed_nexthops(computed_nexthops)