Exemplo n.º 1
0
    def _create_and_mirred_to_ifb(self, dev_name):
        """
        Creates a IFB for the interface so that a Qdisc can be
        installed on it
        Mirrors packets to be sent out of the interface first to
        itself (IFB)
        Assumes the interface has already invoked _set_structure()

        Parameters
        ----------
        dev_name : string
            The interface to which the ifb was added
        """
        ifb_name = "ifb-" + dev_name
        if config.get_value("assign_random_names") is False:
            if len(ifb_name) > MAX_CUSTOM_NAME_LEN:
                raise ValueError(
                    f"Auto-generated IFB interface name {ifb_name} is too long. "
                    f"The length of name should not exceed 15 characters.")

        self.ifb = Interface(ifb_name)
        engine.create_ifb(self.ifb.id)

        # Add ifb to namespace
        self.node._add_interface(self.ifb)

        # Set interface up
        self.ifb.set_mode("UP")

        default_route = {"default": "1"}

        # TODO: find how to set a good bandwidth
        default_bandwidth = {"rate": config.get_value("default_bandwidth")}

        # TODO: Standardize this; seems like arbitrary handle values
        # were chosen.
        self.ifb.add_qdisc("htb", "root", "1:", **default_route)
        self.ifb.add_class("htb", "1:", "1:1", **default_bandwidth)
        self.ifb.add_qdisc("pfifo", "1:1", "11:")

        action_redirect = {
            "match": "u32 0 0",  # from man page examples
            "action": "mirred",
            "egress": "redirect",
            "dev": self.ifb.id,
        }

        # NOTE: Use Filter API
        engine.add_filter(self.node.id,
                          self.id,
                          "all",
                          "1",
                          "u32",
                          parent="1:",
                          **action_redirect)
Exemplo n.º 2
0
def setup_flow_workers(exp_runners, exp_stop_time):
    """
    Setup flow generation and stats collection processes(netperf, ss, tc, iperf3...).

    Also add a progress bar process for showing experiment progress.

    Parameters
    ----------
    exp_runners: collections.NamedTuple
        all(netperf, ping, ss, tc..) the runners
    exp_stop_time: int
        Time when experiment stops (in seconds)

    Returns
    -------
    List[multiprocessing.Process]
        flow generation and stats collection processes
        + progress bar process
    """
    workers = []

    for runners in exp_runners:
        workers.extend([Process(target=runner.run) for runner in runners])

    # Add progress bar process
    if config.get_value("show_progress_bar"):
        workers.extend([Process(target=progress_bar, args=(exp_stop_time,))])

    return workers
Exemplo n.º 3
0
    def __init__(self, name, node_id, veth_end_id):
        """
        Constructor for an IFB

        Created only if bandwidth is given or default bandwidth is set
        in case of adding qdisc and delay buy bandwidth not specified

        Parameters
        ----------
        name : str
            User given name for the interface
        node_id : str
            This is the id of the node that the device belongs to
        veth_end_id : str
            This is the id of the veth that the IFB is attached to
        """

        super().__init__(name, node_id)
        self.veth_end_id = veth_end_id

        # Create Ifb and add to namespace
        engine.create_ifb(self._id)
        engine.add_int_to_ns(node_id, self._id)

        self.set_mode("UP")

        self.current_bandwidth = config.get_value("default_bandwidth")
        self._add_default_qdisc_and_mirror_packets()
Exemplo n.º 4
0
 def create_config(self):
     """
     Creates config file on disk from `self.conf`
     """
     with open(self.conf_file, "w") as conf:
         shutil.chown(self.conf_file, user=config.get_value("routing_suite"))
         self.conf.seek(0)
         shutil.copyfileobj(self.conf, conf)
Exemplo n.º 5
0
    def _create_directory(self, dir_path):
        """
        Creates a quagga/frr owned directory at `dir_path`

        Parmeters
        ---------
        dir_path: path of the directory to be created

        """
        mkdir(dir_path)
        chown(dir_path, user=config.get_value("routing_suite"))
