mininet-sample/core/experiment.py
2020-07-13 09:55:15 +02:00

451 lines
18 KiB
Python

from .parameter import Parameter
from topos.multi_interface import MultiInterfaceTopo
class ExperimentParameter(Parameter):
"""
Handler for experiment parameters stored in configuration files
"""
RMEM = "rmem"
WMEM = "wmem"
SCHED = "sched"
CC = "congctrl"
AUTOCORK = "autocork"
EARLY_RETRANS = "earlyRetrans"
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"
CLIENT_PCAP = "clientPcap"
SERVER_PCAP = "serverPcap"
SNAPLEN_PCAP = "snaplenPcap"
XP_TYPE = "xpType"
PING_COUNT = "pingCount"
PRIO_PATH0 = "prioPath0"
PRIO_PATH1 = "prioPath1"
BACKUP_PATH0 = "backupPath0"
BACKUP_PATH1 = "backupPath1"
EXPIRATION = "expiration"
BUFFER_AUTOTUNING = "bufferAutotuning"
METRIC = "metric"
# Global sysctl keys
SYSCTL_KEY = {
RMEM: "net.ipv4.tcp_rmem",
WMEM: "net.ipv4.tcp_wmem",
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",
EXPIRATION: "net.mptcp.mptcp_sched_expiration",
BUFFER_AUTOTUNING: "net.ipv4.tcp_moderate_rcvbuf",
}
# 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",
}
# Default values for unspecified experiment parameters
DEFAULT_PARAMETERS = {
RMEM: "10240 87380 16777216",
WMEM: "4096 16384 4194304",
KERNELPM: "fullmesh",
KERNELPMC: "fullmesh",
KERNELPMS: "fullmesh",
USERPMC: "fullmesh",
USERPMS: "fullmesh",
USERPMC_ARGS: "",
USERPMS_ARGS: "",
CC: "olia",
SCHED: "default",
AUTOCORK: "1",
EARLY_RETRANS: "3",
EXPIRATION: "300",
BUFFER_AUTOTUNING: "1",
METRIC: "-1",
CLIENT_PCAP: "no",
SERVER_PCAP: "no",
SNAPLEN_PCAP: "65535", # Default snapping value of tcpdump
XP_TYPE: "none",
PING_COUNT: "5",
PRIO_PATH0: "0",
PRIO_PATH1: "0",
BACKUP_PATH0: "0",
BACKUP_PATH1: "0",
}
def __init__(self, parameter_filename):
super(ExperimentParameter, self).__init__(parameter_filename)
self.default_parameters = ExperimentParameter.DEFAULT_PARAMETERS
def get(self, key):
val = super(ExperimentParameter, self).get(key)
if val is None:
if key in self.default_parameters:
return self.default_parameters[key]
else:
raise Exception("Parameter not found " + key)
else:
return val
class Experiment(object):
"""
Base class to instantiate an experiment to perform.
This class is not instantiable as it. You must define a child class with the
`NAME` attribute.
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
such case, the inherinting class must override the `PARAMETER_CLASS` class
variable to point to another class inheriting from ExperimentParameter.
Attributes:
experiment_parameter Instance of ExperimentParameter
topo Instance of Topo
topo_config Instance of TopoConfig
"""
PARAMETER_CLASS = ExperimentParameter
def __init__(self, experiment_parameter_filename, topo, topo_config):
"""
Instantiation of this base class only load the experiment parameter
"""
self.experiment_parameter = self.__class__.PARAMETER_CLASS(experiment_parameter_filename)
self.topo = topo
self.topo_config = topo_config
def load_parameters(self):
"""
Load the parameter of interest from self.experiment_parameter
"""
# 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,...)
- A running phase through `run()` (where the actual experiment takes place)
- A cleaning phase through `clean()` (stopping traffic, removing generated files,...)
"""
self.prepare()
self.run()
self.clean()
def prepare(self):
"""
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.
TODO: split experiment traffic and protocol configuration
"""
self.setup_sysctl()
self.run_userspace_path_manager() # TODO to move elsewhere
self.topo_config.configure_network()
self.change_metric() # TODO to move elsewhere
self.put_priority_on_paths() # TODO to move elsewhere
self.disable_tso()
self.runTcpDump()
self.runNetemAt()
def change_metric(self):
"""
Function only meaningful for MPTCP and its specific scheduler
"""
metric = self.experiment_parameter.get(ExperimentParameter.METRIC)
if int(metric) >= 0:
self.topo.command_global(
"echo {} > /sys/module/mptcp_sched_metric/parameters/metric".format(metric))
def put_priority_on_paths(self):
"""
Function only meaningful for MPTCP
"""
# Only meaningful if mpTopo is instance of MultiInterfaceTopo
if isinstance(self.topo, MultiInterfaceTopo):
prioPath0 = self.experiment_parameter.get(ExperimentParameter.PRIO_PATH0)
prioPath1 = self.experiment_parameter.get(ExperimentParameter.PRIO_PATH1)
if not prioPath0 == prioPath1:
self.topo.command_to(self.topo_config.client, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getClientInterface(0) + " priority " + str(prioPath0))
self.topo.command_to(self.topo_config.router, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getRouterInterfaceSwitch(0) + " priority " +
str(prioPath0))
self.topo.command_to(self.topo_config.client, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getClientInterface(1) + " priority " + str(prioPath1))
self.topo.command_to(self.topo_config.router, "/home/mininet/iproute/ip/ip link set dev " +
self.topo_config.getRouterInterfaceSwitch(1) + " priority " +
str(prioPath1))
backupPath0 = self.experiment_parameter.get(ExperimentParameter.BACKUP_PATH0)
if int(backupPath0) > 0:
self.topo.command_to(self.topo_config.client, self.topo_config.interfaceBUPCommand(self.topo_config.getClientInterface(0)))
self.topo.command_to(self.topo_config.router, self.topo_config.interfaceBUPCommand(self.topo_config.getRouterInterfaceSwitch(0)))
backupPath1 = self.experiment_parameter.get(ExperimentParameter.BACKUP_PATH1)
if int(backupPath1) > 0:
self.topo.command_to(self.topo_config.client, self.topo_config.interfaceBUPCommand(self.topo_config.getClientInterface(1)))
self.topo.command_to(self.topo_config.router, self.topo_config.interfaceBUPCommand(self.topo_config.getRouterInterfaceSwitch(1)))
def disable_tso(self):
links = self.topo.getLinkCharacteristics()
i = 0
for l in links:
lname = self.topo_config.getMidLeftName(i)
rname = self.topo_config.getMidRightName(i)
lbox = self.topo.get_host(lname)
lif = self.topo_config.getMidL2RInterface(i)
rif = self.topo_config.getMidR2LInterface(i)
rbox = self.topo.get_host(rname)
print(str(lname) + " " + str(lif))
print(str(rname) + " " + str(rif))
print("boxes " + str(lbox) + " " + str(rbox))
cmd = "ethtool -K " + lif + " tso off"
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = "ethtool -K " + rif + " tso off"
print(cmd)
self.topo.command_to(rbox, cmd)
i = i + 1
# And for the server
cmd = "ethtool -K " + self.topo_config.getServerInterface() + " tso off"
print(cmd)
self.topo.command_to(self.topo_config.server, cmd)
cmd = "ethtool -K " + self.topo_config.getRouterInterfaceSwitch(self.topo_config.getClientInterfaceCount()) + " tso off"
print(cmd)
self.topo.command_to(self.topo_config.router, cmd)
def run_userspace_path_manager(self):
if self.experiment_parameter.get(ExperimentParameter.KERNELPMC) != "netlink":
print("Client : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upmc = self.experiment_parameter.get(ExperimentParameter.USERPMC)
upmca = self.experiment_parameter.get(ExperimentParameter.USERPMC_ARGS)
self.topo.command_to(self.topo_config.client, upmc + \
" " + upmca + " &>upmc.log &")
if self.experiment_parameter.get(ExperimentParameter.KERNELPMS) != "netlink":
print("Server : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upms = self.experiment_parameter.get(ExperimentParameter.USERPMS)
upmsa = self.experiment_parameter.get(ExperimentParameter.USERPMS_ARGS)
self.topo.command_to(self.topo_config.server, upms + \
" " + upmsa + " &>upms.log &")
def cleanUserspacePM(self):
if self.experiment_parameter.get(ExperimentParameter.KERNELPMC) != "netlink":
print("Client : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upmc = self.experiment_parameter.get(ExperimentParameter.USERPMC)
self.topo.command_to(self.topo_config.client, "killall " + upmc)
if self.experiment_parameter.get(ExperimentParameter.KERNELPMS) != "netlink":
print("Server : Error, I can't change the userspace pm if the kernel pm is not netlink !")
else:
upms = self.experiment_parameter.get(ExperimentParameter.USERPMS)
self.topo.command_to(self.topo_config.server, "killall " + upms)
def runNetemAt(self):
if not self.topo.changeNetem == "yes":
print("I don't need to change netem")
return
print("Will change netem config on the fly")
links = self.topo.getLinkCharacteristics()
i = 0
for l in links:
lname = self.topo_config.getMidLeftName(i)
rname = self.topo_config.getMidRightName(i)
lbox = self.topo.get_host(lname)
lif = self.topo_config.getMidL2RInterface(i)
rif = self.topo_config.getMidR2LInterface(i)
rbox = self.topo.get_host(rname)
print(str(lname) + " " + str(lif))
print(str(rname) + " " + str(rif))
print("boxes " + str(lbox) + " " + str(rbox))
cmd = l.buildBwCmd(lif)
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = l.buildBwCmd(rif)
print(cmd)
self.topo.command_to(rbox, cmd)
ilif = self.topo_config.getMidL2RIncomingInterface(i)
irif = self.topo_config.getMidR2LIncomingInterface(i)
cmd = l.buildPolicingCmd(ilif)
print(cmd)
self.topo.command_to(lbox, cmd)
cmd = l.buildPolicingCmd(irif)
print(cmd)
self.topo.command_to(rbox, cmd)
cmd = l.buildNetemCmd(irif)
print(cmd)
self.topo.command_to(rbox, cmd)
cmd = l.buildNetemCmd(ilif)
print(cmd)
self.topo.command_to(lbox, cmd)
i = i + 1
def run(self):
pass
def clean(self):
self.topo.command_to(self.topo_config.client,
"killall tcpdump")
self.topo.command_to(self.topo_config.server,
"killall tcpdump")
self.backUpSysctl()
self.cleanUserspacePM()
pass
def setup_sysctl(self):
self.save_sysctl()
self.write_sysctl()
def save_sysctl(self):
self.sysctlBUP = {}
self._save_sysctl(ExperimentParameter.SYSCTL_KEY, self.sysctlBUP)
self.sysctlBUPC = {}
self._save_sysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self.sysctlBUPS = {}
self._save_sysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _save_sysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlDic:
SYSCTL_KEY = sysctlDic[k]
cmd = self.cmdReadSysctl(SYSCTL_KEY)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't get sysctl " + SYSCTL_KEY)
else:
# For Python3 compatibility
if type(val) is bytes:
val = val.decode()
sysctlBUP[k] = val.split(" ",2)[2][:-1]
def cmdReadSysctl(self, key):
s = "sysctl " + key
return s
def cmd_write_sysctl(self, key, value):
s = self.cmdReadSysctl(key)
s = s + "=\"" + str(value) + "\""
return s
def write_sysctl(self):
self._write_sysctl(ExperimentParameter.SYSCTL_KEY, self.sysctlBUP)
self._write_sysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self._write_sysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _write_sysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlBUP:
SYSCTL_KEY = sysctlDic[k]
sysctlValue = self.experiment_parameter.get(k)
cmd = self.cmd_write_sysctl(SYSCTL_KEY,sysctlValue)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't set sysctl " + SYSCTL_KEY)
def backUpSysctl(self):
self._backUpSysctl(ExperimentParameter.SYSCTL_KEY, self.sysctlBUP)
self._backUpSysctl(ExperimentParameter.SYSCTL_KEY_CLIENT, self.sysctlBUPC,
ns = True, who = self.topo_config.client)
self._backUpSysctl(ExperimentParameter.SYSCTL_KEY_SERVER, self.sysctlBUPS,
ns = True, who = self.topo_config.server)
def _backUpSysctl(self, sysctlDic, sysctlBUP, ns = False, who = None):
for k in sysctlBUP:
SYSCTL_KEY = sysctlDic[k]
sysctlValue = sysctlBUP[k]
cmd = self.cmd_write_sysctl(SYSCTL_KEY,sysctlValue)
if not ns:
val = self.topo.command_global(cmd)
else:
val = self.topo.command_to(who, cmd)
if val == "Error":
print("oooops can't set sysctl " + SYSCTL_KEY)
def runTcpDump(self):
#todo : replace filename by cst
cpcap = self.experiment_parameter.get(ExperimentParameter.CLIENT_PCAP)
spcap = self.experiment_parameter.get(ExperimentParameter.SERVER_PCAP)
snaplenpcap = self.experiment_parameter.get(ExperimentParameter.SNAPLEN_PCAP)
if cpcap == "yes" :
self.topo.command_to(self.topo_config.client,
"tcpdump -i any -s " + snaplenpcap + " -w client.pcap &")
if spcap == "yes" :
self.topo.command_to(self.topo_config.server,
"tcpdump -i any -s " + snaplenpcap + " -w server.pcap &")
if spcap == "yes" or cpcap == "yes":
self.topo.command_to(self.topo_config.client,"sleep 5")
class RandomFileParameter(ExperimentParameter):
"""
Parameters for the RandomFileExperiment
"""
FILE = "file" # file to fetch; if random, we create a file with random data called random.
RANDOM_SIZE = "file_size" # in KB
def __init__(self, experiment_parameter_filename):
super(RandomFileParameter, self).__init__(experiment_parameter_filename)
self.default_parameters.update({
RandomFileParameter.FILE: "random",
RandomFileParameter.RANDOM_SIZE: "1024",
})
class RandomFileExperiment(Experiment):
"""
Enable a experiment to use random files
This class is not directly instantiable
"""
PARAMETER_CLASS = RandomFileParameter
def __init__(self, experiment_parameter_filename, topo, topo_config):
super(RandomFileExperiment, self).__init__(experiment_parameter_filename, topo, topo_config)
self.load_parameters()
self.ping()
def load_parameters(self):
super(RandomFileExperiment, self).load_parameters()
self.file = self.experiment_parameter.get(RandomFileParameter.FILE)
self.random_size = self.experiment_parameter.get(RandomFileParameter.RANDOM_SIZE)
def prepare(self):
super(RandomFileExperiment, self).prepare()
if self.file == "random":
self.topo.command_to(self.topo_config.client,
"dd if=/dev/urandom of=random bs=1K count={}".format(self.random_size))
def clean(self):
super(RandomFileExperiment, self).clean()
if self.file == "random":
self.topo.command_to(self.topo_config.client, "rm random*")