def change_qdisc(self, handle, qdisc="", **kwargs): """ Change a qdisc that is already present in the device Parameters ---------- handle : string Hande of the qdisc to be changed qdisc : string The new qdisc to be added to the device """ for qdisc_member in self.qdisc_list: if qdisc_member.handle == handle: # In case you want to just change kwargs if qdisc == "": qdisc = qdisc_member.qdisc else: TopologyMap.change_qdisc(self.node_id, self.dev_id, qdisc, handle) qdisc_member.qdisc = qdisc engine.change_qdisc( self.node_id, self.dev_id, qdisc, qdisc_member.parent, handle, **kwargs, )
def test_add_and_get_interface(self): self.assertEqual( TopologyMap.get_interface(self.ns_id1, self.int_id1)["name"], self.int_name1) self.assertEqual( TopologyMap.get_interface(self.ns_id1, self.int_id2)["name"], self.int_name2)
def add_qdisc(self, qdisc, parent="root", handle="", **kwargs): """ Add a qdisc (Queueing Discipline) to this interface Parameters ---------- qdisc : string The qdisc which needs to be added to the interface dev : Interface class The interface to which the qdisc is to be added parent : string id of the parent class in major:minor form(optional) (Default value = 'root') handle : string id of the filter (Default value = '') """ self.qdisc_list.append( traffic_control.Qdisc(self.node.id, self.id, qdisc, parent, handle, **kwargs)) # Add qdisc to TopologyMap TopologyMap.add_qdisc(self.node.id, self.id, qdisc, handle, parent=parent) return self.qdisc_list[-1]
def add_interface(self, _interface=None): """ Adding interface to the network. Parameters ---------- _interface : interface The interface which needs to be added to the Network. """ self.interface.append(_interface) TopologyMap.decrement_orphan_interfaces()
def node_id(self, node_id): """ Setter for the `Node` associated with the device Parameters ---------- node : str The id of the node where the device is to be installed """ self._traffic_control_handler.node_id = node_id if node_id is not None: TopologyMap.add_interface(self.node_id, self._id, self._name)
def _add_interface(self, interface): """ Add `interface` to `Node` Parameters ---------- interface: Interface `Interface` to be added to `Node` """ self._interfaces.append(interface) interface.node = self engine.add_int_to_ns(self.id, interface.id) TopologyMap.add_interface(self.id, interface.id, interface.name)
def enable_ip_forwarding(self, ipv4=True, ipv6=True): """ Enable IP forwarding in `Node`. After this method runs, the `Node` can be used as a router. """ if not ipv4 and not ipv6: raise Exception( "IP Forwarding cannot be false for both IPv4 and IPv6 addresses" ) engine.en_ip_forwarding(self.id, ipv4, ipv6) TopologyMap.add_router(self)
def __init__(self, network_address): """ Constructor of Network. Parameters ---------- network_address : str IP address of the network """ self.net_address = network_address self.interface = [] # Adding each network's object reference to the static list of networks in topology_map. TopologyMap.add_network(self)
def setup_udp_flows(dependency, flow, destination_nodes): """ Setup iperf3 to run udp flows Parameters ---------- dependency: int whether iperf3 is installed flow: Flow Flow parameters destination_nodes: Destination nodes so far already running iperf3 server Returns ------- dependency: int updated dependency in case iproute2 is not installed iperf3_runners: List[NetperfRunner] all the iperf3 udp flows generated workers: List[multiprocessing.Process] Processes to run iperf3 udp flows """ iperf3_runners = [] if not dependency: logger.warning("Iperf3 not found. Udp flows cannot be generated") else: # Get flow attributes [ src_ns, dst_ns, dst_addr, start_t, stop_t, n_flows, options, ] = flow._get_props() # pylint: disable=protected-access # Run iperf3 server if not already run before on given dst_node if dst_ns not in destination_nodes: Iperf3Runner.run_server(dst_ns) src_name = TopologyMap.get_namespace(src_ns)["name"] f_flow = "flow" if n_flows == 1 else "flows" logger.info( "Running %s udp %s from %s to %s...", n_flows, f_flow, src_name, dst_addr ) runner_obj = Iperf3Runner( src_ns, dst_addr, options["target_bw"], n_flows, start_t, stop_t - start_t, dst_ns, ) iperf3_runners.append(runner_obj) return iperf3_runners
def print_error(self, error_string_prefix): """ Method to print error from `self.err` """ self.err.seek(0) # rewind to start of file error = self.err.read().decode() ns_name = TopologyMap.get_namespace(self.ns_id)["name"] self.logger.error("%s at %s. %s", error_string_prefix, ns_name, error)
def delete_qdisc(self, handle): """ Delete qdisc (Queueing Discipline) from this interface Parameters ---------- handle : string Handle of the qdisc to be deleted """ # TODO: Handle this better by using the destructor in traffic-control counter = 0 for qdisc in self.qdisc_list: if qdisc.handle == handle: engine.delete_qdisc(qdisc.namespace_id, qdisc.dev_id, qdisc.parent, qdisc.handle) TopologyMap.delete_qdisc(self.node.id, self.id, handle) self.qdisc_list.pop(counter) break counter += 1
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)
def __init__(self, protocol="static", hosts=None, routers=None, ldp_routers=None): """ Constructor for RoutingHelper Parameters ---------- protocol: str routing protocol to be run. One of [ospf, rip, isis] hosts: List[Node] List of hosts in the network. If `None`, considers the entire topology. Use this if your topology has disjoint networks routers: List[Node] List of routers in the network. If `None`, considers the entire topology. Use this if your topology has disjoint networks ldp_routers: List[Node] List of Routers which are to be used with mpls. Only enables ldp discovery on interfaces with mpls enabled """ if protocol == "static": raise NotImplementedError( "Static routing is yet to be implemented. Use rip or ospf") self.protocol = protocol self.routers = TopologyMap.get_routers( ) if routers is None else routers self.hosts = TopologyMap.get_hosts() if hosts is None else hosts self.ldp_routers = ldp_routers if ldp_routers is not None else [] self.conf_dir = None self.log_dir = None module_str, class_str = RoutingHelper._module_map[self.protocol] module = importlib.import_module(module_str) self.protocol_class = getattr(module, class_str) self.zebra_list = [] self.protocol_list = [] self.ldp_list = [] atexit.register(self._clean_up)
def run_server(ns_id): """ Run iperf server in `ns_id` Parameters ---------- ns_id : str namespace to run netserver on """ return_code = run_iperf_server(ns_id) if return_code != 0: ns_name = TopologyMap.get_namespace(ns_id)["name"] logger.error("Error running iperf3 server at %s.", ns_name)
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)
def get_meta_item(self): """ Return the meta item for the given flow. This "meta" information is required by plotter. """ meta_item = { "meta": True, "start_time": str(self.start_time), "stop_time": str(self.start_time + self.run_time), } if self.dst_ns is not None: meta_item["destination_node"] = TopologyMap.get_namespace( self.dst_ns)["name"] return meta_item
def setUp(self): # Define namespace id and names self.ns_id1 = "ns_id1" self.ns_id2 = "ns_id2" self.ns_name1 = "ns_name1" self.ns_name2 = "ns_name2" # Add namespaces TopologyMap.add_namespace(self.ns_id1, self.ns_name1) TopologyMap.add_namespace(self.ns_id2, self.ns_name2) # Define interface id and names self.int_id1 = "int_id1" self.int_id2 = "int_id2" self.int_name1 = "int_name1" self.int_name2 = "int_name2" # Add interfaces TopologyMap.add_interface(self.ns_id1, self.int_id1, self.int_name1) TopologyMap.add_interface(self.ns_id1, self.int_id2, self.int_name2)
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)
def test_add_same_entity_again(self): with self.assertRaises(ValueError): TopologyMap.add_namespace(self.ns_id1, self.ns_name1) with self.assertRaises(ValueError): TopologyMap.add_interface(self.ns_id1, self.int_id1, self.int_name1)
def __init__( self, protocol: str, hosts: List[Node] = None, routers: List[Node] = None, ldp_routers: List[Node] = None, ): """ Constructor for RoutingHelper. The dynamic routing daemons will be run only on nodes with more than one interface. Specify `hosts` & `routers` parameters to override this. Parameters ---------- protocol: str routing protocol to be run. One of [ospf, rip, isis] hosts: List[Node] List of hosts in the network. If `None`, considers the entire topology. Use this if your topology has disjoint networks routers: List[Node] List of routers in the network. If `None`, considers the entire topology. Use this if your topology has disjoint networks ldp_routers: List[Node] List of Routers which are to be used with mpls. Only enables ldp discovery on interfaces with mpls enabled """ if protocol == "static": raise NotImplementedError( "Static routing is yet to be implemented. Use rip, ospf or isis" ) if protocol not in ["rip", "ospf", "isis"]: raise ValueError( f"Supported routing protocols are rip, ospf and isis, " f"but got protocol {protocol}") self.protocol = protocol # Validate hosts, routers and ldp_routers self._is_node_list("hosts", hosts) self._is_node_list("routers", routers) self._is_node_list("ldp_routers", ldp_routers) self.hosts = [] self.routers = [] if routers is None and hosts is None: all_nodes = TopologyMap.get_hosts() + TopologyMap.get_routers() for node in all_nodes: num_interfaces = len(node.interfaces) if num_interfaces == 1: self.hosts.append(node) elif num_interfaces > 1: self.routers.append(node) else: self.hosts = hosts self.routers = routers self.ldp_routers = ldp_routers if ldp_routers is not None else [] self.conf_dir = None self.log_dir = None module_str, class_str = RoutingHelper._module_map[self.protocol] module = importlib.import_module(module_str) self.protocol_class = getattr(module, class_str) self.zebra_list = [] self.protocol_list = [] self.ldp_list = [] atexit.register(self._clean_up)
def tearDown(self): TopologyMap.delete_all_mapping()
def tearDown(self): delete_namespaces() TopologyMap.delete_all_mapping()
def setup_tcp_flows(dependency, flow, ss_schedules, destination_nodes): """ Setup netperf to run tcp flows Parameters ---------- dependency: int whether netperf is installed flow: Flow Flow parameters ss_schedules: ss_schedules so far destination_nodes: Destination nodes so far already running netperf server Returns ------- dependency: int updated dependency in case netperf is not installed netperf_runners: List[NetperfRunner] all the netperf flows generated workers: List[multiprocessing.Process] Processes to run netperf flows ss_schedules: dict updated ss_schedules """ netperf_runners = [] if not dependency: logger.warning("Netperf not found. Tcp flows cannot be generated") else: # Get flow attributes [ src_ns, dst_ns, dst_addr, start_t, stop_t, n_flows, options, ] = flow._get_props() # pylint: disable=protected-access # Run netserver if not already run before on given dst_node if dst_ns not in destination_nodes: NetperfRunner.run_netserver(dst_ns) src_name = TopologyMap.get_namespace(src_ns)["name"] netperf_options = {} netperf_options["testname"] = "TCP_STREAM" netperf_options["cong_algo"] = options["cong_algo"] f_flow = "flow" if n_flows == 1 else "flows" logger.info( "Running %s netperf %s from %s to %s...", n_flows, f_flow, src_name, dst_addr, ) # Create new processes to be run simultaneously for _ in range(n_flows): runner_obj = NetperfRunner(src_ns, dst_addr, start_t, stop_t - start_t, **netperf_options) netperf_runners.append(runner_obj) # Find the start time and stop time to run ss command in `src_ns` to a `dst_addr` ss_schedules = _get_start_stop_time_for_ss(src_ns, dst_addr, start_t, stop_t, ss_schedules) return netperf_runners, ss_schedules