You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
722 lines
30 KiB
722 lines
30 KiB
import argparse
|
|
from mininet.net import Mininet
|
|
from mininet.log import setLogLevel, info
|
|
from mininet.cli import CLI
|
|
import topologies
|
|
import inspect
|
|
import importlib
|
|
import pkgutil
|
|
from time import sleep
|
|
from mininet.link import TCLink
|
|
from mininet.node import CPULimitedHost
|
|
from datetime import datetime
|
|
|
|
import threading, time, traceback
|
|
from nftables import Nftables
|
|
from nftables import json
|
|
import os
|
|
|
|
topos = {
|
|
"Minimal": {
|
|
"class": "Minimal",
|
|
"module": "minimum",
|
|
"description": "2 hosts connected to a switch"
|
|
},
|
|
"4R4H": {
|
|
"class": "FourRoutersFourHosts",
|
|
"module": "4r4h_topo",
|
|
"description": "A topology using 4 routers and 3 hosts"
|
|
},
|
|
"6R4H": {
|
|
"class": "SixRoutersFourHosts",
|
|
"module": "6r4h_topo",
|
|
"description": "A topology using 6 routers and 3 hosts"
|
|
},
|
|
"8R4H": {
|
|
"class": "EightRoutersFourHosts",
|
|
"module": "8r4h_topo",
|
|
"description": "A topology using 8 routers and 3 hosts"
|
|
}
|
|
}
|
|
|
|
functions = {
|
|
"connection_shutdown": {
|
|
"callable": lambda net, triple: connection_shutdown(net, triple[0], triple[1], triple[2])
|
|
},
|
|
"measure_bandwidth": {
|
|
"callable": lambda net, nonet: measure_bandwidth(net, nonet[0], nonet[1], nonet[2], nonet[3], nonet[4],
|
|
nonet[5], nonet[6], nonet[7], nonet[8])
|
|
},
|
|
"measure_latency": {
|
|
"callable": lambda net, septet: measure_latency(net, septet[0], septet[1], septet[2], septet[3], septet[4],
|
|
septet[5], septet[6])
|
|
},
|
|
"measure_packet_flow": {
|
|
"callable": lambda net, elevenet: measure_packet_flow(net, elevenet[0], elevenet[1], elevenet[2], elevenet[3],
|
|
elevenet[4], elevenet[5], elevenet[6], elevenet[7],
|
|
elevenet[8], elevenet[9], elevenet[10])
|
|
},
|
|
"measure_link_usage_bandwidth": {
|
|
"callable": lambda net, nonet: measure_link_usage_bandwidth(net, nonet[0], nonet[1], nonet[2], nonet[3], nonet[4],
|
|
nonet[5], nonet[6], nonet[7], nonet[8])
|
|
}
|
|
}
|
|
|
|
use_shortcut = False
|
|
# Stores information about already created chains and tables on each router. If a router is in this list, it means the chain and table for nftables were already created
|
|
shortcut_nftables_memory = []
|
|
packet_counter_memory = []
|
|
packet_counter_initialized = False
|
|
|
|
|
|
def get_tests(topo):
|
|
available_tests = []
|
|
topology = get_topo_by_name(topo)
|
|
if topology is None:
|
|
print(f"Unknown topology {topo}, exiting...")
|
|
exit()
|
|
for test, test_data in topology.get_tests().items():
|
|
available_tests.append(test)
|
|
print('Currently available tests are: ' + str(available_tests))
|
|
|
|
|
|
def implement_shortcut(net, router, interface, dest_ip, gateway, queue_id):
|
|
# implement hook
|
|
info(f"Deploying ShortCut for {interface} to {dest_ip} in Queue {queue_id}\n")
|
|
shortcut_log_file = "/tmp/shortcut.log"
|
|
if router not in shortcut_nftables_memory:
|
|
# creating nftables table and chains as these are not supplied by default
|
|
net[router].cmd(f"nft add table ip filter >> {shortcut_log_file}")
|
|
net[router].cmd(
|
|
f"nft 'add chain ip filter INPUT {{ type filter hook input priority 0; policy accept; }}' >> {shortcut_log_file}")
|
|
net[router].cmd(
|
|
f"nft 'add chain ip filter FORWARD {{ type filter hook forward priority 0; policy accept; }}' >> {shortcut_log_file}")
|
|
net[router].cmd(
|
|
f"nft 'add chain ip filter OUTPUT {{ type filter hook output priority 0; policy accept; }}' >> {shortcut_log_file}")
|
|
shortcut_nftables_memory.append(router)
|
|
net[router].cmd(
|
|
f"nft add rule ip filter FORWARD iifname '{interface}' ip daddr {dest_ip} counter queue num {queue_id} >> {shortcut_log_file}")
|
|
# execute listener
|
|
net[router].cmd(f"sudo python3 shortcut_listener.py -g {gateway} -id {queue_id} -s 24 &> listener.log &")
|
|
|
|
|
|
def implement_packet_counter(net, device, protocol):
|
|
available_protocols = ["tcp", "udp"]
|
|
if protocol.lower() not in available_protocols:
|
|
info(
|
|
f"The protocol {protocol} is not an available protocol. Available protocols for packet counters are: {str(available_protocols)}")
|
|
return
|
|
table_name = f"counter_{protocol.lower()}"
|
|
counter_name = f"{protocol.lower()}_{device}_counter"
|
|
net[device].cmd(f"nft add table ip {table_name}")
|
|
net[device].cmd(
|
|
f"nft 'add chain ip {table_name} FORWARD {{ type filter hook forward priority filter; policy accept; }}'")
|
|
net[device].cmd(f"nft add counter {table_name} {counter_name}")
|
|
net[device].cmd(f"nft 'add rule {table_name} FORWARD {protocol.lower()} dport 0-65535 counter name {counter_name}'")
|
|
|
|
memory_entry = {
|
|
"device": device,
|
|
"protocol": protocol.lower(),
|
|
"counter": counter_name,
|
|
"table": table_name
|
|
}
|
|
packet_counter_memory.append(memory_entry)
|
|
|
|
|
|
def reset_packet_counters(net):
|
|
for memory_entry in packet_counter_memory:
|
|
net[memory_entry["device"]].cmd(f"nft reset counter ip {memory_entry['table']} {memory_entry['counter']}")
|
|
|
|
|
|
def start_packet_counter_timers(net, interval, length, flag, test_prefix, test_type, timestamp):
|
|
for memory_entry in packet_counter_memory:
|
|
threading.Thread(
|
|
target=lambda: read_packet_counter(interval, length, net, memory_entry["device"], memory_entry["table"],
|
|
memory_entry["counter"], flag, test_prefix, test_type,
|
|
timestamp)).start()
|
|
|
|
|
|
def get_packet_counter_log_file_name(device, table, flag):
|
|
return f"{device}_packet_{table}_{flag}.out"
|
|
|
|
|
|
def read_packet_counter(delay, length, net, device, table, counter, flag, test_prefix, test_type, timestamp):
|
|
info(f"\nStarted packet counter thread for {device}")
|
|
executions = int(length / delay)
|
|
execution_counter = 0
|
|
current_time = 0
|
|
running = True
|
|
initial_run = True
|
|
next_time = int(time.time())
|
|
last_value = 0
|
|
log_file_folder = get_test_folder_path(test_prefix, test_type, timestamp)
|
|
log_file = f"{log_file_folder}/{get_packet_counter_log_file_name(device, table, flag)}"
|
|
|
|
if os.path.exists(log_file):
|
|
os.remove(log_file)
|
|
log_file_object = open(log_file, "a")
|
|
while running:
|
|
if initial_run:
|
|
initial_run = False
|
|
else:
|
|
time.sleep(max(0, next_time - int(time.time())))
|
|
|
|
try:
|
|
# nft works in network namespaces, therefore each device holds its own nftables configuration
|
|
|
|
packet_counter_output = net[device].cmd(f"nft list counter ip {table} {counter}")
|
|
if packet_counter_output is not None:
|
|
packet_counter_output_lines = packet_counter_output.split("\n")
|
|
# parsing the received output from nftables
|
|
relevant_output = packet_counter_output_lines[2][
|
|
packet_counter_output_lines[2].find("packets") + 7:packet_counter_output_lines[
|
|
2].find("bytes")].strip()
|
|
|
|
current_time = round(current_time + delay, 1)
|
|
log_output = int(relevant_output) - last_value
|
|
last_value = int(relevant_output)
|
|
log_file_object.write(f"{current_time - delay} {str(log_output)}\n")
|
|
|
|
if execution_counter >= executions:
|
|
running = False
|
|
else:
|
|
execution_counter += 1
|
|
except Exception:
|
|
traceback.print_exc()
|
|
# in production code you might want to have this instead of course:
|
|
# logger.exception("Problem while executing repetitive task.")
|
|
# skip tasks if we are behind schedule:
|
|
next_time = next_time + delay
|
|
|
|
log_file_object.close()
|
|
info(f"\nPacket counter thread for {device} finished executing after {execution_counter}/{executions} executions")
|
|
|
|
|
|
def connection_shutdown(net, connection, names, interfaces):
|
|
counter = 0
|
|
info(f"\nCutting connection between {names[0]} and {names[1]}\n")
|
|
for component in connection:
|
|
name = names[counter]
|
|
interface = interfaces[counter]
|
|
interface_shutdown(net[component], name, interface)
|
|
counter += 1
|
|
|
|
|
|
def number_check(pseudo_number):
|
|
return type(pseudo_number) == float or type(pseudo_number) == int
|
|
|
|
|
|
def create_plot_command(graph_title, file_name, yrange, xlabel="", ylabel=""):
|
|
title_string = ""
|
|
if graph_title != "":
|
|
title_string = f"title \'{graph_title}\' "
|
|
graph_file_name = f"{file_name}_graph.eps"
|
|
info(f"\nCreating plot graphic {graph_file_name}...")
|
|
if yrange[1] < 0:
|
|
calc_yrange = get_graph_yrange(file_name + "_parsed.out", 0)
|
|
yrange[1] = calc_yrange[1]
|
|
|
|
return f'gnuplot -e "set term eps;set yrange [{yrange[0]}:{yrange[1]}]; set ylabel \'{ylabel}\' ; set xlabel \'{xlabel}\' ; set output \'{graph_file_name}\'; plot \'{file_name}_parsed.out\' {title_string}w linespoints lc rgb \'black\'"'
|
|
|
|
|
|
def map_color(number_of_colors):
|
|
distinguishable_greys = [0, 37, 82, 115, 150]
|
|
if number_of_colors == 1:
|
|
return [0]
|
|
elif number_of_colors == 2:
|
|
return [0, 150]
|
|
elif number_of_colors == 3:
|
|
return [0, 82, 150]
|
|
elif number_of_colors == 4:
|
|
return [0, 37, 115, 150]
|
|
elif number_of_colors == 5:
|
|
return distinguishable_greys
|
|
|
|
|
|
def grey_to_hex(grey_value_list):
|
|
hex_value_list = []
|
|
for grey_value in grey_value_list:
|
|
hex_value = '{:02x}'.format(int(grey_value))
|
|
hex_value_list.append("#" + hex_value * 3)
|
|
return hex_value_list
|
|
|
|
|
|
def get_test_folder_path(prefix, test_type, timestamp):
|
|
return f"/tmp/{prefix}_{test_type}_{timestamp}"
|
|
|
|
|
|
def create_multi_plot_command(graph_title, file_name, yrange, data_file_names, xlabel="", ylabel=""):
|
|
title_string = ""
|
|
if graph_title != "":
|
|
title_string = f"title \'{graph_title}\' "
|
|
|
|
if len(data_file_names) > 5:
|
|
info("\nThis function can only create up to 5 plots in one graph, you provided " + str(
|
|
len(data_file_names)) + " file sources")
|
|
return ""
|
|
|
|
graph_file_name = f"{file_name}_graph.eps"
|
|
info(f"\nCreating plot graphic {graph_file_name}...")
|
|
if yrange[1] < 0:
|
|
calc_yrange = get_graph_yrange(file_name + "_parsed.out", 0)
|
|
yrange[1] = calc_yrange[1]
|
|
|
|
plot_command = "plot "
|
|
plot_counter = 0
|
|
|
|
graph_colors = grey_to_hex(map_color(len(data_file_names)))
|
|
|
|
for label, data_file_name in data_file_names.items():
|
|
plot_command += f"'{data_file_name}' using 1:2 title '{label}' lc rgb '{graph_colors[plot_counter]}' dt {plot_counter + 1} w linespoints,"
|
|
plot_counter += 1
|
|
|
|
plot_command = plot_command[:-1]
|
|
gnuplot_command = f'gnuplot -e "set term eps;set yrange [{yrange[0]}:{yrange[1]}]; set ylabel \'{ylabel}\' ; set xlabel \'{xlabel}\' ; set output \'{graph_file_name}\'; show style line; {plot_command}"'
|
|
info("\nGnuplot command: " + gnuplot_command)
|
|
return gnuplot_command
|
|
|
|
|
|
def measure_packet_flow(net, client, server, server_ip, flow_measurement_targets, length, interval, unique_test_name,
|
|
y_range, graph_title, flag="tcp", bandwidth=100):
|
|
global packet_counter_initialized
|
|
|
|
if not packet_counter_initialized:
|
|
info("\nImplementing nftables packet counters")
|
|
for target in flow_measurement_targets:
|
|
implement_packet_counter(net, target, flag)
|
|
packet_counter_initialized = True
|
|
else:
|
|
info("\nPacket counters already initialized, resetting counters")
|
|
reset_packet_counters(net)
|
|
|
|
# make sure everything is initialized
|
|
sleep(1)
|
|
|
|
client_type_string = ""
|
|
if flag == "udp":
|
|
client_type_string = f"-u"
|
|
|
|
shortcut_flag = "wo_sc"
|
|
if use_shortcut:
|
|
shortcut_flag = "sc"
|
|
test_prefix = f"{client}_to_{server}"
|
|
test_type = "packet_flow"
|
|
timestamp = datetime.now().strftime("%d%m%Y_%H%M%S")
|
|
tmp_file_folder = get_test_folder_path(test_prefix, test_type, timestamp)
|
|
if not os.path.exists(tmp_file_folder):
|
|
info(f"\nCreated folder for new test under {tmp_file_folder}")
|
|
os.mkdir(tmp_file_folder)
|
|
tmp_file_name = f"{tmp_file_folder}/{unique_test_name}_{shortcut_flag}"
|
|
iperf_server_command = get_iperf_server_command(interval, tmp_file_name)
|
|
net[server].cmd(iperf_server_command)
|
|
sleep(1)
|
|
net[client].cmd(
|
|
f"iperf3 -c {server_ip} -b 0 -f m -i {interval} -t {length+5} {client_type_string} > {tmp_file_name}_client.out &")
|
|
sleep(2)
|
|
reset_packet_counters(net)
|
|
start_packet_counter_timers(net, interval, length, unique_test_name, test_prefix, test_type, timestamp)
|
|
|
|
sleep(length + 4)
|
|
# make sure packet counters ran out
|
|
|
|
info("\nStopping iPerf server...")
|
|
net[server].cmd("pkill iperf3")
|
|
|
|
info("\nCollecting information...")
|
|
|
|
number_of_lines = int(length / interval)
|
|
|
|
file_names = {}
|
|
for memory_entry in packet_counter_memory:
|
|
file_names[memory_entry[
|
|
"device"]] = f"{tmp_file_folder}/{get_packet_counter_log_file_name(memory_entry['device'], memory_entry['table'], unique_test_name)}"
|
|
|
|
multiplot_command = create_multi_plot_command(graph_title, tmp_file_name, y_range, file_names, "Time in seconds",
|
|
"Packets received")
|
|
if multiplot_command is not None:
|
|
net[server].cmd(multiplot_command)
|
|
|
|
|
|
def get_graph_yrange(parsed_file_name, y_offset):
|
|
parsed_file = open(parsed_file_name)
|
|
parsed_file_lines = parsed_file.readlines()
|
|
y_max = None
|
|
y_min = None
|
|
|
|
for line in parsed_file_lines:
|
|
values = line.split()
|
|
if len(values) != 2:
|
|
pass
|
|
else:
|
|
if y_max is None or int(values[1]) > y_max:
|
|
y_max = int(values[1])
|
|
if y_min is None or int(values[1]) < y_min:
|
|
y_min = int(values[1])
|
|
|
|
y_min -= y_offset
|
|
y_max += y_offset
|
|
|
|
return [y_min, y_max]
|
|
|
|
|
|
def measure_latency(net, sender, dest_ip, length, interval, unique_test_name, y_range, graph_title=""):
|
|
shortcut_flag = "wo_sc"
|
|
if use_shortcut:
|
|
shortcut_flag = "sc"
|
|
tmp_file_name = f"/tmp/{sender}_to_{dest_ip}_ping_{unique_test_name}_{shortcut_flag}"
|
|
packet_count = int(length / interval)
|
|
ping_command = f"ping -c {packet_count} -i {interval} {dest_ip} > {tmp_file_name}.out &"
|
|
net[sender].cmd(ping_command)
|
|
info(f"\nUsing ping command: {ping_command}")
|
|
|
|
sleep(length + 5)
|
|
parsed_file_name = f"{tmp_file_name}_pre_parsed.out"
|
|
|
|
extended_file_name = f"{tmp_file_name}_parsed.out"
|
|
extended_command = f'printf "0.0 0\n$(cat {parsed_file_name})" > {extended_file_name}'
|
|
|
|
parsing_command = f"more {tmp_file_name}.out | grep ms | head -{packet_count} | tr = ' ' | awk '{{print $6*{interval},$10}}' > {parsed_file_name}"
|
|
info(f"\nLatency measurement finished. Output under: {tmp_file_name}.out")
|
|
net[sender].cmd(parsing_command)
|
|
info(f"\nParsed output. Data source for Gnuplot under: {extended_file_name}")
|
|
net[sender].cmd(extended_command)
|
|
sleep(1)
|
|
|
|
net[sender].cmd(create_plot_command(graph_title, tmp_file_name, y_range, "Time in seconds", "Latency in milliseconds"))
|
|
|
|
|
|
def measure_bandwidth(net, components, iperf_server_ip, length, interval, unique_test_name, graph_title="", flag="tcp",
|
|
bandwidth=100, yrange=[0, 0]):
|
|
if len(components) != 2:
|
|
info("\nCan't measure bandwidth between more than 2 hosts")
|
|
return
|
|
|
|
if not number_check(length) or not number_check(interval):
|
|
info("\nlength and interval need to be either floats or ints")
|
|
return
|
|
|
|
iperf_client = components[0]
|
|
iperf_server = components[1]
|
|
|
|
shortcut_flag = "wo_sc"
|
|
if use_shortcut:
|
|
shortcut_flag = "sc"
|
|
|
|
tmp_file_name = f"/tmp/{iperf_client}_to_{iperf_server}_iperf_{unique_test_name}_{shortcut_flag}"
|
|
|
|
client_type_string = ""
|
|
if flag == "udp":
|
|
client_type_string = f"-u"
|
|
iperf_server_command = get_iperf_server_command(interval, tmp_file_name)
|
|
net[iperf_server].cmd(iperf_server_command)
|
|
info("\niPerf3 server command: " + iperf_server_command)
|
|
# wait for server to finish starting
|
|
sleep(1)
|
|
# setting bandwidth to a value of bits/s, default for bandwidth argument is Megabit/s
|
|
iperf_client_command = f"iperf3 -c {iperf_server_ip} -b {int(bandwidth) * 1000000} -f m -i {interval} -t {length} {client_type_string} > {tmp_file_name}_client.out &"
|
|
info("\niPerf3 client command: " + iperf_client_command)
|
|
net[iperf_client].cmd(iperf_client_command)
|
|
sleep(length + 5)
|
|
info("\nStopping iPerf server...")
|
|
net[iperf_server].cmd("pkill iperf3")
|
|
info("\nCollecting information...")
|
|
number_of_lines = int(length / interval)
|
|
parsed_file_name = f"{tmp_file_name}_pre_parsed.out"
|
|
parsing_command = f'cat {tmp_file_name}_server.out | grep sec | head -{number_of_lines} | tr - " " | awk \'{{print $4,$8}}\' > {parsed_file_name}'
|
|
|
|
net[iperf_server].cmd(parsing_command)
|
|
extended_file_name = f"{tmp_file_name}_parsed.out"
|
|
extended_command = f'printf "0.0 0\n$(cat {parsed_file_name})" > {extended_file_name}'
|
|
|
|
net[iperf_server].cmd(extended_command)
|
|
sleep(1)
|
|
|
|
xlabel = "Time in seconds"
|
|
ylabel = "Mbit(s)"
|
|
plot_command = create_plot_command(graph_title, tmp_file_name, yrange, xlabel, ylabel)
|
|
|
|
net[iperf_server].cmd(plot_command)
|
|
|
|
|
|
def get_iperf_server_command(interval, file_name, port=5201):
|
|
return f"iperf3 -s -p {port} -f m -i {interval} > {file_name}_server.out &"
|
|
|
|
|
|
def get_iperf_client_command(server_ip, bandwidth, interval, length, flag, file_name, port=5201):
|
|
client_type_string = f"-b {int(bandwidth) * 1000000}"
|
|
if flag == "udp":
|
|
client_type_string = "-u -b 0"
|
|
return f"iperf3 -c {server_ip} -p {port} -f m -i {interval} -t {length} {client_type_string} > {file_name}_client.out &"
|
|
|
|
def execute_command(net, device, command):
|
|
net[device].cmd(command)
|
|
|
|
def measure_link_usage_bandwidth(net, test_iperf_config, additional_iperf_config, length, interval, unique_test_name,
|
|
graph_title="", flag="tcp",
|
|
bandwidth=100, yrange=[0, 0]):
|
|
shortcut_flag = "wo_sc"
|
|
if use_shortcut:
|
|
shortcut_flag = "sc"
|
|
|
|
sleep(2) # little delay in case of previous executions
|
|
|
|
tmp_file_name = f"/tmp/{test_iperf_config['client']}_to_{test_iperf_config['server']}_iperf_{unique_test_name}_{shortcut_flag}"
|
|
|
|
iperf_additional_server_command = get_iperf_server_command(interval, tmp_file_name + "_additional", 5202)
|
|
info(f"\nStarting additional transfer to measure influence on bandwidth between {additional_iperf_config['client']} and {additional_iperf_config['server']}")
|
|
net[additional_iperf_config['server']].cmd(iperf_additional_server_command)
|
|
sleep(1) # wait to be sure server started
|
|
iperf_additional_client_command = get_iperf_client_command(additional_iperf_config['server_ip'], bandwidth, interval, length+4, flag, tmp_file_name+"_additional", 5202)
|
|
info(f"\nExecuting command on {additional_iperf_config['client']}: {iperf_additional_client_command}")
|
|
# Start concurrent traffic at the same time as actual traffic
|
|
#threading.Timer(1, execute_command, [net, additional_iperf_config['client'], iperf_additional_client_command])
|
|
net[additional_iperf_config['client']].cmd(iperf_additional_client_command)
|
|
measure_bandwidth(net, [test_iperf_config['client'], test_iperf_config['server']], test_iperf_config['server_ip'], length, interval, unique_test_name, graph_title, flag, bandwidth, yrange)
|
|
sleep(5)
|
|
net[additional_iperf_config['server']].cmd("pkill iperf3")
|
|
|
|
number_of_lines = int(length / interval)
|
|
parsed_file_name = f"{tmp_file_name}_pre_parsed.out"
|
|
parsing_command = f'cat {tmp_file_name}_additional_server.out | grep sec | head -{number_of_lines+1} | tail -{number_of_lines} | tr - " " | awk \'{{print $3,$8}}\' > {parsed_file_name}'
|
|
|
|
net[additional_iperf_config['server']].cmd(parsing_command)
|
|
extended_file_name = f"{tmp_file_name}_additional_parsed.out"
|
|
extended_command = f'printf "0.0 0\n$(cat {parsed_file_name})" > {extended_file_name}'
|
|
net[additional_iperf_config['server']].cmd(extended_command)
|
|
|
|
xlabel = "Time in seconds"
|
|
ylabel = "Mbit(s)"
|
|
plot_command = create_multi_plot_command(graph_title, tmp_file_name+"_combined", yrange, {"main data flow": f"{tmp_file_name}_parsed.out", "additional data flow": f"{tmp_file_name}_additional_parsed.out"}, xlabel, ylabel)
|
|
|
|
net[additional_iperf_config['server']].cmd(plot_command)
|
|
|
|
|
|
def interface_shutdown(component, component_name, interface):
|
|
info(f"\nShutting down intf {interface} on {component_name}\n")
|
|
component.cmd(f"ifconfig {interface} down")
|
|
|
|
|
|
def get_available_topos():
|
|
available_topos = {}
|
|
for topo_id, topo_info in topos.items():
|
|
available_topos[topo_id] = topo_info["description"]
|
|
return available_topos
|
|
|
|
|
|
def configure_mininet(net, topo, shortcut=False):
|
|
table_interface_map = topo.get_policy_table_interface_map()
|
|
interface_gateway_map = topo.get_interface_gateway_map()
|
|
for router, configuration in topo.get_routings().items():
|
|
tables = []
|
|
shortcut_memory = []
|
|
shortcut_queue_id = 1
|
|
table_id = 200
|
|
for table, routes in configuration.items():
|
|
if table not in tables and table != "default":
|
|
# Creating tables that do not exist for this router yet
|
|
net[router].cmd(f"echo {table_id} {table} >> /etc/iproute2/rt_tables")
|
|
tables.append(table)
|
|
table_id += 1
|
|
for route in routes:
|
|
address = route[0]
|
|
gateway = route[1]
|
|
interface = route[2]
|
|
priority = route[3]
|
|
table_string = ""
|
|
if table != "default":
|
|
table_string = f"table {table} "
|
|
incoming_interface = table_interface_map[router][table]
|
|
cut_gateway = interface_gateway_map[incoming_interface]
|
|
shortcut_identification = f"{incoming_interface}-{address}-{cut_gateway}"
|
|
if shortcut_identification not in shortcut_memory and shortcut:
|
|
implement_shortcut(net, router, incoming_interface, address, cut_gateway, shortcut_queue_id)
|
|
shortcut_memory.append(shortcut_identification)
|
|
shortcut_queue_id += 1
|
|
|
|
net[router].cmd(f"ip route add {table_string}{address} via {gateway} dev {interface} metric {priority}")
|
|
|
|
|
|
def get_topo_by_name(name, limit=None, delay=None):
|
|
if name not in topos:
|
|
info(f"Unknown topology {name}...")
|
|
|
|
module = importlib.import_module(f"topologies.{topos[name]['module']}")
|
|
topo_class = getattr(module, topos[name]['class'])
|
|
return topo_class(custom_limit=limit, custom_delay=delay)
|
|
|
|
|
|
def handle_config_execute(net, execute_config, execution_flag=None):
|
|
if execution_flag is None or "separate_definitions" not in execute_config or not execute_config[
|
|
"separate_definitions"]:
|
|
if execute_config['use_pre_defined_function']:
|
|
return execute_function(net, execute_config["command"])
|
|
else:
|
|
return execute_config['command'](net)
|
|
else:
|
|
if execution_flag == "pre":
|
|
if execute_config['use_pre_defined_function']:
|
|
return execute_function(net, execute_config["command_pre"])
|
|
else:
|
|
return execute_config['command_pre'](net)
|
|
elif execution_flag == "post":
|
|
if execute_config['use_pre_defined_function']:
|
|
return execute_function(net, execute_config["command_post"])
|
|
else:
|
|
return execute_config['command_post'](net)
|
|
else:
|
|
print("Unknown execution flag, valid flags are 'pre' and 'post'")
|
|
return None
|
|
|
|
|
|
def handle_timer_execute(net, execute_config, timing):
|
|
if execute_config['use_pre_defined_function']:
|
|
timer = threading.Timer(timing, execute_function, [net, execute_config["command"]])
|
|
else:
|
|
timer = threading.Timer(timing, execute_config['command'], [net])
|
|
timer.start()
|
|
|
|
|
|
def execute_function(net, function_tuple):
|
|
function_call = functions[function_tuple[0]]
|
|
if function_call is not None:
|
|
return function_call["callable"](net, function_tuple[1])
|
|
return None
|
|
|
|
|
|
def run(topology, test=None, limit=None, delay=None, shortcut=False):
|
|
if topology is None:
|
|
info("No topology was provided, aborting...")
|
|
exit()
|
|
topo_obj = get_topo_by_name(topology, limit, delay)
|
|
if topo_obj is None:
|
|
exit()
|
|
net = Mininet(topo=topo_obj, host=CPULimitedHost, link=TCLink)
|
|
configure_mininet(net, topo_obj, shortcut)
|
|
# adding additional routing tables, we use 200 as custom table number
|
|
for router, policies in topo_obj.get_policies().items():
|
|
for rule in policies['rules']:
|
|
for to in rule['to']:
|
|
net[router].cmd(f"ip rule add iif {rule['inport']} to {to} table {rule['table']}")
|
|
|
|
# if we want to create a timed failure (maybe while pinging), we can use a timer
|
|
# router2_failure = Timer(15, introduce_failure(net["r2"], "r2-eth3"))
|
|
global packet_counter_initialized
|
|
global packet_counter_memory
|
|
global shortcut_nftables_memory
|
|
|
|
# reset values in case of second execution in runtime
|
|
packet_counter_initialized = False
|
|
packet_counter_memory = []
|
|
shortcut_nftables_memory = []
|
|
|
|
net.start()
|
|
# CLI(net)
|
|
n = 1
|
|
tests = topo_obj.get_tests()
|
|
intermediates = []
|
|
timers = []
|
|
if test is not None:
|
|
if test in tests:
|
|
test_data = tests[test]
|
|
# print(test_data)
|
|
repeat_test = False
|
|
info(f"---- Performing test on route {test_data['source']} -> {test_data['destination']} ----\n")
|
|
if 'pre_execution' in test_data.keys():
|
|
handle_config_execute(net, test_data['pre_execution'])
|
|
if test_data['failures']:
|
|
|
|
for failure in test_data['failures']:
|
|
if failure['type'] == "intermediate":
|
|
intermediates.append(failure)
|
|
if failure['type'] == "timer":
|
|
timers.append(failure)
|
|
|
|
if timers:
|
|
for timer in timers:
|
|
handle_timer_execute(net, timer['execute'], timer['timing'])
|
|
|
|
info(f"Performing test {n}\n")
|
|
results = handle_config_execute(net, test_data['execute'], 'pre')
|
|
if type(results) is str:
|
|
info(results + '\n')
|
|
|
|
if intermediates:
|
|
repeat_test = True
|
|
for failure in intermediates:
|
|
handle_config_execute(net, failure['execute'])
|
|
|
|
if repeat_test:
|
|
info(f"Performing test {n} after failure\n")
|
|
# Make sure failure has taken effect
|
|
sleep(1)
|
|
if 'pre_execution' in test_data.keys():
|
|
handle_config_execute(net, test_data['pre_execution'])
|
|
results = handle_config_execute(net, test_data['execute'], 'post')
|
|
if type(results) is str:
|
|
info(results + '\n')
|
|
n += 1
|
|
else:
|
|
CLI(net)
|
|
|
|
# introduce_failure(net["r2"], "r2-eth3")
|
|
net.stop()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
setLogLevel('info')
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--tests', help='list all available tests for a topology', action='store_true')
|
|
parser.add_argument('--test', help='perform defined test on execution')
|
|
parser.add_argument('--topo', help='use defined topo')
|
|
parser.add_argument('--topos', help='list all available topologies', action='store_true')
|
|
parser.add_argument('--shortcut', help='use shortcut', action='store_true')
|
|
parser.add_argument('--limit_bw', help='limit bandwith of links in MBits')
|
|
parser.add_argument('--delay', help='add delay in ms to each link')
|
|
parser.add_argument('--produce_set', help='will execute the test with and without ShortCut', action='store_true')
|
|
parser.add_argument('--full_suite', help='will execute the test with and without ShortCut on all defined topologies', action='store_true')
|
|
args = parser.parse_args()
|
|
bw_limit = None
|
|
delay = None
|
|
if args.tests:
|
|
if args.topo is None:
|
|
info("Please specify a topology to print out tests for! (--topo)")
|
|
exit()
|
|
topo = get_topo_by_name(args.topo)
|
|
if topo is None:
|
|
exit()
|
|
get_tests(args.topo)
|
|
exit()
|
|
if args.topos:
|
|
print(get_available_topos())
|
|
exit()
|
|
if args.limit_bw is not None:
|
|
if not args.limit_bw.isnumeric():
|
|
info("Please only specify a numeric value as bandwith limit, exiting...")
|
|
exit()
|
|
bw_limit = int(args.limit_bw)
|
|
if args.delay is not None:
|
|
if not args.delay.isnumeric():
|
|
info("Please only specify a numeric value as delay, exiting...")
|
|
exit()
|
|
delay = f"{args.delay}ms"
|
|
if args.shortcut:
|
|
use_shortcut = True
|
|
|
|
if args.produce_set:
|
|
# we need to set global value because some functions depend on it
|
|
use_shortcut = False
|
|
run(args.topo, args.test, bw_limit, delay, use_shortcut)
|
|
sleep(1)
|
|
use_shortcut = True
|
|
run(args.topo, args.test, bw_limit, delay, use_shortcut)
|
|
elif args.full_suite:
|
|
topologies_to_test = ['4R4H', '6R4H', '8R4H']
|
|
for test_topo in topologies_to_test:
|
|
use_shortcut = False
|
|
run(test_topo, args.test, bw_limit, delay, use_shortcut)
|
|
sleep(1)
|
|
use_shortcut = True
|
|
run(test_topo, args.test, bw_limit, delay, use_shortcut)
|
|
else:
|
|
run(args.topo, args.test, bw_limit, delay, use_shortcut)
|
|
|