diff --git a/config/topo/topo_1 b/config/topo/topo_1 index a56ea41..4d87e30 100644 --- a/config/topo/topo_1 +++ b/config/topo/topo_1 @@ -1,5 +1,5 @@ leftSubnet:10.0. rightSubnet:10.1. -path_0:10,10,4 -path_1:40,30,4 +path_c2r_0:10,10,4 +path_c2r_1:40,30,4 topoType:MultiIf diff --git a/config/topo/topo_2 b/config/topo/topo_2 index 1e5f37d..e2c79ae 100644 --- a/config/topo/topo_2 +++ b/config/topo/topo_2 @@ -1,5 +1,6 @@ leftSubnet:10.0. rightSubnet:10.1. -path_0:100,20,4 -path_1:1,20,4 +path_c2r_0:100,20,4 +path_c2r_1:1,20,4 +path_r2s_0:10,20,10 topoType:MultiIf diff --git a/config/topo/topo_3 b/config/topo/topo_3 new file mode 100644 index 0000000..9335d7e --- /dev/null +++ b/config/topo/topo_3 @@ -0,0 +1,5 @@ +leftSubnet:10.0. +rightSubnet:10.1. +path_c2r_0:100,20,4 +path_r2s_0:10,20,10 +topoType:MultiIf diff --git a/config/topo/topo_4 b/config/topo/topo_4 new file mode 100644 index 0000000..8c7cb9c --- /dev/null +++ b/config/topo/topo_4 @@ -0,0 +1,8 @@ +leftSubnet:10.0. +rightSubnet:10.1. +path_c2r_0:100,20,4 +path_c2r_1:100,20,4 +path_c2r_2:100,20,4 +path_r2s_0:10,20,10 +path_r2s_1:10,20,10 +topoType:MultiIf diff --git a/config/topo/topo_5 b/config/topo/topo_5 new file mode 100644 index 0000000..7951be8 --- /dev/null +++ b/config/topo/topo_5 @@ -0,0 +1,8 @@ +leftSubnet:10.0. +rightSubnet:10.1. +path_c2r_0:100,20,4 +path_c2r_1:100,20,4 +path_r2s_0:10,20,10 +path_r2s_1:10,20,10 +path_r2s_2:10,20,10 +topoType:MultiIf diff --git a/core/experiment.py b/core/experiment.py index b5ee251..0b9ce40 100644 --- a/core/experiment.py +++ b/core/experiment.py @@ -179,24 +179,24 @@ class Experiment(object): 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_switch(0), priority_path_0)) + Experiment.IP_BIN, self.topo_config.get_router_interface_to_client_switch(0), priority_path_0)) 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_switch(1), priority_path_1)) + Experiment.IP_BIN, self.topo_config.get_router_interface_to_client_switch(1), priority_path_1)) backup_path_0 = self.experiment_parameter.get(ExperimentParameter.BACKUP_PATH_0) if int(backup_path_0) > 0: self.topo.command_to(self.topo_config.client, self.topo_config.interface_backup_command(self.topo_config.get_client_interface(0))) self.topo.command_to(self.topo_config.router, - self.topo_config.interface_backup_command(self.topo_config.get_router_interface_to_switch(0))) + 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) if int(backup_path_1) > 0: self.topo.command_to(self.topo_config.client, self.topo_config.interface_backup_command(self.topo_config.get_client_interface(1))) self.topo.command_to(self.topo_config.router, - self.topo_config.interface_backup_command(self.topo_config.get_router_interface_to_switch(1))) + self.topo_config.interface_backup_command(self.topo_config.get_router_interface_to_client_switch(1))) def run_userspace_path_manager(self): """ @@ -364,7 +364,7 @@ class Experiment(object): count = self.experiment_parameter.get(ExperimentParameter.PING_COUNT) for i in range(0, self.topo_config.client_interface_count()): cmd = self.ping_command(self.topo_config.get_client_ip(i), - self.topo_config.get_server_ip(), n=count) + self.topo_config.get_server_ip(interface_index=0), n=count) logging.info(cmd) self.topo.command_to(self.topo_config.client, cmd) diff --git a/core/topo.py b/core/topo.py index a0393d1..6dc883d 100644 --- a/core/topo.py +++ b/core/topo.py @@ -17,12 +17,22 @@ class NetemAt(object): return "netem at {} ({}) will be {}".format(self.at, self.delta, self.cmd) +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)) + + class LinkCharacteristics(object): """ Network characteristics associated to a link Attributes: id the identifier of the link + link_type type of the link 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 @@ -31,8 +41,9 @@ class LinkCharacteristics(object): 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) """ - def __init__(self, id, delay, queue_size, bandwidth, loss, backup=False): + def __init__(self, id, link_type, delay, queue_size, bandwidth, loss, backup=0): self.id = id + self.link_type = link_type self.delay = delay self.queue_size = queue_size self.bandwidth = bandwidth @@ -45,10 +56,7 @@ class LinkCharacteristics(object): """ Get the bandwidth-delay product in terms of packets (hence, dividing by the MTU) """ - rtt = 2 * float(self.delay) - """ Since bandwidth is in Mbps and rtt in ms """ - bandwidth_delay_product = (float(self.bandwidth) * 125000.0) * (rtt / 1000.0) - return int(math.ceil(bandwidth_delay_product * 1.0 / 1500.0)) + return get_bandwidth_delay_product_divided_by_mtu(self.delay, self.bandwidth) def buffer_size(self): """ @@ -116,6 +124,8 @@ class LinkCharacteristics(object): Notably used by BottleneckLink """ return { + "link_id": self.id, + "link_type": self.link_type, "bw": float(self.bandwidth), "delay": "{}ms".format(self.delay), "loss": float(self.loss), @@ -124,13 +134,14 @@ class LinkCharacteristics(object): def __str__(self): return """ +Link type: {} Link id: {} Delay: {} Queue Size: {} Bandwidth: {} Loss: {} Backup: {} - """.format(self.id, self.delay, self.queue_size, self.bandwidth, self.loss, self.backup) + \ + """.format(self.link_type, self.id, self.delay, self.queue_size, self.bandwidth, self.loss, self.backup) + \ "".join(["\t {} \n".format(n) for n in self.netem_at]) @@ -139,13 +150,11 @@ class TopoParameter(Parameter): RIGHT_SUBNET = "rightSubnet" NETEM_AT = "netem_at_" CHANGE_NETEM = "changeNetem" - SERVER_PATHS = "serverPaths" DEFAULT_PARAMETERS = { LEFT_SUBNET: "10.1.", RIGHT_SUBNET: "10.2.", CHANGE_NETEM: "false", - SERVER_PATHS: "1", } def __init__(self, parameter_filename): @@ -184,34 +193,63 @@ class TopoParameter(Parameter): logging.info(self.link_characteristics[link_id].netem_at) + 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)) + def load_link_characteristics(self): """ - CAUTION: the path_i in config file is not taken into account. Hence place them in - increasing order in the topo parameter file! + Load the path characteristics """ - i = 0 for k in sorted(self.parameters): - # TODO FIXME rewrite this function if k.startswith("path"): - tab = self.parameters[k].split(",") - bup = False - loss = "0.0" - if len(tab) == 5: - loss = tab[3] - bup = tab[4].lower() == 'true' - if len(tab) == 4: - try: - loss = float(tab[3]) - loss = tab[3] - except ValueError: - bup = tab[3].lower() == 'true' - if len(tab) == 3 or len(tab) == 4 or len(tab) == 5: - path = LinkCharacteristics(i, tab[0], - tab[1], tab[2], loss, bup) - self.link_characteristics.append(path) - i = i + 1 + 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)) else: - logging.warning("Ignored path {}".format(self.parameters[k])) + path = LinkCharacteristics(link_id, link_type, delay, bw, + queue_size, loss_perc, backup=is_backup) + self.link_characteristics.append(path) def __str__(self): s = "{}".format(super(TopoParameter, self).__str__()) @@ -248,7 +286,8 @@ class BottleneckLink(object): topo_builder.add_link(self.bs2, self.bs3) def get_bs_name(self, index): - return "{}_{}_{}".format(BottleneckLink.BOTTLENECK_SWITCH_NAME_PREFIX, self.link_characteristics.id, index) + return "{}_{}_{}_{}".format(BottleneckLink.BOTTLENECK_SWITCH_NAME_PREFIX, + self.link_characteristics.link_type, self.link_characteristics.id, index) def reinit_variables(self): # Required to retrieve actual nodes @@ -534,15 +573,33 @@ class TopoConfig(object): """ raise NotImplementedError() + def server_interface_count(self): + """ + Return the number of server's interfaces, without lo + """ + raise NotImplementedError() + def get_client_interface(self, client_index, interface_index): """ Return the interface with index `interface_index` of the client with index `client_index` """ raise NotImplementedError() - def get_router_interface_to_switch(self, index): + def get_server_interface(self, server_index, interface_index): """ - Return the router's interface to switch with index `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): + """ + Return the router's interface to server's switch with index `index` """ raise NotImplementedError() diff --git a/topos/multi_interface.py b/topos/multi_interface.py index bab39b9..b367686 100644 --- a/topos/multi_interface.py +++ b/topos/multi_interface.py @@ -11,41 +11,86 @@ class MultiInterfaceTopo(Topo): self.client = self.add_client() self.server = self.add_server() self.router = self.add_router() - self.client_switches = [] - self.server_switches = [] - for l in self.topo_parameter.link_characteristics: - self.client_switches.append(self.add_client_side_switch(l)) - self.add_link(self.client,self.client_switches[-1]) - self.server_switches.append(self.add_router_side_switch(l)) - self.add_bottleneck_link(self.client_switches[-1], self.server_switches[-1], link_characteristics=l) - self.add_link(self.server_switches[-1],self.router) + self.c2r_client_switches = [] + self.c2r_router_switches = [] + self.r2s_router_switches = [] + self.r2s_server_switches = [] - for i in range(int(self.topo_parameter.get(TopoParameter.SERVER_PATHS))): + # Add client - router links + for l in self.get_client_to_router_links(): + self.c2r_client_switches.append(self.add_c2r_client_side_switch(l)) + self.add_link(self.client, self.c2r_client_switches[-1]) + self.c2r_router_switches.append(self.add_c2r_router_side_switch(l)) + self.add_bottleneck_link(self.c2r_client_switches[-1], self.c2r_router_switches[-1], link_characteristics=l) + self.add_link(self.c2r_router_switches[-1], self.router) + + # Special case: if there is no specified link between router and server, directly connect them! + if len(self.get_router_to_server_links()) > 0: + for l in self.get_router_to_server_links(): + self.r2s_router_switches.append(self.add_r2s_router_side_switch(l)) + self.add_link(self.router, self.r2s_router_switches[-1]) + self.r2s_server_switches.append(self.add_r2s_server_side_switch(l)) + self.add_bottleneck_link(self.r2s_router_switches[-1], self.r2s_server_switches[-1], link_characteristics=l) + self.add_link(self.r2s_server_switches[-1], self.server) + else: self.add_link(self.router, self.server) + - def add_client_side_switch(self, link): - return self.add_switch("{}{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id)) + def get_client_to_router_links(self): + return [l for l in self.topo_parameter.link_characteristics if l.link_type == "c2r"] - def add_router_side_switch(self, link): - return self.add_switch("{}{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id + 1)) + def get_router_to_server_links(self): + return [l for l in self.topo_parameter.link_characteristics if l.link_type == "r2s"] + + def add_c2r_client_side_switch(self, link): + return self.add_switch("{}c2r{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id)) + + def add_c2r_router_side_switch(self, link): + return self.add_switch("{}c2r{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id + 1)) + + def add_r2s_router_side_switch(self, link): + return self.add_switch("{}r2s{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id)) + + def add_r2s_server_side_switch(self, link): + return self.add_switch("{}r2s{}".format(MultiInterfaceTopo.SWITCH_NAME_PREFIX, 2 * link.id + 1)) def __str__(self): - s = "Simple multiple interface topolgy \n" + s = "Simple multiple interface topology \n" i = 0 - n = len(self.topo_parameter.link_characteristics) - for p in self.topo_parameter.link_characteristics: - if i == n // 2: - if n % 2 == 0: - s = s + "c r-----s\n" - s = s + "|--sw----sw--|\n" + nc = len(self.get_client_to_router_links()) + ns = len(self.get_router_to_server_links()) + m = max(nc, ns) + skipped = 0 + for i in range(0, m): + if i == m // 2: + if m % 2 == 0: + s = s + "c r--sw---bl---sw--s\n" + s = s + " \-sw---bl---sw-/\n" else: - s = s + "c--sw----sw--r-----s\n" + s = s + "c--sw---bl---sw--r--sw---bl---sw--s\n" else: - s = s + "|--sw----sw--|\n" - - i = i + 1 + if i < m // 2: + if (nc == m and ns + skipped == m) or (ns == m and nc + skipped == m): + s = s + " /-sw---bl---sw-\ /-sw---bl---sw-\ \n" + elif nc == m: + s = s + " /-sw---bl---sw-\ \n" + skipped += 1 + else: + s = s + " /-sw---bl---sw-\ \n" + skipped += 1 + else: + if (nc == m and ns + skipped == m) or (ns == m and nc + skipped == m): + s = s + " \-sw---bl---sw-/ \-sw---bl---sw-/ \n" + elif nc == m: + s = s + " \-sw---bl---sw-/ \n" + skipped += 1 + else: + s = s + " \-sw---bl---sw-/ \n" + skipped += 1 + return s + class MultiInterfaceConfig(TopoConfig): NAME = "MultiIf" @@ -53,7 +98,7 @@ class MultiInterfaceConfig(TopoConfig): super(MultiInterfaceConfig, self).__init__(topo, param) def configure_routing(self): - for i, l in enumerate(self.topo.client_switches): + for i, l in enumerate(self.topo.c2r_client_switches): cmd = self.add_table_route_command(self.get_client_ip(i), i) self.topo.command_to(self.client, cmd) @@ -66,7 +111,7 @@ class MultiInterfaceConfig(TopoConfig): i) self.topo.command_to(self.client, cmd) - for i in range(int(self.topo.topo_parameter.get(TopoParameter.SERVER_PATHS))): + for i, l in enumerate(self.topo.r2s_server_switches): cmd = self.add_table_route_command(self.get_server_ip(i), i) self.topo.command_to(self.server, cmd) @@ -96,7 +141,7 @@ class MultiInterfaceConfig(TopoConfig): netmask = "255.255.255.0" links = self.topo.get_link_characteristics() - for i, l in enumerate(self.topo.client_switches): + for i, l in enumerate(self.topo.c2r_client_switches): cmd = self.interface_up_command(self.get_client_interface(0, i), self.get_client_ip(i), netmask) self.topo.command_to(self.client, cmd) client_interface_mac = self.client.intf(self.get_client_interface(0, i)).MAC() @@ -106,23 +151,38 @@ class MultiInterfaceConfig(TopoConfig): cmd = self.interface_backup_command(self.get_client_interface(0, i)) self.topo.command_to(self.client, cmd) - for i, l in enumerate(self.topo.server_switches): - cmd = self.interface_up_command(self.get_router_interface_to_switch(i), + for i, l in enumerate(self.topo.c2r_router_switches): + cmd = self.interface_up_command(self.get_router_interface_to_client_switch(i), self.get_router_ip_to_client_switch(i), netmask) self.topo.command_to(self.router, cmd) - router_interface_mac = self.router.intf(self.get_router_interface_to_switch(i)).MAC() + router_interface_mac = self.router.intf(self.get_router_interface_to_client_switch(i)).MAC() self.topo.command_to(self.client, "arp -s {} {}".format( self.get_router_ip_to_client_switch(i), router_interface_mac)) - for i in range(int(self.topo.topo_parameter.get(TopoParameter.SERVER_PATHS))): - cmd = self.interface_up_command(self.get_router_interface_to_server(i), + if len(self.topo.r2s_router_switches) == 0: + # Case no server param is specified + cmd = self.interface_up_command(self.get_router_interface_to_server_switch(0), + self.get_router_ip_to_server_switch(0), netmask) + self.topo.command_to(self.router, cmd) + router_interface_mac = self.router.intf(self.get_router_interface_to_server_switch(0)).MAC() + self.topo.command_to(self.server, "arp -s {} {}".format( + self.get_router_ip_to_server_switch(0), router_interface_mac)) + + cmd = self.interface_up_command(self.get_server_interface(0, 0), self.get_server_ip(0), netmask) + self.topo.command_to(self.server, cmd) + server_interface_mac = self.server.intf(self.get_server_interface(0, 0)).MAC() + self.topo.command_to(self.router, "arp -s {} {}".format( + self.get_server_ip(0), server_interface_mac)) + + for i, l in enumerate(self.topo.r2s_router_switches): + cmd = self.interface_up_command(self.get_router_interface_to_server_switch(i), self.get_router_ip_to_server_switch(i), netmask) self.topo.command_to(self.router, cmd) - router_interface_mac = self.router.intf(self.get_router_interface_to_server(i)).MAC() + router_interface_mac = self.router.intf(self.get_router_interface_to_server_switch(i)).MAC() self.topo.command_to(self.server, "arp -s {} {}".format( self.get_router_ip_to_server_switch(i), router_interface_mac)) - for i in range(int(self.topo.topo_parameter.get(TopoParameter.SERVER_PATHS))): + for i, l in enumerate(self.topo.r2s_server_switches): cmd = self.interface_up_command(self.get_server_interface(0, i), self.get_server_ip(i), netmask) self.topo.command_to(self.server, cmd) server_interface_mac = self.server.intf(self.get_server_interface(0, i)).MAC() @@ -148,15 +208,18 @@ class MultiInterfaceConfig(TopoConfig): return "{}{}.0/24".format(self.param.get(TopoParameter.RIGHT_SUBNET), interface_index) def client_interface_count(self): - return len(self.topo.client_switches) + return len(self.topo.c2r_client_switches) - def get_router_interface_to_server(self, switch_index): - return self.get_router_interface_to_switch(len(self.topo.server_switches) + switch_index) + def server_interface_count(self): + return len(self.topo.r2s_server_switches) + + def get_router_interface_to_server_switch(self, switch_index): + return self.get_router_interface_to_client_switch(len(self.topo.c2r_router_switches) + switch_index) def get_client_interface(self, client_index, interface_index): return "{}-eth{}".format(self.topo.get_client_name(client_index), interface_index) - def get_router_interface_to_switch(self, interface_index): + def get_router_interface_to_client_switch(self, interface_index): return "{}-eth{}".format(self.topo.get_router_name(0), interface_index) def get_server_interface(self, server_index, interface_index): diff --git a/topos/multi_interface_cong.py b/topos/multi_interface_cong.py index 79073e1..79bcc5b 100644 --- a/topos/multi_interface_cong.py +++ b/topos/multi_interface_cong.py @@ -158,10 +158,10 @@ class MultiInterfaceCongConfig(TopoConfig): self.topo.command_to(self.router, "arp -s " + self.getCongClientIP(i) + " " + congClientIntfMac) cmd = self.interface_up_command( - self.get_router_interface_to_switch(i), + self.get_router_interface_to_client_switch(i), self.getRouterIPSwitch(i), netmask) self.topo.command_to(self.router, cmd) - routerIntfMac = self.router.intf(self.get_router_interface_to_switch(i)).MAC() + routerIntfMac = self.router.intf(self.get_router_interface_to_client_switch(i)).MAC() self.topo.command_to(self.client, "arp -s " + self.getRouterIPSwitch(i) + " " + routerIntfMac) # Don't forget the congestion client self.topo.command_to(self.cong_clients[i], "arp -s " + self.getRouterIPSwitch(i) + " " + routerIntfMac) @@ -240,10 +240,10 @@ class MultiInterfaceCongConfig(TopoConfig): return len(self.topo.switch) def get_router_interface_to_server(self): - return self.get_router_interface_to_switch(len(self.topo.switch)) + return self.get_router_interface_to_client_switch(len(self.topo.switch)) def getRouterInterfaceCongServer(self, congID): - return self.get_router_interface_to_switch(len(self.topo.switch) + 1 + congID) + return self.get_router_interface_to_client_switch(len(self.topo.switch) + 1 + congID) def get_client_interface(self, interfaceID): return Topo.CLIENT_NAME + "-eth" + str(interfaceID) @@ -251,7 +251,7 @@ class MultiInterfaceCongConfig(TopoConfig): def getCongClientInterface(self, interfaceID): return MultiInterfaceCongConfig.congClientName + str(interfaceID) + "-eth0" - def get_router_interface_to_switch(self, interfaceID): + def get_router_interface_to_client_switch(self, interfaceID): return Topo.ROUTER_NAME + "-eth" + str(interfaceID) def get_server_interface(self):