Exemplo n.º 6
0
def _autogenerate_interface_name(node1, node2, connections):
    """
    Auto-generate interface names based on respective node names
    and number of connections
    """
    interface_name = node1.name + "-" + node2.name + "-" + str(connections)

    if config.get_value("assign_random_names") is False:
        if len(interface_name) > MAX_CUSTOM_NAME_LEN:
            raise ValueError(
                f"Auto-generated device name {interface_name} is too long. "
                f"The length of name should not exceed 15 characters.")

    return interface_name
Exemplo n.º 7
0
    def _create_conf_directory(self):
        """
        Creates a directory for holding routing related config
        and pid files.
        Override this to create directory at a location other than /tmp

        Returns
        -------
        str:
            path of the created directory
        """
        salt = config.get_value("routing_suite") + str(time.clock_gettime(0))
        dir_path = f"/tmp/{salt}-configs_{IdGen.topology_id}"
        self._create_directory(dir_path)
        return dir_path
Exemplo n.º 8
0
    def _run_dyn_routing(self):
        """
        Run zebra and `self.protocol`
        """
        logger.info("Running zebra and %s on routers", self.protocol)
        self.conf_dir = self._create_conf_directory()
        if config.get_value("routing_logs"):
            self.log_dir = self._create_log_directory()

        for router in self.routers:
            self._run_zebra(router)
            self._run_routing_protocol(router)
            if router in self.ldp_routers:
                self._run_ldp(router)
        self._check_for_convergence()
Exemplo n.º 9
0
    def _set_structure(self):
        """
        Sets a proper structure to the interface by creating HTB class
        with default bandwidth and a netem qdisc as a child.
        (default bandwidth = 1024mbit)
        """
        self.set_structure = True

        default_route = {"default": "1"}

        self.add_qdisc("htb", "root", "1:", **default_route)

        # TODO: find how to set a good bandwidth
        default_bandwidth = {"rate": config.get_value("default_bandwidth")}

        self.add_class("htb", "1:", "1:1", **default_bandwidth)

        self.add_qdisc("netem", "1:1", "11:")
Exemplo n.º 10
0
    def __init__(self, interface_name):
        """
        Constructor of Interface.

        *Note*: Unlike Node object, the creation of Interface object
        does not actually create an interface in the backend. This has
        to be done seperately by invoking engine.
        [See `Veth` class for an example]

        Parameters
        ----------
        interface_name : str
            Name of the interface
        """

        if config.get_value("assign_random_names") is False:
            if len(interface_name) > MAX_CUSTOM_NAME_LEN:
                raise ValueError(
                    f"Interface name {interface_name} is too long. Interface names "
                    f"should not exceed 15 characters")

        # TODO: name and address should be the only public members
        self._name = interface_name
        self._id = IdGen.get_id(interface_name)

        self._address = None
        self._node = None
        self._pair = None
        # Normally this is the default mtu value.
        self._mtu = 1500

        self.set_structure = False
        self.ifb = None

        # TODO: These are not rightly updated
        # set_delay and set_bandwidth invoke the
        # engine function directly
        self.qdisc_list = []
        self.class_list = []
        self.filter_list = []

        # mpls input
        self._is_mpls_enabled = False
Exemplo n.º 11
0
    def __init__(self, router_ns_id, interfaces, daemon, conf_dir, **kwargs):
        """
        Parameters
        ----------
        conf_dir : str
            Directory to store config files
        **kwargs
            Key worded arguments for other daemon specific parameters
            ``log_dir``:
                Directory to store log files. (`str`)
        """
        self.logger = logging.getLogger(__name__)
        self.daemon = daemon
        if not any(
            isinstance(filter, DepedencyCheckFilter) for filter in self.logger.filters
        ):
            # Duplicate filter is added to avoid logging of same error
            # message incase any of the routing daemon is not installed
            self.logger.addFilter(DepedencyCheckFilter())

        if not any(
            isinstance(filter, DuplicateRoutingLogsFilter)
            for filter in self.logger.filters
        ):
            self.logger.addFilter(DuplicateRoutingLogsFilter())

        if not supports_dynamic_routing(daemon):
            self.handle_dependecy_error()

        self.conf = io.StringIO()
        self.router_ns_id = router_ns_id
        self.conf_file = f"{conf_dir}/{self.router_ns_id}_{daemon}.conf"
        self.pid_file = f"{conf_dir}/{self.router_ns_id}_{daemon}.pid"
        self.log_file = None
        if kwargs["log_dir"] is not None:
            self.logger.info(
                "%s logging enabled. Log files can found in %s directory",
                config.get_value("routing_suite"),
                kwargs["log_dir"],
            )
            self.log_file = f"{kwargs['log_dir']}/{self.router_ns_id}_{daemon}.log"
        self.interfaces = interfaces
        self.ipv6 = interfaces[0].address.is_ipv6()
