2020-06-24 08:36:26 +00:00
|
|
|
from .parameter import Parameter
|
|
|
|
|
2020-06-26 09:17:00 +00:00
|
|
|
import logging
|
2020-06-24 09:26:56 +00:00
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
|
|
class NetemAt(object):
|
2020-06-29 07:51:55 +00:00
|
|
|
"""
|
|
|
|
Class representing a netem command to be run after some time
|
|
|
|
"""
|
2020-06-24 10:19:05 +00:00
|
|
|
def __init__(self, at, cmd):
|
|
|
|
self.at = at
|
|
|
|
self.cmd = cmd
|
|
|
|
self.delta = 0
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-24 10:19:05 +00:00
|
|
|
def __str__(self):
|
2020-06-26 10:04:09 +00:00
|
|
|
return "netem at {} ({}) will be {}".format(self.at, self.delta, self.cmd)
|
2020-06-24 09:26:56 +00:00
|
|
|
|
|
|
|
|
2020-07-03 13:20:20 +00:00
|
|
|
def get_bandwidth_delay_product_divided_by_mtu(delay, bandwidth):
|
|
|
|
"""
|
|
|
|
With delay in ms, bandwidth in Mbps
|
|
|
|
"""
|
|
|
|
rtt = 2 * float(delay)
|
|
|
|
bandwidth_delay_product = (float(bandwidth) * 125000.0) * (rtt / 1000.0)
|
|
|
|
return int(math.ceil(bandwidth_delay_product * 1.0 / 1500.0))
|
|
|
|
|
|
|
|
|
2020-06-24 09:26:56 +00:00
|
|
|
class LinkCharacteristics(object):
|
2020-06-26 10:04:09 +00:00
|
|
|
"""
|
|
|
|
Network characteristics associated to a link
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
id the identifier of the link
|
2020-07-03 13:20:20 +00:00
|
|
|
link_type type of the link
|
2020-06-26 10:04:09 +00:00
|
|
|
delay the one-way delay introduced by the link in ms
|
|
|
|
queue_size the size of the link buffer, in packets
|
|
|
|
bandwidth the bandwidth of the link in Mbps
|
|
|
|
loss the random loss rate in percentage
|
|
|
|
queuing_delay the maximum time that a packet can stay in the link buffer (computed over queue_size)
|
|
|
|
netem_at list of NetemAt instances applicable to the link
|
|
|
|
backup integer indicating if this link is a backup one or not (useful for MPTCP)
|
|
|
|
"""
|
2020-07-03 13:20:20 +00:00
|
|
|
def __init__(self, id, link_type, delay, queue_size, bandwidth, loss, backup=0):
|
2020-06-26 10:04:09 +00:00
|
|
|
self.id = id
|
2020-07-03 13:20:20 +00:00
|
|
|
self.link_type = link_type
|
2020-06-26 10:04:09 +00:00
|
|
|
self.delay = delay
|
|
|
|
self.queue_size = queue_size
|
|
|
|
self.bandwidth = bandwidth
|
|
|
|
self.loss = loss
|
|
|
|
self.queuing_delay = str(self.extract_queuing_delay(queue_size, bandwidth, delay))
|
|
|
|
self.netem_at = []
|
|
|
|
self.backup = backup
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-26 10:04:09 +00:00
|
|
|
def bandwidth_delay_product_divided_by_mtu(self):
|
|
|
|
"""
|
|
|
|
Get the bandwidth-delay product in terms of packets (hence, dividing by the MTU)
|
|
|
|
"""
|
2020-07-03 13:20:20 +00:00
|
|
|
return get_bandwidth_delay_product_divided_by_mtu(self.delay, self.bandwidth)
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-26 10:04:09 +00:00
|
|
|
def buffer_size(self):
|
|
|
|
"""
|
|
|
|
Return the buffer size in bytes
|
|
|
|
"""
|
|
|
|
return (1500.0 * self.bandwidth_delay_product_divided_by_mtu()) + \
|
|
|
|
(float(self.bandwidth) * 1000.0 * float(self.queuing_delay) / 8)
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-26 10:04:09 +00:00
|
|
|
def extract_queuing_delay(self, queue_size, bandwidth, delay, mtu=1500):
|
|
|
|
queuing_delay = (int(queue_size) * int(mtu) * 8.0 * 1000.0) / \
|
|
|
|
(float(bandwidth) * 1024 * 1024)
|
|
|
|
return max(int(queuing_delay), 1)
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-26 10:04:09 +00:00
|
|
|
def add_netem_at(self, n):
|
|
|
|
if len(self.netem_at) == 0:
|
2020-06-24 09:26:56 +00:00
|
|
|
n.delta = n.at
|
2020-06-26 10:04:09 +00:00
|
|
|
self.netem_at.append(n)
|
2020-06-24 09:26:56 +00:00
|
|
|
else:
|
2020-06-26 10:04:09 +00:00
|
|
|
if n.at > self.netem_at[-1].at:
|
|
|
|
n.delta = n.at - self.netem_at[-1].at
|
|
|
|
self.netem_at.append(n)
|
2020-06-24 09:26:56 +00:00
|
|
|
else:
|
2020-06-26 10:04:09 +00:00
|
|
|
logging.error("{}: not taken into account because not specified in order in the topo param file".format(n))
|
|
|
|
|
2020-07-01 14:24:37 +00:00
|
|
|
def build_delete_tc_cmd(self, ifname):
|
|
|
|
return "tc qdisc del dev {} root; tc qdisc del dev {} ingress ".format(ifname, ifname)
|
|
|
|
|
|
|
|
def build_bandwidth_cmd(self, ifname, change=False):
|
|
|
|
return "tc qdisc {} dev {} root handle 5:0 tbf rate {}mbit burst 15000 limit {}".format(
|
|
|
|
"change" if change else "add", ifname, self.bandwidth, self.buffer_size())
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
def build_changing_bandwidth_cmd(self, ifname):
|
2020-07-03 14:45:18 +00:00
|
|
|
return "&& ".join(
|
2020-06-29 10:46:07 +00:00
|
|
|
["sleep {} && {} ".format(
|
2020-07-01 14:24:37 +00:00
|
|
|
n.delta, self.build_bandwidth_cmd(ifname, change=True)) for n in self.netem_at]
|
2020-06-29 10:46:07 +00:00
|
|
|
+ ["true &"]
|
2020-06-26 10:04:09 +00:00
|
|
|
)
|
|
|
|
|
2020-07-01 14:24:37 +00:00
|
|
|
def build_netem_cmd(self, ifname, cmd, change=False):
|
2020-07-03 14:39:49 +00:00
|
|
|
return "tc qdisc {} dev {} root handle 10: netem {} {}".format(
|
|
|
|
"change" if change else "add", ifname, cmd, "delay {}ms limit 50000".format(self.delay) if not change else "")
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
def build_changing_netem_cmd(self, ifname):
|
2020-07-03 14:45:18 +00:00
|
|
|
return "&& ".join(
|
2020-06-29 10:46:07 +00:00
|
|
|
["sleep {} && {} ".format(
|
2020-07-01 14:24:37 +00:00
|
|
|
n.delta, self.build_netem_cmd(ifname, n.cmd, change=True)) for n in self.netem_at]
|
2020-06-29 10:46:07 +00:00
|
|
|
+ ["true &"]
|
2020-06-26 10:04:09 +00:00
|
|
|
)
|
|
|
|
|
2020-07-01 14:24:37 +00:00
|
|
|
def build_policing_cmd(self, ifname, change=False):
|
|
|
|
return "tc qdisc {} dev {} handle ffff: ingress ; \
|
|
|
|
tc filter {} dev {} parent ffff: u32 match u32 0 0 police rate {}mbit burst {} drop".format(
|
|
|
|
"change" if change else "add", ifname, "change" if change else "add", ifname,
|
|
|
|
self.bandwidth, int(self.buffer_size()) * 1.2)
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
def build_changing_policing_cmd(self, ifname):
|
2020-07-03 14:45:18 +00:00
|
|
|
return "&& ".join(
|
2020-07-03 14:50:10 +00:00
|
|
|
["sleep {} && ({}) ".format(
|
2020-07-01 14:24:37 +00:00
|
|
|
n.delta, self.build_policing_cmd(ifname, change=True)) for n in self.netem_at]
|
2020-06-29 10:46:07 +00:00
|
|
|
+ ["true &"]
|
2020-06-26 10:04:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def as_dict(self):
|
2020-06-29 10:46:07 +00:00
|
|
|
"""
|
|
|
|
Notably used by BottleneckLink
|
|
|
|
"""
|
2020-06-26 10:04:09 +00:00
|
|
|
return {
|
2020-07-03 13:20:20 +00:00
|
|
|
"link_id": self.id,
|
|
|
|
"link_type": self.link_type,
|
2020-06-26 10:04:09 +00:00
|
|
|
"bw": float(self.bandwidth),
|
|
|
|
"delay": "{}ms".format(self.delay),
|
|
|
|
"loss": float(self.loss),
|
|
|
|
"max_queue_size": int(self.queue_size)
|
|
|
|
}
|
2020-06-24 09:26:56 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
2020-06-26 10:04:09 +00:00
|
|
|
return """
|
2020-07-03 13:20:20 +00:00
|
|
|
Link type: {}
|
2020-06-26 10:04:09 +00:00
|
|
|
Link id: {}
|
|
|
|
Delay: {}
|
|
|
|
Queue Size: {}
|
|
|
|
Bandwidth: {}
|
|
|
|
Loss: {}
|
|
|
|
Backup: {}
|
2020-07-03 13:20:20 +00:00
|
|
|
""".format(self.link_type, self.id, self.delay, self.queue_size, self.bandwidth, self.loss, self.backup) + \
|
2020-06-26 10:04:09 +00:00
|
|
|
"".join(["\t {} \n".format(n) for n in self.netem_at])
|
2020-06-24 09:26:56 +00:00
|
|
|
|
2020-06-24 08:36:26 +00:00
|
|
|
|
|
|
|
class TopoParameter(Parameter):
|
2020-06-29 07:51:55 +00:00
|
|
|
LEFT_SUBNET = "leftSubnet"
|
|
|
|
RIGHT_SUBNET = "rightSubnet"
|
2020-07-03 14:39:49 +00:00
|
|
|
NETEM_AT = "netemAt_"
|
2020-06-29 07:51:55 +00:00
|
|
|
CHANGE_NETEM = "changeNetem"
|
|
|
|
|
|
|
|
DEFAULT_PARAMETERS = {
|
|
|
|
LEFT_SUBNET: "10.1.",
|
|
|
|
RIGHT_SUBNET: "10.2.",
|
|
|
|
CHANGE_NETEM: "false",
|
|
|
|
}
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-25 12:56:47 +00:00
|
|
|
def __init__(self, parameter_filename):
|
|
|
|
Parameter.__init__(self, parameter_filename)
|
2020-06-29 07:51:55 +00:00
|
|
|
self.default_parameters.update(TopoParameter.DEFAULT_PARAMETERS)
|
|
|
|
self.link_characteristics = []
|
|
|
|
self.load_link_characteristics()
|
|
|
|
self.load_netem_at()
|
|
|
|
logging.info(self)
|
|
|
|
|
2020-07-03 14:39:49 +00:00
|
|
|
def parse_netem_at(self, key):
|
|
|
|
"""
|
|
|
|
Parse key of the form netemAt_{link_type}_{link_id}
|
|
|
|
|
|
|
|
Return link_type, link_id
|
|
|
|
"""
|
|
|
|
_, link_type, link_id = key.split("_")
|
|
|
|
return link_type, int(link_id)
|
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def load_netem_at(self):
|
|
|
|
if not self.get(TopoParameter.CHANGE_NETEM) == "yes":
|
2020-06-24 10:19:05 +00:00
|
|
|
return
|
2020-06-25 12:56:47 +00:00
|
|
|
for k in sorted(self.parameters):
|
2020-06-29 07:51:55 +00:00
|
|
|
if k.startswith(TopoParameter.NETEM_AT):
|
2020-07-03 14:39:49 +00:00
|
|
|
link_type, link_id = self.parse_netem_at(k)
|
|
|
|
self.load_netem_at_value(link_type, link_id, self.parameters[k])
|
|
|
|
|
|
|
|
def find_link_characteristic(self, link_type, link_id):
|
|
|
|
for l in self.link_characteristics:
|
|
|
|
if l.link_type == link_type and l.id == link_id:
|
|
|
|
return l
|
|
|
|
|
|
|
|
return ValueError("No link with link_type {} and link_id {}".format(link_type, link_id))
|
|
|
|
|
|
|
|
def load_netem_at_value(self, link_type, link_id, n):
|
|
|
|
try:
|
|
|
|
at, cmd = n.split(",")
|
|
|
|
na = NetemAt(float(at), cmd)
|
|
|
|
l = self.find_link_characteristic(link_type, link_id)
|
|
|
|
l.add_netem_at(na)
|
|
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
logging.error("Unable to set netem for link {} with command {}: {}".format(link_id, n, e))
|
2020-06-29 07:51:55 +00:00
|
|
|
|
|
|
|
logging.info(self.link_characteristics[link_id].netem_at)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-07-03 13:20:20 +00:00
|
|
|
def parse_link_id_and_type(self, key):
|
|
|
|
"""
|
|
|
|
The key of a path must have the following format:
|
|
|
|
path_{link_type}_{ID}
|
|
|
|
|
|
|
|
Note that several links can have the same ID, several links can have the same
|
|
|
|
link_type, but the tuple (link_type, ID) is unique.
|
|
|
|
"""
|
|
|
|
_, link_type, link_id = key.split("_")
|
|
|
|
return link_type, int(link_id)
|
|
|
|
|
|
|
|
def parse_link_characteristics(self, value):
|
|
|
|
"""
|
|
|
|
The format of a link characteristic is one of the following:
|
|
|
|
- "{delay},{queue_size},{bandwidth},{loss_perc},{is_backup}"
|
|
|
|
- "{delay},{queue_size},{bandwidth},{loss_perc}"
|
|
|
|
- "{delay},{queue_size},{bandwidth}"
|
|
|
|
- "{delay},{bandwidth}"
|
|
|
|
|
|
|
|
When not specified, default values are the following:
|
|
|
|
- queue_size: get_bandwidth_delay_product_divided_by_mtu(delay, bandwidth)
|
|
|
|
- loss_perc: 0
|
|
|
|
- is_backup: 0
|
|
|
|
|
|
|
|
Return
|
|
|
|
delay, bandwidth, queue_size, loss_perc, is_backup
|
|
|
|
"""
|
|
|
|
loss_perc, is_backup = 0.0, 0
|
|
|
|
c = value.split(",")
|
|
|
|
if len(c) == 2:
|
|
|
|
delay, bw = float(c[0]), float(c[1])
|
|
|
|
return delay, bw, get_bandwidth_delay_product_divided_by_mtu(delay, bw), loss_perc, is_backup
|
|
|
|
if len(c) == 3:
|
|
|
|
return float(c[0]), float(c[2]), int(c[1]), loss_perc, is_backup
|
|
|
|
if len(c) == 4:
|
|
|
|
return float(c[0]), float(c[2]), int(c[1]), float(c[3]), is_backup
|
|
|
|
if len(c) == 5:
|
|
|
|
return float(c[0]), float(c[2]), int(c[1]), float(c[3]), int(c[4])
|
|
|
|
|
|
|
|
raise ValueError("Invalid link characteristics: {}".format(value))
|
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def load_link_characteristics(self):
|
|
|
|
"""
|
2020-07-03 13:20:20 +00:00
|
|
|
Load the path characteristics
|
2020-06-29 07:51:55 +00:00
|
|
|
"""
|
2020-06-25 12:56:47 +00:00
|
|
|
for k in sorted(self.parameters):
|
2020-06-24 10:19:05 +00:00
|
|
|
if k.startswith("path"):
|
2020-07-03 13:20:20 +00:00
|
|
|
try:
|
|
|
|
link_type, link_id = self.parse_link_id_and_type(k)
|
|
|
|
delay, bw, queue_size, loss_perc, is_backup = self.parse_link_characteristics(
|
|
|
|
self.parameters[k])
|
|
|
|
except ValueError as e:
|
|
|
|
logging.error("Ignored path {}: {}".format(k, e))
|
2020-06-24 10:19:05 +00:00
|
|
|
else:
|
2020-07-03 13:27:18 +00:00
|
|
|
path = LinkCharacteristics(link_id, link_type, delay, queue_size,
|
|
|
|
bw, loss_perc, backup=is_backup)
|
2020-07-03 13:20:20 +00:00
|
|
|
self.link_characteristics.append(path)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
2020-06-29 07:51:55 +00:00
|
|
|
s = "{}".format(super(TopoParameter, self).__str__())
|
|
|
|
s += "".join(["{}".format(lc) for lc in self.link_characteristics])
|
2020-06-24 10:19:05 +00:00
|
|
|
return s
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
class BottleneckLink(object):
|
|
|
|
"""
|
|
|
|
Representation of a bottleneck link having limited bandwidth, a buffer,
|
|
|
|
experiencing propagation delay and introducing packet losses.
|
|
|
|
|
|
|
|
A bottleneck link has the following actual representation:
|
|
|
|
|
|
|
|
bs0 -- bs1 -- bs2 -- bs3
|
|
|
|
|
|
|
|
Where bs0 (resp. bs3) is the left (resp. right) side of the link, and having
|
|
|
|
TC commands for the packet flow s0 -> s3 as follows:
|
|
|
|
- Policing command to implement buffer on ingress of bs1 from bs0
|
|
|
|
- Shaping command to implement bandwidth on egress of bs1 to bs2
|
|
|
|
- Netem command to implement delay and loss on egress of bs2 to bs3
|
|
|
|
"""
|
|
|
|
BOTTLENECK_SWITCH_NAME_PREFIX = "bs"
|
|
|
|
|
|
|
|
def __init__(self, topo_builder, topo, link_characteristics):
|
|
|
|
self.link_characteristics = link_characteristics
|
|
|
|
self.topo = topo
|
2020-06-29 14:02:17 +00:00
|
|
|
self.bs0 = topo_builder.add_switch(self.get_bs_name(0))
|
|
|
|
self.bs1 = topo_builder.add_switch(self.get_bs_name(1))
|
|
|
|
self.bs2 = topo_builder.add_switch(self.get_bs_name(2))
|
|
|
|
self.bs3 = topo_builder.add_switch(self.get_bs_name(3))
|
2020-06-29 10:46:07 +00:00
|
|
|
topo_builder.add_link(self.bs0, self.bs1)
|
|
|
|
topo_builder.add_link(self.bs1, self.bs2)
|
|
|
|
topo_builder.add_link(self.bs2, self.bs3)
|
|
|
|
|
2020-06-29 14:02:17 +00:00
|
|
|
def get_bs_name(self, index):
|
2020-07-03 13:20:20 +00:00
|
|
|
return "{}_{}_{}_{}".format(BottleneckLink.BOTTLENECK_SWITCH_NAME_PREFIX,
|
|
|
|
self.link_characteristics.link_type, self.link_characteristics.id, index)
|
2020-06-29 14:02:17 +00:00
|
|
|
|
|
|
|
def reinit_variables(self):
|
2020-06-29 12:22:39 +00:00
|
|
|
# Required to retrieve actual nodes
|
2020-06-29 14:02:17 +00:00
|
|
|
self.bs0 = self.topo.get_host(self.get_bs_name(0))
|
|
|
|
self.bs1 = self.topo.get_host(self.get_bs_name(1))
|
|
|
|
self.bs2 = self.topo.get_host(self.get_bs_name(2))
|
|
|
|
self.bs3 = self.topo.get_host(self.get_bs_name(3))
|
|
|
|
|
|
|
|
def configure_bottleneck(self):
|
2020-06-29 12:13:47 +00:00
|
|
|
bs1_interface_names = self.topo.get_interface_names(self.bs1)
|
2020-07-01 14:24:37 +00:00
|
|
|
bs2_interface_names = self.topo.get_interface_names(self.bs2)
|
|
|
|
|
|
|
|
# Cleanup tc commands
|
|
|
|
for bs1_ifname in bs1_interface_names:
|
|
|
|
clean_cmd = self.link_characteristics.build_delete_tc_cmd(bs1_ifname)
|
|
|
|
logging.info(clean_cmd)
|
2020-07-01 14:25:45 +00:00
|
|
|
self.topo.command_to(self.bs1, clean_cmd)
|
2020-07-01 14:24:37 +00:00
|
|
|
|
|
|
|
for bs2_ifname in bs2_interface_names:
|
|
|
|
clean_cmd = self.link_characteristics.build_delete_tc_cmd(bs2_ifname)
|
|
|
|
logging.info(clean_cmd)
|
2020-07-01 14:25:45 +00:00
|
|
|
self.topo.command_to(self.bs2, clean_cmd)
|
2020-07-01 14:24:37 +00:00
|
|
|
|
2020-06-29 10:46:07 +00:00
|
|
|
# Flow bs0 -> bs3
|
2020-06-29 12:13:47 +00:00
|
|
|
policing_cmd = self.link_characteristics.build_policing_cmd(bs1_interface_names[0])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(policing_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, policing_cmd)
|
|
|
|
shaping_cmd = self.link_characteristics.build_bandwidth_cmd(bs1_interface_names[-1])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(shaping_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, shaping_cmd)
|
|
|
|
netem_cmd = self.link_characteristics.build_netem_cmd(bs2_interface_names[-1],
|
2020-07-01 14:39:30 +00:00
|
|
|
"loss {}".format(self.link_characteristics.loss) if float(self.link_characteristics.loss) > 0 else "")
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(netem_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, netem_cmd)
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
# Flow bs3 -> bs0
|
2020-06-29 12:13:47 +00:00
|
|
|
policing_cmd = self.link_characteristics.build_policing_cmd(bs2_interface_names[-1])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(policing_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, policing_cmd)
|
|
|
|
shaping_cmd = self.link_characteristics.build_bandwidth_cmd(bs2_interface_names[0])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(shaping_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, shaping_cmd)
|
|
|
|
netem_cmd = self.link_characteristics.build_netem_cmd(bs1_interface_names[0],
|
2020-07-01 14:39:30 +00:00
|
|
|
"loss {}".format(self.link_characteristics.loss) if float(self.link_characteristics.loss) > 0 else "")
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(netem_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, netem_cmd)
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
def configure_changing_bottleneck(self):
|
2020-06-29 12:13:47 +00:00
|
|
|
bs1_interface_names = self.topo.get_interface_names(self.bs1)
|
|
|
|
bs2_interface_names = self.topo.get_interface_names(self.bs2)
|
2020-06-29 10:46:07 +00:00
|
|
|
# Flow bs0 -> bs3
|
2020-06-29 12:13:47 +00:00
|
|
|
policing_cmd = self.link_characteristics.build_changing_policing_cmd(bs1_interface_names[0])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(policing_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, policing_cmd)
|
|
|
|
shaping_cmd = self.link_characteristics.build_changing_bandwidth_cmd(bs1_interface_names[-1])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(shaping_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, shaping_cmd)
|
|
|
|
netem_cmd = self.link_characteristics.build_changing_netem_cmd(bs2_interface_names[-1])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(netem_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, netem_cmd)
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
# Flow bs3 -> bs0
|
2020-06-29 12:13:47 +00:00
|
|
|
policing_cmd = self.link_characteristics.build_changing_policing_cmd(bs2_interface_names[-1])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(policing_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, policing_cmd)
|
|
|
|
shaping_cmd = self.link_characteristics.build_changing_bandwidth_cmd(bs2_interface_names[0])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(shaping_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs2, shaping_cmd)
|
|
|
|
netem_cmd = self.link_characteristics.build_changing_netem_cmd(bs1_interface_names[0])
|
2020-06-29 10:46:07 +00:00
|
|
|
logging.info(netem_cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(self.bs1, netem_cmd)
|
2020-06-29 10:46:07 +00:00
|
|
|
|
|
|
|
def get_left(self):
|
|
|
|
return self.bs0
|
|
|
|
|
|
|
|
def get_right(self):
|
|
|
|
return self.bs3
|
|
|
|
|
|
|
|
|
2020-06-24 08:54:44 +00:00
|
|
|
class Topo(object):
|
2020-06-24 10:28:44 +00:00
|
|
|
"""
|
2020-06-24 14:11:54 +00:00
|
|
|
Base class to instantiate a topology.
|
2020-06-24 10:28:44 +00:00
|
|
|
|
2020-06-29 10:46:07 +00:00
|
|
|
The network topology has always the following elements:
|
|
|
|
- a (set of) client(s)
|
|
|
|
- a (set of) router(s)
|
|
|
|
- a (set of) server(s)
|
|
|
|
- a set of bottleneck links
|
|
|
|
|
2020-06-24 14:11:54 +00:00
|
|
|
This class is not instantiable as it. You must define a child class with the
|
|
|
|
`NAME` attribute.
|
2020-06-29 07:51:55 +00:00
|
|
|
|
|
|
|
Attributes:
|
|
|
|
topo_builder instance of TopoBuilder
|
|
|
|
topo_parameter instance of TopoParameter
|
|
|
|
change_netem boolean indicating if netem must be changed
|
|
|
|
log_file file descriptor logging commands relative to the topo
|
2020-06-24 14:11:54 +00:00
|
|
|
"""
|
2020-06-25 08:53:56 +00:00
|
|
|
MININET_BUILDER = "mininet"
|
|
|
|
TOPO_ATTR = "topoType"
|
2020-06-29 07:51:55 +00:00
|
|
|
SWITCH_NAME_PREFIX = "s"
|
2020-06-29 14:02:17 +00:00
|
|
|
CLIENT_NAME_PREFIX = "Client"
|
|
|
|
SERVER_NAME_PREFIX = "Server"
|
|
|
|
ROUTER_NAME_PREFIX = "Router"
|
2020-06-29 07:51:55 +00:00
|
|
|
CMD_LOG_FILENAME = "command.log"
|
|
|
|
|
|
|
|
def __init__(self, topo_builder, topo_parameter):
|
|
|
|
self.topo_builder = topo_builder
|
|
|
|
self.topo_parameter = topo_parameter
|
|
|
|
self.change_netem = topo_parameter.get(TopoParameter.CHANGE_NETEM).lower() == "yes"
|
|
|
|
self.log_file = open(Topo.CMD_LOG_FILENAME, 'w')
|
2020-06-29 10:46:07 +00:00
|
|
|
self.clients = []
|
|
|
|
self.routers = []
|
|
|
|
self.servers = []
|
|
|
|
self.bottleneck_links = []
|
2020-06-29 07:51:55 +00:00
|
|
|
|
2020-06-29 14:02:17 +00:00
|
|
|
def get_client_name(self, index):
|
|
|
|
return "{}_{}".format(Topo.CLIENT_NAME_PREFIX, index)
|
|
|
|
|
|
|
|
def get_router_name(self, index):
|
|
|
|
return "{}_{}".format(Topo.ROUTER_NAME_PREFIX, index)
|
|
|
|
|
|
|
|
def get_server_name(self, index):
|
|
|
|
return "{}_{}".format(Topo.SERVER_NAME_PREFIX, index)
|
|
|
|
|
|
|
|
def add_client(self):
|
|
|
|
client = self.add_host(self.get_client_name(self.client_count()))
|
|
|
|
self.clients.append(client)
|
|
|
|
return client
|
|
|
|
|
|
|
|
def add_router(self):
|
|
|
|
router = self.add_host(self.get_router_name(self.router_count()))
|
|
|
|
self.routers.append(router)
|
|
|
|
return router
|
|
|
|
|
|
|
|
def add_server(self):
|
|
|
|
server = self.add_host(self.get_server_name(self.server_count()))
|
|
|
|
self.servers.append(server)
|
|
|
|
return server
|
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def get_link_characteristics(self):
|
|
|
|
return self.topo_parameter.link_characteristics
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def command_to(self, who, cmd):
|
2020-06-29 07:51:55 +00:00
|
|
|
self.log_file.write("{} : {}\n".format(who, cmd))
|
|
|
|
return self.topo_builder.command_to(who, cmd)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def command_global(self, cmd):
|
2020-06-24 10:19:05 +00:00
|
|
|
"""
|
|
|
|
mainly use for not namespace sysctl.
|
|
|
|
"""
|
2020-06-29 07:51:55 +00:00
|
|
|
self.log_file.write("Global : {}\n".format(cmd))
|
|
|
|
return self.topo_builder.command_global(cmd)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 10:46:07 +00:00
|
|
|
def client_count(self):
|
|
|
|
return len(self.clients)
|
|
|
|
|
|
|
|
def get_client(self, index):
|
|
|
|
return self.clients[index]
|
|
|
|
|
|
|
|
def get_router(self, index):
|
|
|
|
return self.routers[index]
|
|
|
|
|
|
|
|
def get_server(self, index):
|
|
|
|
return self.servers[index]
|
|
|
|
|
|
|
|
def router_count(self):
|
|
|
|
return len(self.routers)
|
|
|
|
|
|
|
|
def server_count(self):
|
|
|
|
return len(self.servers)
|
|
|
|
|
|
|
|
def bottleneck_link_count(self):
|
|
|
|
return len(self.bottleneck_links)
|
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def get_host(self, who):
|
2020-06-29 07:51:55 +00:00
|
|
|
return self.topo_builder.get_host(who)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 12:13:47 +00:00
|
|
|
def get_interface_names(self, who):
|
|
|
|
return self.topo_builder.get_interface_names(who)
|
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_host(self, host):
|
|
|
|
return self.topo_builder.add_host(host)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_switch(self, switch):
|
|
|
|
return self.topo_builder.add_switch(switch)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_link(self, from_a, to_b, **kwargs):
|
|
|
|
self.topo_builder.add_link(from_a, to_b, **kwargs)
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 10:46:07 +00:00
|
|
|
def add_bottleneck_link(self, from_a, to_b, link_characteristics=None, bottleneck_link=None):
|
|
|
|
"""
|
|
|
|
If bottleneck_link is None, create a bottleneck link with parameters kwargs,
|
|
|
|
otherwise just connect it to from_a and to_b and returns the bottleneck_link
|
|
|
|
"""
|
|
|
|
if bottleneck_link is None:
|
|
|
|
bottleneck_link = BottleneckLink(self.topo_builder, self, link_characteristics)
|
|
|
|
self.bottleneck_links.append(bottleneck_link)
|
|
|
|
|
|
|
|
self.topo_builder.add_link(from_a, bottleneck_link.get_left())
|
|
|
|
self.topo_builder.add_link(bottleneck_link.get_right(), to_b)
|
|
|
|
return bottleneck_link
|
|
|
|
|
2020-06-29 14:02:17 +00:00
|
|
|
def reinit_variables(self):
|
|
|
|
# Because we create nodes before starting mininet
|
|
|
|
self.clients = [self.get_host(self.get_client_name(i)) for i in range(len(self.clients))]
|
|
|
|
self.routers = [self.get_host(self.get_router_name(i)) for i in range(len(self.routers))]
|
|
|
|
self.servers = [self.get_host(self.get_server_name(i)) for i in range(len(self.servers))]
|
|
|
|
for b in self.bottleneck_links:
|
|
|
|
b.reinit_variables()
|
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def get_cli(self):
|
2020-06-29 07:51:55 +00:00
|
|
|
self.topo_builder.get_cli()
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def start_network(self):
|
2020-06-29 07:51:55 +00:00
|
|
|
self.topo_builder.start_network()
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def close_log_file(self):
|
|
|
|
self.log_file.close()
|
2020-06-24 10:19:05 +00:00
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def stop_network(self):
|
2020-06-29 07:51:55 +00:00
|
|
|
self.topo_builder.stop_network()
|
2020-06-24 08:36:26 +00:00
|
|
|
|
|
|
|
|
2020-06-24 08:54:44 +00:00
|
|
|
class TopoConfig(object):
|
2020-06-24 10:28:44 +00:00
|
|
|
"""
|
2020-06-24 14:11:54 +00:00
|
|
|
Base class to instantiate a topology.
|
2020-06-24 10:28:44 +00:00
|
|
|
|
2020-06-24 14:11:54 +00:00
|
|
|
This class is not instantiable as it. You must define a child class with the
|
|
|
|
`NAME` attribute.
|
|
|
|
"""
|
2020-06-24 08:36:26 +00:00
|
|
|
def __init__(self, topo, param):
|
|
|
|
self.topo = topo
|
|
|
|
self.param = param
|
|
|
|
|
2020-06-25 08:53:56 +00:00
|
|
|
def configure_network(self):
|
2020-06-29 14:02:17 +00:00
|
|
|
self.topo.reinit_variables()
|
|
|
|
self.disable_tso()
|
2020-06-29 07:51:55 +00:00
|
|
|
logging.debug("Configure network in TopoConfig")
|
|
|
|
self.configure_interfaces()
|
|
|
|
self.configure_routing()
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-26 09:17:00 +00:00
|
|
|
def disable_tso(self):
|
|
|
|
"""
|
|
|
|
Disable TSO on all interfaces
|
|
|
|
"""
|
2020-07-08 12:42:32 +00:00
|
|
|
logging.info("Disable TSO on all interfaces of all nodes")
|
2020-06-29 12:26:18 +00:00
|
|
|
for node in [self.topo.get_host(n) for n in self.topo.topo_builder.net]:
|
2020-06-29 12:13:47 +00:00
|
|
|
for intf in self.topo.get_interface_names(node):
|
2020-07-08 13:02:30 +00:00
|
|
|
logging.debug("Disable TSO on interface {}".format(intf))
|
2020-06-29 10:46:07 +00:00
|
|
|
cmd = "ethtool -K {} tso off".format(intf)
|
2020-07-08 12:42:32 +00:00
|
|
|
logging.debug(cmd)
|
2020-06-29 12:13:47 +00:00
|
|
|
self.topo.command_to(node, cmd)
|
2020-06-26 09:17:00 +00:00
|
|
|
|
|
|
|
def run_netem_at(self):
|
|
|
|
"""
|
|
|
|
Prepare netem commands to be run after some delay
|
|
|
|
"""
|
2020-06-29 07:51:55 +00:00
|
|
|
if not self.topo.change_netem:
|
2020-06-26 09:17:00 +00:00
|
|
|
# Just rely on defaults of TCLink
|
2020-06-29 07:51:55 +00:00
|
|
|
logging.info("No need to change netem")
|
2020-06-26 09:17:00 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
logging.info("Will change netem config on the fly")
|
2020-06-29 10:46:07 +00:00
|
|
|
for b in self.topo.bottleneck_links:
|
|
|
|
b.configure_changing_bottleneck()
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def configure_interfaces(self):
|
|
|
|
"""
|
2020-06-29 14:02:17 +00:00
|
|
|
Function to inherit to configure the interfaces of the topology
|
2020-06-29 07:51:55 +00:00
|
|
|
"""
|
2020-06-29 12:22:39 +00:00
|
|
|
for b in self.topo.bottleneck_links:
|
|
|
|
b.configure_bottleneck()
|
2020-06-29 07:51:55 +00:00
|
|
|
|
|
|
|
def configure_routing(self):
|
|
|
|
"""
|
|
|
|
Function to override to configure the routing of the topology
|
|
|
|
"""
|
2020-06-24 08:36:26 +00:00
|
|
|
pass
|
|
|
|
|
2020-06-26 09:17:00 +00:00
|
|
|
def client_interface_count(self):
|
|
|
|
"""
|
2020-06-29 14:02:17 +00:00
|
|
|
Return the number of client's interfaces, without lo
|
2020-06-26 09:17:00 +00:00
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-07-03 13:20:20 +00:00
|
|
|
def server_interface_count(self):
|
|
|
|
"""
|
|
|
|
Return the number of server's interfaces, without lo
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-06-29 14:02:17 +00:00
|
|
|
def get_client_interface(self, client_index, interface_index):
|
2020-06-26 09:17:00 +00:00
|
|
|
"""
|
2020-06-29 14:02:17 +00:00
|
|
|
Return the interface with index `interface_index` of the client with index `client_index`
|
2020-06-26 09:17:00 +00:00
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-07-03 13:20:20 +00:00
|
|
|
def get_server_interface(self, server_index, interface_index):
|
|
|
|
"""
|
|
|
|
Return the interface with index `interface_index` of the server with index `server_index`
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def get_router_interface_to_client_switch(self, index):
|
|
|
|
"""
|
|
|
|
Return the router's interface to client's switch with index `index`
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def get_router_interface_to_server_switch(self, index):
|
2020-06-26 09:17:00 +00:00
|
|
|
"""
|
2020-07-03 13:20:20 +00:00
|
|
|
Return the router's interface to server's switch with index `index`
|
2020-06-26 09:17:00 +00:00
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def interface_backup_command(self, interface_name):
|
|
|
|
return "ip link set dev {} multipath backup ".format(
|
|
|
|
interface_name)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def interface_up_command(self, interface_name, ip, subnet):
|
|
|
|
return "ifconfig {} {} netmask {}".format(interface_name, ip, subnet)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_table_route_command(self, from_ip, id):
|
|
|
|
return "ip rule add from {} table {}".format(from_ip, id + 1)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_link_scope_route_command(self, network, interface_name, id):
|
|
|
|
return "ip route add {} dev {} scope link table {}".format(
|
|
|
|
network, interface_name, id + 1)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_table_default_route_command(self, via, id):
|
|
|
|
return "ip route add default via {} table {}".format(via, id + 1)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_global_default_route_command(self, via, interface_name):
|
|
|
|
return "ip route add default scope global nexthop via {} dev {}".format(via, interface_name)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def arp_command(self, ip, mac):
|
|
|
|
return "arp -s {} {}".format(ip, mac)
|
2020-06-24 08:36:26 +00:00
|
|
|
|
2020-06-29 07:51:55 +00:00
|
|
|
def add_simple_default_route_command(self, via):
|
|
|
|
return "ip route add default via {}".format(via)
|