Source code for caida.parse_plugin

import os
import gzip
import contextlib
from itertools import product

import netaddr
from caida.parse import ASAnnotation
from base_objects import Switch
from generic_vm_objects import GenericRouter

from firewheel.control.experiment_graph import Edge, Vertex, AbstractPlugin

current_module_path = os.path.abspath(os.path.dirname(__file__))
aslinks_default = os.path.join(current_module_path, "cycle-aslinks.txt.gz")
bgp_table_default = os.path.join(current_module_path, "routeviews.gz")


[docs] class ParseCAIDA(AbstractPlugin): """ This imports a CAIDA AS trace into the graph. This will walk through the CAIDA trace files and create a BGP router for each AS number. Any links in the traces will be placed into the graph appropriately. The attributes that it sets in the graph vertices are as follows: Router: - **type** - Always set to ``"router"`` - **as** - The router's AS number - **interfaces** - The active interfaces for the router Format: ``{ <NUM>: { 'netmask', 'name', 'address' } }`` - **bgp** - The BGP neighbor information for this router Format: ``{ <NUM>: { 'as', 'address' } }`` - **bgp_networks** - the networks to be explicitly advertised by BGP Format: ``{ <NUM>: { 'netmask', 'address' } }`` - **new** - Indicates that this is a new device and needs to be processed by the other plugins. (It is set to :py:data:`True`) Link: - **new** - Indicates that this is a new device and needs to be processed by the other plugins. (It is set to :py:data:`True`) """
[docs] def run(self, aslinks=aslinks_default, bgp_table=bgp_table_default): """ Parse the given CAIDA information into a BGP network used for a FIREWHEEL experiment. The method generates AS links, assigns BGP networks, and removes OSPF information. Args: aslinks (str): The AS links file to parse and import. This should be a gzipped file in the format provided by CAIDA. An example filename would be ``cycle-aslinks.l7.t3.c006830.20180719.txt.gz``. bgp_table (str): The file mapping AS numbers to IP networks. This should be a gzipped file in the format provided by CAIDA. An example filename would be ``routeviews-rv2-20180731-1200.pfx2as.gz``. """ self.vertices = {} self.link_attrs = {} # We need to be able to build a subnet tree to keep track of the # AS' networks self.tree = Vertex(self.g) self.tree.decorate(ASAnnotation, init_args=["ANNOTATION_subnet_tree"]) # First the AS map with links will be built, and then the # appropriate BGP networks will be assigned self.log.debug("Generate AS links") self.generate_as_links(aslinks) # Then, assign the appropriate BGP network to every needed BGP # router self.log.debug("Assign BGP networks") self.assign_bgp_networks(bgp_table) # Remove all OSPF information self.log.debug("Strip OSPF info") self.remove_ospf_info()
[docs] def assign_bgp_networks(self, bgp_table): """ Assign the given BGP networks to each AS. Args: bgp_table (str): The file mapping AS numbers to IP networks. """ if bgp_table.endswith(".gz"): with gzip.open(bgp_table, "rt") as tablestream: data = tablestream.read() else: with open(bgp_table, "r", encoding="utf-8") as tablestream: data = tablestream.read() for line in data.splitlines(): self.process_bgp_table_line(line) tablestream.close()
[docs] def process_bgp_table_line(self, line): """ Process the given line in the BGP table by adding the BGP networks to the appropriate ASes in the graph. This line takes the form:: network cidr AS Note: Currently, multi-origin AS (MOAS) isn't supported, so if this is the case then the first entry is chosen. Args: line (str): The line from the BGP table representing a network, CIDR, and AS. Raises: ValueError: If the line in the BGP table is malformatted. """ try: (net, cidr, as_str) = line.split() network = netaddr.IPNetwork("%s/%s" % (net, cidr)) ases = self._get_AS_list(as_str) # At the moment, multi-origin AS (MOAS) isn't supported, so return the first AS ases = [ases[0]] except Exception as exc: raise ValueError("Poorly formatted BGP table line: %s" % exc) from exc # Check if this network has already been processed if self.tree.is_network_in_tree(network): return for autosys in ases: # Now, add this network to the AS as_name = self._get_AS_name(autosys) try: vertex = self.vertices[as_name] except KeyError: # self.log.warning('%s has no links', as_name) continue # Add in this new network vertex.add_bgp_network(network) # Now, build the switch for the new BGP network. # Do this now so we can add a complete record to the subnet tree # annotation. bgp_net_hosts = network.iter_hosts() bgp_net_switch = self._get_bgp_net_switch(network) if bgp_net_switch not in self.vertices: self.vertices[bgp_net_switch] = Vertex(self.g) self.vertices[bgp_net_switch].decorate( Switch, init_args=[bgp_net_switch] ) switch = self.vertices[bgp_net_switch] switch.network = network switch.skip_create_network = True # Form the edge between the switch and the router. try: vertex.connect(switch, next(bgp_net_hosts), network.netmask) except StopIteration: # Add the AS to the subnet tree either way. self.log.warning("Unable to fit router on %s, skipping", network) # Finally, add this AS and network to the subnet tree self.tree.add_subnet(network, as_name, switch)
[docs] def _get_AS_list(self, as_str): # noqa: N802 """ Return a list of the ASes referenced, including multi-origin AS (MOAS) and sets. The CAIDA syntax for MOAS is ``X_Y_Z``, and the syntax for sets is that they are comma separated. This method separates each AS number as if they were independent (effectively ignoring MOAS). Args: as_str (str): The string representing AS numbers. Returns: list: A list of AS numbers. """ ases = [] for asblock in as_str.split("_"): for autosys in asblock.split(","): ases.append(autosys) # noqa: PERF402 return ases
[docs] def _get_AS_name(self, as_number): # noqa: N802 """ Return the AS name in the graph for the given AS number. Args: as_number (str): The AS number. Returns: str: The AS name in the graph. """ return "router.AS%s.as.net" % as_number
[docs] def _get_switch_name(self, from_as, to_as): """ Return the switch name in the graph for the given link. Args: from_as (str): The AS number of the source. to_as (str): The AS number of the destination. Returns: str: The switch name in the graph. """ return "SWITCH_%s_%s" % (from_as, to_as)
[docs] def _get_bgp_net_switch(self, net): """ Return the canonical switch name for the given BGP network. Args: net (IPNetwork): The BGP network. Returns: str: The canonical switch name for the BGP network. """ return "BGP-" + str(net.cidr).replace(".", "-").replace("/", "-")
[docs] def remove_ospf_info(self): """ Set all OSPF parameters to None. """ for vertex in self.vertices: self.vertices[vertex].bgp_over_ospf_redistribution = None self.vertices[vertex].ospf_over_bgp_redistribution = None self.vertices[vertex].ospf = None