Exemplo n.º 12
0
    def create_basic_config(self):
        """
        Creates a file with basic configuration for OSPF.
        Use base `add_to_config` directly for more complex configurations
        """
        if self.ipv6:
            for interface in self.interfaces:
                self.add_to_config(f"interface {interface.id}")
                # send hello packets every 1 second for faster convergence
                self.add_to_config("ipv6 ospf6 hello-interval 1")

            self.add_to_config("router ospf6")

            # Generates random router-id in A.B.C.D format
            router_id = ".".join(
                map(str, (random.randint(0, 255) for _ in range(0, 4))))
            # for quagga
            if config.get_value("routing_suite") == "quagga":
                self.add_to_config(f"router-id {router_id}")
            # for frr
            else:
                self.add_to_config(f"ospf6 router-id {router_id}")
            for interface in self.interfaces:
                self.add_to_config(f" interface {interface.id} area 0.0.0.0")
        else:
            for interface in self.interfaces:
                self.add_to_config(f"interface {interface.id}")
                # send hello packets every 1 second for faster convergence
                self.add_to_config("ip ospf hello-interval 1")

            self.add_to_config("router ospf")
            self.add_to_config(
                f"ospf router-id {self.interfaces[0].address.get_addr(with_subnet=False)}"
            )
            for interface in self.interfaces:
                self.add_to_config(
                    f" network {interface.address.get_subnet()} area 0.0.0.0")

        if self.log_file is not None:
            self.add_to_config(f"log file {self.log_file}")

        self.create_config()
Exemplo n.º 13
0
    def wrapper_dad_check(*args, **kwargs):
        """
        Wrapper function for DAD

        Parameters
        ----------
        *args : Non-Keyword Arguments
            passes variable number of arguments to a function
        **kwargs : Keyword Arguments
            passes keyworded, variable-length argument list to a function

        Returns
        -------
        func
            Function to be executed after wrapper is executed
        """

        if (g_var.IS_IPV6 is True
                and config.get_value("disable_dad") is not True
                and g_var.IS_DAD_CHECKED is not True):

            namespaces = TopologyMap.get_namespaces()

            # Verifies if IPv6 states are addressable or not
            while True:
                status = check_ipv6_states(namespaces)

                # IPv6 state will be both in tentative and dadfailed together
                if status["dadfailed"][0] is True:
                    raise Exception(
                        "Duplicate address found "
                        f"at interface of node {status['dadfailed'][1]}."
                        "\nExiting ....")

                # Wait if IPv6 interface is in tentative state
                if status["tentative"] is True:
                    sleep(1)
                else:
                    break
            g_var.IS_DAD_CHECKED = True
        return func(*args, **kwargs)
Exemplo n.º 14
0
    def __init__(self, name):
        """
        Create a node with given `name`.

        An unique `id` is assigned to this node which is used by
        `engine` module to create the network namespace.
        This ensures that there is no naming conflict between any two
        nodes.

        Parameters
        ----------
        name: str
            The name of the node to be created
        """
        if name == "":
            raise ValueError("Node name can't be an empty string")
        if config.get_value("assign_random_names") is False and len(name) > 3:
            # We chose 3 because: 'ifb-ns1-ns2-20' is a potential IFB interface name
            # and it's already 14 character long. Note that here node names
            # are 'ns1' and 'ns2'. The `ip` utility won't accept interface names
            # longer than 15 characters
            logger.warning(
                "%s is longer than 3 characters. It's safer to use "
                "node names with atmost 3 characters with the current config.",
                name,
            )

        self._name = name
        self._id = IdGen.get_id(name)
        self._interfaces = []
        # mpls max platform label kernel parameter
        self._mpls_max_label = 0
        # Global variable disables when any new node is created
        # to ensure DAD check (if applicable)
        g_var.IS_DAD_CHECKED = False

        engine.create_ns(self.id)
        engine.set_interface_mode(self.id, "lo", "up")
        TopologyMap.add_namespace(self.id, self.name)
        TopologyMap.add_host(self)
