mininet-sample/core/experiment.py

421 lines
17 KiB
Python
Raw Normal View History

2020-06-25 12:56:47 +00:00
from .parameter import Parameter
2020-06-26 09:17:00 +00:00
import logging
2022-02-16 04:26:18 +00:00
2020-06-26 06:52:56 +00:00
class ExperimentParameter(Parameter):
2020-06-25 12:56:47 +00:00
"""
2020-06-26 09:17:00 +00:00
Handler for experiment parameters stored in configuration files.
The following parameters are common (and thus usable) by all experiments.
If you want to add experiement-specific parameters, you should extend this
class.
Attribute:
default_parameters Default values for the parameters
2020-06-25 12:56:47 +00:00
"""
2022-02-16 04:26:18 +00:00
RMEM = "rmem"
WMEM = "wmem"
2020-06-30 09:23:41 +00:00
MPTCP_ENABLED = "mptcpEnabled"
2022-02-16 04:26:18 +00:00
SCHED = "sched"
CC = "congctrl"
AUTOCORK = "autocork"
2020-06-25 15:01:58 +00:00
EARLY_RETRANS = "earlyRetrans"
2022-02-16 04:26:18 +00:00
KERNELPM = "kpm"
KERNELPMC = "kpmc" # kernel path manager client / server
KERNELPMS = "kpms"
USERPMC = "upmc"
USERPMS = "upms" # userspace path manager client / server
USERPMC_ARGS = "upmc_args"
USERPMS_ARGS = "upms_args"
2020-06-25 15:01:58 +00:00
CLIENT_PCAP = "clientPcap"
SERVER_PCAP = "serverPcap"
2020-06-26 09:17:00 +00:00
SNAPLEN_PCAP = "snaplen_pcap"
2022-02-16 04:26:18 +00:00
XP_TYPE = "xpType"
PING_COUNT = "pingCount"
2020-06-26 09:17:00 +00:00
PRIO_PATH_0 = "priority_path_0"
PRIO_PATH_1 = "priority_path_1"
BACKUP_PATH_0 = "backup_path_0"
BACKUP_PATH_1 = "backup_path_1"
2020-06-25 15:01:58 +00:00
BUFFER_AUTOTUNING = "bufferAutotuning"
2020-06-25 12:56:47 +00:00
# Global sysctl keys
SYSCTL_KEY = {
RMEM: "net.ipv4.tcp_rmem",
WMEM: "net.ipv4.tcp_wmem",
2022-02-16 04:26:18 +00:00
# MPTCP_ENABLED: "net.mptcp.mptcp_enabled",
# KERNELPM: "net.mptcp.mptcp_path_manager",
# SCHED: "net.mptcp.mptcp_scheduler",
# CC: "net.ipv4.tcp_congestion_control",
# AUTOCORK: "net.ipv4.tcp_autocorking",
# EARLY_RETRANS: "net.ipv4.tcp_early_retrans",
# BUFFER_AUTOTUNING: "net.ipv4.tcp_moderate_rcvbuf",
2020-06-25 12:56:47 +00:00
}
# sysctl keys specific to client and server, independently
SYSCTL_KEY_CLIENT = {
KERNELPMC: "net.mptcp.mptcp_path_manager",
}
SYSCTL_KEY_SERVER = {
KERNELPMS: "net.mptcp.mptcp_path_manager",
}
2020-06-26 06:52:56 +00:00
# Default values for unspecified experiment parameters
2020-06-25 12:56:47 +00:00
DEFAULT_PARAMETERS = {
RMEM: "10240 87380 16777216",
WMEM: "4096 16384 4194304",
2020-06-30 09:23:41 +00:00
MPTCP_ENABLED: "1",
2020-06-25 12:56:47 +00:00
KERNELPM: "fullmesh",
KERNELPMC: "fullmesh",
KERNELPMS: "fullmesh",
USERPMC: "fullmesh",
USERPMS: "fullmesh",
2020-06-25 15:01:58 +00:00
USERPMC_ARGS: "",
USERPMS_ARGS: "",
2020-06-25 12:56:47 +00:00
CC: "olia",
SCHED: "default",
AUTOCORK: "1",
2020-06-25 15:01:58 +00:00
EARLY_RETRANS: "3",
BUFFER_AUTOTUNING: "1",
CLIENT_PCAP: "no",
SERVER_PCAP: "no",
SNAPLEN_PCAP: "65535", # Default snapping value of tcpdump
XP_TYPE: "none",
PING_COUNT: "5",
2020-06-26 09:17:00 +00:00
PRIO_PATH_0: "0",
PRIO_PATH_1: "0",
BACKUP_PATH_0: "0",
BACKUP_PATH_1: "0",
2020-06-25 12:56:47 +00:00
}
def __init__(self, parameter_filename):
2020-06-26 06:52:56 +00:00
super(ExperimentParameter, self).__init__(parameter_filename)
2020-06-29 07:51:55 +00:00
self.default_parameters.update(ExperimentParameter.DEFAULT_PARAMETERS)
2020-06-25 12:56:47 +00:00
2020-06-26 06:52:56 +00:00
class Experiment(object):
2020-06-24 14:11:54 +00:00
"""
2022-02-16 04:26:18 +00:00
Base class to instantiate an experiment to perform.
2020-06-24 14:11:54 +00:00
2022-02-16 04:26:18 +00:00
This class is not instantiable as it. You must define a child class with the
`NAME` attribute.
2020-06-26 06:52:56 +00:00
By default, an Experiment relies on an instance of ExperimentParameter to
collect the parameters from the experiment configuration file. However, an
experiment may introduce specific parameters in the configuration file. In
2020-06-25 12:56:47 +00:00
such case, the inherinting class must override the `PARAMETER_CLASS` class
2020-06-26 06:52:56 +00:00
variable to point to another class inheriting from ExperimentParameter.
2020-06-25 12:56:47 +00:00
Attributes:
2020-06-26 06:52:56 +00:00
experiment_parameter Instance of ExperimentParameter
topo Instance of Topo
topo_config Instance of TopoConfig
2022-02-16 04:26:18 +00:00
"""
2020-06-26 06:52:56 +00:00
PARAMETER_CLASS = ExperimentParameter
2020-06-24 14:11:54 +00:00
2020-06-26 09:17:00 +00:00
IP_BIN = "ip"
2020-06-29 07:51:55 +00:00
PING_OUTPUT = "ping.log"
2020-06-26 09:17:00 +00:00
2020-06-26 06:52:56 +00:00
def __init__(self, experiment_parameter_filename, topo, topo_config):
2020-06-25 12:56:47 +00:00
"""
2020-06-26 06:52:56 +00:00
Instantiation of this base class only load the experiment parameter
2020-06-25 12:56:47 +00:00
"""
2022-02-16 04:26:18 +00:00
self.experiment_parameter = self.__class__.PARAMETER_CLASS(
experiment_parameter_filename)
self.topo = topo
self.topo_config = topo_config
2020-06-25 12:56:47 +00:00
def load_parameters(self):
"""
2020-06-26 06:52:56 +00:00
Load the parameter of interest from self.experiment_parameter
2020-06-25 12:56:47 +00:00
"""
# Nothing to do in the base class
pass
def classic_run(self):
"""
Default function to perform the experiment. It consists into three phases:
- A preparation phase through `prepare()` (generating experiment files,...)
2020-06-26 06:52:56 +00:00
- A running phase through `run()` (where the actual experiment takes place)
- A cleaning phase through `clean()` (stopping traffic, removing generated files,...)
"""
2020-06-23 11:20:07 +00:00
self.prepare()
self.run()
self.clean()
2020-06-23 11:20:07 +00:00
def prepare(self):
"""
2020-06-26 06:52:56 +00:00
Prepare the environment to run the experiment.
Typically, when you inherit from this class, you want to extend this
method, while still calling this parent function.
2020-06-26 06:52:56 +00:00
TODO: split experiment traffic and protocol configuration
"""
self.setup_sysctl()
self.run_userspace_path_manager() # TODO to move elsewhere
self.put_priority_on_paths() # TODO to move elsewhere
2020-06-26 09:17:00 +00:00
self.run_tcpdump()
self.run_netem_at()
def put_priority_on_paths(self):
"""
Function only meaningful for MPTCP
"""
2022-02-16 04:26:18 +00:00
priority_path_0 = self.experiment_parameter.get(
ExperimentParameter.PRIO_PATH_0)
priority_path_1 = self.experiment_parameter.get(
ExperimentParameter.PRIO_PATH_1)
2020-06-26 09:17:00 +00:00
if not priority_path_0 == priority_path_1:
self.topo.command_to(self.topo_config.client, "{} link set dev {} priority {}".format(
Experiment.IP_BIN, self.topo_config.get_client_interface(0), priority_path_0))
self.topo.command_to(self.topo_config.router, "{} link set dev {} priority {}".format(
Experiment.IP_BIN, self.topo_config.get_router_interface_to_client_switch(0), priority_path_0))
2020-06-26 09:17:00 +00:00
self.topo.command_to(self.topo_config.client, "{} link set dev {} priority {}".format(
Experiment.IP_BIN, self.topo_config.get_client_interface(1), priority_path_1))
self.topo.command_to(self.topo_config.router, "{} link set dev {} priority {}".format(
Experiment.IP_BIN, self.topo_config.get_router_interface_to_client_switch(1), priority_path_1))
2020-06-26 09:17:00 +00:00
2022-02-16 04:26:18 +00:00
backup_path_0 = self.experiment_parameter.get(
ExperimentParameter.BACKUP_PATH_0)
2020-06-26 09:17:00 +00:00
if int(backup_path_0) > 0:
self.topo.command_to(self.topo_config.client,
2022-02-16 04:26:18 +00:00
self.topo_config.interface_backup_command(self.topo_config.get_client_interface(0)))
2020-06-26 09:17:00 +00:00
self.topo.command_to(self.topo_config.router,
2022-02-16 04:26:18 +00:00
self.topo_config.interface_backup_command(self.topo_config.get_router_interface_to_client_switch(0)))
backup_path_1 = self.experiment_parameter.get(
ExperimentParameter.BACKUP_PATH_1)
2020-06-26 09:17:00 +00:00
if int(backup_path_1) > 0:
self.topo.command_to(self.topo_config.client,
2022-02-16 04:26:18 +00:00
self.topo_config.interface_backup_command(self.topo_config.get_client_interface(1)))
2020-06-26 09:17:00 +00:00
self.topo.command_to(self.topo_config.router,
2022-02-16 04:26:18 +00:00
self.topo_config.interface_backup_command(self.topo_config.get_router_interface_to_client_switch(1)))
def run_userspace_path_manager(self):
2020-06-26 09:17:00 +00:00
"""
Function only meaningful to MPTCP with a specific path manager
"""
if self.experiment_parameter.get(ExperimentParameter.KERNELPMC) == "netlink":
logging.info("Running user-space path manager on client")
2020-06-26 06:52:56 +00:00
upmc = self.experiment_parameter.get(ExperimentParameter.USERPMC)
2022-02-16 04:26:18 +00:00
upmca = self.experiment_parameter.get(
ExperimentParameter.USERPMC_ARGS)
2020-06-26 09:17:00 +00:00
self.topo.command_to(self.topo_config.client, "{} {} &>{} &".format(
upmc, upmca, "upmc.log"))
if self.experiment_parameter.get(ExperimentParameter.KERNELPMS) == "netlink":
logging.info("Running user-space path manager on server")
2020-06-26 06:52:56 +00:00
upms = self.experiment_parameter.get(ExperimentParameter.USERPMS)
2022-02-16 04:26:18 +00:00
upmsa = self.experiment_parameter.get(
ExperimentParameter.USERPMS_ARGS)
2020-06-26 09:17:00 +00:00
self.topo.command_to(self.topo_config.server, "{} {} &>{} &".format(
upms, upmsa, "upms.log"))
2020-06-26 09:17:00 +00:00
def clean_userspace_path_manager(self):
if self.experiment_parameter.get(ExperimentParameter.KERNELPMC) == "netlink":
logging.info("Cleaning user-space path manager on client")
2020-06-26 06:52:56 +00:00
upmc = self.experiment_parameter.get(ExperimentParameter.USERPMC)
2022-02-16 04:26:18 +00:00
self.topo.command_to(self.topo_config.client,
"killall {}".format(upmc))
if self.experiment_parameter.get(ExperimentParameter.KERNELPMS) == "netlink":
logging.info("Cleaning user-space path manager on server")
2020-06-26 06:52:56 +00:00
upms = self.experiment_parameter.get(ExperimentParameter.USERPMS)
2022-02-16 04:26:18 +00:00
self.topo.command_to(self.topo_config.client,
"killall {}".format(upms))
2020-06-26 09:17:00 +00:00
def run_netem_at(self):
self.topo_config.run_netem_at()
2020-06-23 11:20:07 +00:00
def run(self):
2020-06-26 09:17:00 +00:00
"""
Perform the experiment
This function MUST be overriden by child classes
"""
raise NotImplementedError("Trying to run Experiment")
2020-06-23 11:20:07 +00:00
def clean(self):
2020-06-26 09:17:00 +00:00
"""
Clean the environment where the experiment took place.
Typically, when you inherit from this class, you want to extend this
method, while still calling this parent function.
"""
self.topo.command_to(self.topo_config.client, "killall tcpdump")
self.topo.command_to(self.topo_config.server, "killall tcpdump")
self.restore_sysctl()
self.clean_userspace_path_manager()
def setup_sysctl(self):
2020-06-26 09:17:00 +00:00
"""
Record the current sysctls of the host and write the experiment ones
"""
self.save_sysctl()
self.write_sysctl()
def save_sysctl(self):
2020-06-26 09:17:00 +00:00
"""
Record the current sysctls
"""
self.sysctl_to_restore = {}
2022-02-16 04:26:18 +00:00
self._save_sysctl(ExperimentParameter.SYSCTL_KEY,
self.sysctl_to_restore)
2020-06-26 09:17:00 +00:00
self.client_sysctl_to_restore = {}
self._save_sysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.client_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.client)
2020-06-26 09:17:00 +00:00
self.server_sysctl_to_restore = {}
self._save_sysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.server_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.server)
2020-06-26 09:17:00 +00:00
def _save_sysctl(self, sysctl_dict, sysctl_to_restore, ns=False, who=None):
for k in sysctl_dict:
sysctl_key = sysctl_dict[k]
cmd = self.read_sysctl_cmd(sysctl_key)
2020-06-23 11:20:07 +00:00
if not ns:
val = self.topo.command_global(cmd)
2020-06-23 11:20:07 +00:00
else:
val = self.topo.command_to(who, cmd)
2020-06-23 11:20:07 +00:00
if val == "Error":
2020-06-26 09:17:00 +00:00
logging.error("unable to get sysctl {}".format(sysctl_key))
2020-06-23 11:20:07 +00:00
else:
# For Python3 compatibility
if type(val) is bytes:
val = val.decode()
2020-06-26 09:17:00 +00:00
sysctl_to_restore[k] = val.split(" ", 2)[2][:-1]
2020-06-26 09:17:00 +00:00
def read_sysctl_cmd(self, key):
"""
Return a bash command to read the sysctl key `key`
"""
return "sysctl {}".format(key)
def cmd_write_sysctl(self, key, value):
2020-06-26 09:17:00 +00:00
"""
Return a bash command to write the sysctl key `key`with value `value`
"""
return '{}="{}"'.format(self.read_sysctl_cmd(key), value)
def write_sysctl(self):
2020-06-26 09:17:00 +00:00
"""
Write the experiment sysctls
"""
2022-02-16 04:26:18 +00:00
self._write_sysctl(ExperimentParameter.SYSCTL_KEY,
self.sysctl_to_restore)
2020-06-26 09:17:00 +00:00
self._write_sysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.client_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.client)
2020-06-26 09:17:00 +00:00
self._write_sysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.server_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.server)
2020-06-26 09:17:00 +00:00
2022-02-16 04:26:18 +00:00
def _write_sysctl(self, sysctl_dict, sysctl_to_restore, ns=False, who=None):
2020-06-26 09:17:00 +00:00
for k in sysctl_to_restore:
sysctl_key = sysctl_dict[k]
sysctl_value = self.experiment_parameter.get(k)
cmd = self.cmd_write_sysctl(sysctl_key, sysctl_value)
2020-06-23 11:20:07 +00:00
if not ns:
val = self.topo.command_global(cmd)
2020-06-23 11:20:07 +00:00
else:
val = self.topo.command_to(who, cmd)
2020-06-23 11:20:07 +00:00
if val == "Error":
2020-06-26 09:17:00 +00:00
logging.error("unable to set sysctl {}".format(sysctl_key))
2020-06-26 09:17:00 +00:00
def restore_sysctl(self):
"""
Restore back the sysctls that were present before running the experiment
"""
2022-02-16 04:26:18 +00:00
self._restore_sysctl(ExperimentParameter.SYSCTL_KEY,
self.sysctl_to_restore)
2020-06-26 09:17:00 +00:00
self._restore_sysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.client_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.client)
2020-06-26 09:17:00 +00:00
self._restore_sysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.server_sysctl_to_restore,
2022-02-16 04:26:18 +00:00
ns=True, who=self.topo_config.server)
2020-06-26 09:17:00 +00:00
2022-02-16 04:26:18 +00:00
def _restore_sysctl(self, sysctl_dict, sysctl_to_restore, ns=False, who=None):
2020-06-26 09:17:00 +00:00
for k in sysctl_to_restore:
sysctl_key = sysctl_dict[k]
sysctl_value = sysctl_to_restore[k]
cmd = self.cmd_write_sysctl(sysctl_key, sysctl_value)
2020-06-23 11:20:07 +00:00
if not ns:
val = self.topo.command_global(cmd)
2020-06-23 11:20:07 +00:00
else:
val = self.topo.command_to(who, cmd)
2020-06-23 11:20:07 +00:00
if val == "Error":
2020-06-26 09:17:00 +00:00
logging.error("unable to set sysctl {}".format(sysctl_key))
2020-06-26 09:17:00 +00:00
def run_tcpdump(self):
2022-02-16 04:26:18 +00:00
client_pcap = self.experiment_parameter.get(
ExperimentParameter.CLIENT_PCAP)
server_pcap = self.experiment_parameter.get(
ExperimentParameter.SERVER_PCAP)
snaplen_pcap = self.experiment_parameter.get(
ExperimentParameter.SNAPLEN_PCAP)
self.topo.command_to(self.topo_config.router,
"tcpdump -i any -s {} -w router.pcap &".format(snaplen_pcap))
2020-06-26 09:17:00 +00:00
if client_pcap == "yes":
self.topo.command_to(self.topo_config.client,
2022-02-16 04:26:18 +00:00
"tcpdump -i any -s {} -w client.pcap &".format(snaplen_pcap))
2020-06-26 09:17:00 +00:00
if server_pcap == "yes":
self.topo.command_to(self.topo_config.server,
2022-02-16 04:26:18 +00:00
"tcpdump -i any -s {} -w server.pcap &".format(snaplen_pcap))
2020-06-26 09:17:00 +00:00
if server_pcap == "yes" or client_pcap == "yes":
logging.info("Activating tcpdump, waiting for it to run")
2022-02-16 04:26:18 +00:00
self.topo.command_to(self.topo_config.client, "sleep 5")
2020-06-25 12:56:47 +00:00
2020-06-29 07:51:55 +00:00
def ping(self):
self.topo.command_to(self.topo_config.client,
2022-02-16 04:26:18 +00:00
"rm {}".format(Experiment.PING_OUTPUT))
2020-06-29 07:51:55 +00:00
count = self.experiment_parameter.get(ExperimentParameter.PING_COUNT)
for j in range(0, self.topo_config.server_interface_count()):
for i in range(0, self.topo_config.client_interface_count()):
cmd = self.ping_command(self.topo_config.get_client_ip(i),
2022-02-16 04:26:18 +00:00
self.topo_config.get_server_ip(interface_index=j), n=count)
logging.info(cmd)
self.topo.command_to(self.topo_config.client, cmd)
2020-06-29 07:51:55 +00:00
def ping_command(self, from_ip, to_ip, n=5):
return "ping -c {} -I {} {} >> {}".format(n, from_ip, to_ip, Experiment.PING_OUTPUT)
2020-06-25 12:56:47 +00:00
2020-06-26 06:52:56 +00:00
class RandomFileParameter(ExperimentParameter):
2020-06-25 12:56:47 +00:00
"""
2020-06-26 06:52:56 +00:00
Parameters for the RandomFileExperiment
2020-06-25 12:56:47 +00:00
"""
FILE = "file" # file to fetch; if random, we create a file with random data called random.
RANDOM_SIZE = "file_size" # in KB
2020-06-26 06:52:56 +00:00
def __init__(self, experiment_parameter_filename):
2022-02-16 04:26:18 +00:00
super(RandomFileParameter, self).__init__(
experiment_parameter_filename)
2020-06-25 12:56:47 +00:00
self.default_parameters.update({
RandomFileParameter.FILE: "random",
RandomFileParameter.RANDOM_SIZE: "1024",
})
2020-06-26 06:52:56 +00:00
class RandomFileExperiment(Experiment):
2020-06-25 12:56:47 +00:00
"""
2020-06-26 06:52:56 +00:00
Enable a experiment to use random files
2020-06-25 12:56:47 +00:00
This class is not directly instantiable
"""
PARAMETER_CLASS = RandomFileParameter
2020-06-26 06:52:56 +00:00
def __init__(self, experiment_parameter_filename, topo, topo_config):
2022-02-16 04:26:18 +00:00
super(RandomFileExperiment, self).__init__(
experiment_parameter_filename, topo, topo_config)
2020-06-25 12:56:47 +00:00
self.load_parameters()
self.ping()
def load_parameters(self):
2020-06-26 06:52:56 +00:00
super(RandomFileExperiment, self).load_parameters()
self.file = self.experiment_parameter.get(RandomFileParameter.FILE)
2022-02-16 04:26:18 +00:00
self.random_size = self.experiment_parameter.get(
RandomFileParameter.RANDOM_SIZE)
2020-06-25 12:56:47 +00:00
def prepare(self):
2020-06-26 06:52:56 +00:00
super(RandomFileExperiment, self).prepare()
2022-02-16 04:26:18 +00:00
if self.file == "random":
2020-06-25 12:56:47 +00:00
self.topo.command_to(self.topo_config.client,
2022-02-16 04:26:18 +00:00
"dd if=/dev/urandom of=random bs=1K count={}".format(self.random_size))
2020-06-25 12:56:47 +00:00
def clean(self):
2020-06-26 06:52:56 +00:00
super(RandomFileExperiment, self).clean()
2022-02-16 04:26:18 +00:00
if self.file == "random":
2020-06-25 12:56:47 +00:00
self.topo.command_to(self.topo_config.client, "rm random*")