Exemplo n.º 15
0
    def _add_default_qdisc_and_mirror_packets(self):
        """
        Sets default bandwidth to the IFB
        """

        default_route = {"default": "1"}

        # TODO: find how to set a good bandwidth
        default_bandwidth = {"rate": config.get_value("default_bandwidth")}

        # TODO: Standardize this; seems like arbitrary handle values
        # were chosen.
        # HTB class is added since, to use a filter and redirect traffic,
        # a classid is needed and htb gives it that, since it's a class
        self.add_qdisc("htb", "root", "1:", **default_route)
        self.add_class("htb", "1:", "1:1", **default_bandwidth)
        self.add_qdisc("pfifo", "1:1", "11:")

        action_redirect = {
            "match": "u32 0 0",  # from man page examples
            "action": "mirred",
            "egress": "redirect",
            "dev": self.id,
        }

        # NOTE: Use Filter API
        # Action mirred, redicting traffic, etc is needed since netem and
        # the user giver qdisc are both classless and cannot be added to
        # the same device
        engine.add_filter(
            self.node_id,
            self.veth_end_id,
            "all",
            "1",
            "u32",
            parent="1:",
            **action_redirect,
        )
Exemplo n.º 16
0
    def __init__(self, name, node_id):
        """
        Constructore for a device

        Parameters
        ----------
        name : str
            User given name for the device
        node_id : str
            This is the id of the node that the device belongs to
        """

        if config.get_value("assign_random_names") is False:
            if len(name) > MAX_CUSTOM_NAME_LEN:
                raise ValueError(
                    f"Device name {name} is too long. Device names "
                    f"should not exceed 15 characters")

        self._name = name
        self._id = IdGen.get_id(name)
        self._traffic_control_handler = TrafficControlHandler(
            node_id, self._id)
        if node_id is not None:
            TopologyMap.add_interface(self.node_id, self._id, self._name)
Exemplo n.º 17
0
def run_experiment(exp):
    """
    Run experiment

    Parameters
    -----------
    exp : Experiment
        The experiment attributes
    """

    tools = ["netperf", "ss", "tc", "iperf3", "ping"]
    Runners = namedtuple("runners", tools)
    exp_runners = Runners(netperf=[], ss=[], tc=[], iperf3=[],
                          ping=[])  # Runner objects

    # Keep track of all destination nodes [to ensure netperf and iperf3
    # server is run at most once]
    destination_nodes = {"netperf": set(), "iperf3": set()}

    # Contains start time and end time to run respective command
    # from a source netns to destination address
    ss_schedules = defaultdict(lambda: (float("inf"), float("-inf")))
    ping_schedules = defaultdict(lambda: (float("inf"), float("-inf")))

    exp_end_t = float("-inf")

    dependencies = get_dependency_status(tools)

    ss_required = False
    ss_filters = set()

    # Traffic generation
    for flow in exp.flows:
        # Get flow attributes
        [
            src_ns,
            dst_ns,
            dst_addr,
            start_t,
            stop_t,
            _,
            options,
        ] = flow._get_props()  # pylint: disable=protected-access

        exp_end_t = max(exp_end_t, stop_t)

        (min_start, max_stop) = ping_schedules[(src_ns, dst_addr)]
        ping_schedules[(src_ns, dst_addr)] = (
            min(min_start, start_t),
            max(max_stop, stop_t),
        )

        # Setup TCP/UDP flows
        if options["protocol"] == "TCP":
            # * Ignore netperf tcp control connections
            # * Destination port of netperf control connection is 12865
            # * We also have "sport" (source port) in the below condition since
            #   there can be another flow in the reverse direction whose control
            #   connection also we must ignore.
            ss_filters.add("sport != 12865 and dport != 12865")
            ss_required = True
            (
                tcp_runners,
                ss_schedules,
            ) = setup_tcp_flows(
                dependencies["netperf"],
                flow,
                ss_schedules,
                destination_nodes["netperf"],
            )

            exp_runners.netperf.extend(tcp_runners)

            # Update destination nodes
            destination_nodes["netperf"].add(dst_ns)

        elif options["protocol"] == "UDP":
            # * Ignore iperf3 tcp control connections
            # * Destination port of iperf3  control connection is 5201
            # * We also have "sport" (source port) in the below condition since
            #   there can be another flow in the reverse direction whose control
            #   connection also we must ignore.
            ss_filters.add("sport != 5201 and dport != 5201")
            udp_runners = setup_udp_flows(dependencies["iperf3"], flow,
                                          destination_nodes["iperf3"])

            exp_runners.iperf3.extend(udp_runners)

            # Update destination nodes
            destination_nodes["iperf3"].add(dst_ns)

    if ss_required:
        ss_filter = " and ".join(ss_filters)
        ss_runners = setup_ss_runners(dependencies["ss"], ss_schedules,
                                      ss_filter)
        exp_runners.ss.extend(ss_runners)

    tc_runners = setup_tc_runners(dependencies["tc"], exp.qdisc_stats,
                                  exp_end_t)
    exp_runners.tc.extend(tc_runners)

    ping_runners = setup_ping_runners(dependencies["ping"], ping_schedules)
    exp_runners.ping.extend(ping_runners)

    # Start traffic generation
    run_workers(setup_flow_workers(exp_runners))

    logger.info("Experiment complete!")
    logger.info("Parsing statistics...")

    # Parse the stored statistics
    run_workers(setup_parser_workers(exp_runners))

    logger.info("Output results as JSON dump")

    # Output results as JSON dumps
    dump_json_ouputs()

    if config.get_value("plot_results"):
        logger.info("Plotting results...")

        # Plot results and dump them as images
        run_workers(setup_plotter_workers())

        logger.info("Plotting complete!")

    if config.get_value("readme_in_stats_folder"):
        # Copying README.txt to stats folder
        relative_path = os.path.join("info", "README.txt")
        readme_path = os.path.join(os.path.dirname(__file__), relative_path)
        Pack.copy_files(readme_path)

    cleanup()
Exemplo n.º 18
0
def connect(
    node1: topology.Node,
    node2: topology.Node,
    interface1_name: str = "",
    interface2_name: str = "",
    network: Network = None,
):
    """
    Connects two nodes `node1` and `node2`.
    Creates two paired Virtual Ethernet interfaces (veth) and returns
    them as a 2-element tuple.
    The first interface belongs to `node1`, and the second interface
    belongs to `node2`.

    Parameters
    ----------
    node1 : Node
        First veth interface added in this node
    node2 : Node
        Second veth interface added in this node
    interface1_name : str
        Name of first veth interface
    interface2_name : str
        Name of second veth interface
    network : Network
        Object of the Network to add interfaces

    Returns
    -------
    (interface1, interface2)
        2 tuple of created (paired) veth interfaces. `interface1` is in
        `n1` and `interface2` is in `n2`.
    """
    # Number of connections between `node1` and `node2`, set to `None`
    # initially since it hasn't been computed yet
    connections = None

    # Check interface names
    if interface1_name == "":
        connections = _number_of_connections(node1, node2)
        interface1_name = _autogenerate_interface_name(node1, node2,
                                                       connections)

    if interface2_name == "":
        if connections is None:
            connections = _number_of_connections(node1, node2)
        # pylint: disable=arguments-out-of-order
        interface2_name = _autogenerate_interface_name(node2, node1,
                                                       connections)

    # Create 2 interfaces
    (interface1, interface2) = create_veth_pair(interface1_name,
                                                interface2_name)

    node1._add_interface(interface1)
    node2._add_interface(interface2)

    interface1.set_mode("UP")
    interface2.set_mode("UP")

    # Disabling Duplicate Address Detection(DAD) at the interfaces
    if config.get_value("disable_dad") is True:
        interface1.disable_ip_dad()
        interface2.disable_ip_dad()

    # The network parameter takes precedence over "global" network level
    if network is None:
        network = Network.current_network
    # Add the interfaces to the network if mentioned
    if network is not None:
        network.add_interface(interface1)
        network.add_interface(interface2)

    return (interface1, interface2)