diff --git a/tools/contact_player/contact_player.py b/tools/contact_player/contact_player.py index e358126..89dd27e 100755 --- a/tools/contact_player/contact_player.py +++ b/tools/contact_player/contact_player.py @@ -49,40 +49,6 @@ def load_scenario(path): return nodes -# parse scenario filename from args -parser = argparse.ArgumentParser() -parser.add_argument("-l", "--loop", metavar="LOOP", type=bool, help="Override looping") -parser.add_argument( - "-m", "--map-network", help="Map network links", action="store_true" -) -parser.add_argument("scenario", help="scenario file to load") -parser.add_argument("ccp", help="core contact plan to load") -args = parser.parse_args() - - -scenario = load_scenario(args.scenario) - -netmap = args.map_network - -mapping = {} -nodes: dict[str, dict[str, dict[str, str]]] = {} -links = [] - - -for k, v in scenario.items(): - # extract node number from key - node_id = k.split(":")[1].split(".")[0] - mapping[node_id] = v.get("name") - for k, v2 in v["IPs"].items(): - res = run_in_container(v.get("name"), f"ip a | grep {v2}") - if len(res) == 0: - print("Error: IP not found") - continue - net_if = res.rsplit(" ", maxsplit=1)[1].strip() - v["IPs"][k] = {"dev": net_if, "ip": v2} - nodes[v.get("name")] = v["IPs"] - - def find_common_subnet_between_nodes( node1: str, node2: str, nodes: dict[str, dict[str, dict[str, str]]] ) -> str | None: @@ -163,7 +129,12 @@ def contact_to_node_iface( return node_iface_tuples -def set_link(contact: CoreContact, deactivate=False, command="change"): +def set_link( + nodes: dict[str, dict[str, dict[str, str]]], + contact: CoreContact, + deactivate=False, + command="change", +): loss = contact.loss if deactivate: loss = 100.0 @@ -182,28 +153,6 @@ def set_link(contact: CoreContact, deactivate=False, command="change"): ) -# check all node combinations for common subnets/links - -for n1 in nodes.keys(): - for n2 in nodes.keys(): - if n1 == n2: - continue - link = find_common_subnet_between_nodes(n1, n2, nodes) - if link is not None: - # sort n1 and n2 to avoid duplicates - l = sorted([n1, n2]) - l.append("-") - links.append(l) -links = list(set([tuple(l) for l in links])) - -# print(mapping) -# print(nodes) -# print(links) - -scenario_name = os.path.basename(args.scenario) -scenario_name = os.path.splitext(scenario_name)[0] - - def get_pure_node_links(links: list) -> set: pure_node_links = set() for l in links: @@ -257,195 +206,256 @@ def update_netmap(netmap: bool, scenario_name: str, links: list): f.write(f"{l[0]} {l[2]} {l[1]}\n") -print(links) +def main() -> None: + # parse scenario filename from args + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", "--loop", metavar="LOOP", type=bool, help="Override looping" + ) + parser.add_argument( + "-m", "--map-network", help="Map network links", action="store_true" + ) + parser.add_argument("scenario", help="scenario file to load") + parser.add_argument("ccp", help="core contact plan to load") + args = parser.parse_args() + + scenario = load_scenario(args.scenario) + + netmap = args.map_network + + mapping = {} + nodes: dict[str, dict[str, dict[str, str]]] = {} + links = [] + + for k, v in scenario.items(): + # extract node number from key + node_id = k.split(":")[1].split(".")[0] + mapping[node_id] = v.get("name") + for k, v2 in v["IPs"].items(): + res = run_in_container(v.get("name"), f"ip a | grep {v2}") + if len(res) == 0: + print("Error: IP not found") + continue + net_if = res.rsplit(" ", maxsplit=1)[1].strip() + v["IPs"][k] = {"dev": net_if, "ip": v2} + nodes[v.get("name")] = v["IPs"] + + # check all node combinations for common subnets/links + + for n1 in nodes.keys(): + for n2 in nodes.keys(): + if n1 == n2: + continue + link = find_common_subnet_between_nodes(n1, n2, nodes) + if link is not None: + # sort n1 and n2 to avoid duplicates + l = sorted([n1, n2]) + l.append("-") + links.append(l) + links = list(set([tuple(l) for l in links])) + + # print(mapping) + # print(nodes) + # print(links) -plan = CoreContactPlan.from_file(args.ccp, mapping=mapping) + scenario_name = os.path.basename(args.scenario) + scenario_name = os.path.splitext(scenario_name)[0] -# get list of unique nodes from all contacts in plan -container_devs: list[tuple[str, str]] = [] + print(links) + + plan = CoreContactPlan.from_file(args.ccp, mapping=mapping) + + # get list of unique nodes from all contacts in plan + container_devs: list[tuple[str, str]] = [] + + for contact in plan.contacts: + node_iface_tuples = contact_to_node_iface(contact, nodes) + container_devs += node_iface_tuples + + # remove duplicates in container_devs + container_devs = list(set(container_devs)) + + all_contacts = plan.all_contacts() + all_contacts_sorted_pairs = [tuple(sorted(c)) for c in all_contacts] + # add "." to sorted pairs to match format in links + all_contacts_sorted_pairs = [ + tuple([c[0], c[1], "-"]) for c in all_contacts_sorted_pairs + ] + all_contacts_sorted_pairs2 = [] + for c in all_contacts_sorted_pairs: + c = list(c) + if c[0].startswith("dev:"): + dev_str = c[0].split(":")[1] + components = dev_str.split("_") + if len(components) >= 2: + if components[0] == c[1]: + c[0] = components[1] + if components[1] == c[1]: + c[0] = components[0] + else: + print( + f"Warning: Dev string {dev_str} not mappable to nodes, skipping link." + ) + if c[1].startswith("dev:"): + dev_str = c[1].split(":")[1] + components = dev_str.split("_") + if len(components) >= 2: + if components[0] == c[0]: + c[1] = components[1] + if components[1] == c[0]: + c[1] = components[0] + else: + print( + f"Warning: Dev string {dev_str} not mappable to nodes, skipping link." + ) + all_contacts_sorted_pairs2.append(tuple(c)) + print("all contacts sorted pairs: ", all_contacts_sorted_pairs2) + + # remove sorted contact pairs from list of links + links = [l for l in links if tuple(l) not in all_contacts_sorted_pairs2] + print("links: ", links) + + update_netmap(netmap, scenario_name, links) + + # setup handler to intercept ctrl c + def signal_handler(sig, frame): + global args + print("You pressed Ctrl+C") + fixed = plan.fixed + for contact in fixed: + print("Deactivating fixed contact %s" % contact) + set_link(nodes, contact, command="del") + for c, d in container_devs: + print(f"Removing tc netem for {c} on device {d}") + set_on_interface(c, d, command="del", loss=0.0) + + sys.exit(0) + + # setting packet loss to 100% for all dynamic contacts + for c, d in container_devs: + print(f"Setting up tc for {c} on device {d} with 100% loss") + set_on_interface(c, d, command="add", loss=100.0) + + signal.signal(signal.SIGINT, signal_handler) -for contact in plan.contacts: - node_iface_tuples = contact_to_node_iface(contact, nodes) - container_devs += node_iface_tuples - -# remove duplicates in container_devs -container_devs = list(set(container_devs)) - -all_contacts = plan.all_contacts() -all_contacts_sorted_pairs = [tuple(sorted(c)) for c in all_contacts] -# add "." to sorted pairs to match format in links -all_contacts_sorted_pairs = [ - tuple([c[0], c[1], "-"]) for c in all_contacts_sorted_pairs -] -all_contacts_sorted_pairs2 = [] -for c in all_contacts_sorted_pairs: - c = list(c) - if c[0].startswith("dev:"): - dev_str = c[0].split(":")[1] - components = dev_str.split("_") - if len(components) >= 2: - if components[0] == c[1]: - c[0] = components[1] - if components[1] == c[1]: - c[0] = components[0] - else: - print( - f"Warning: Dev string {dev_str} not mappable to nodes, skipping link." - ) - if c[1].startswith("dev:"): - dev_str = c[1].split(":")[1] - components = dev_str.split("_") - if len(components) >= 2: - if components[0] == c[0]: - c[1] = components[1] - if components[1] == c[0]: - c[1] = components[0] - else: - print( - f"Warning: Dev string {dev_str} not mappable to nodes, skipping link." - ) - all_contacts_sorted_pairs2.append(tuple(c)) -print("all contacts sorted pairs: ", all_contacts_sorted_pairs2) - -# remove sorted contact pairs from list of links -links = [l for l in links if tuple(l) not in all_contacts_sorted_pairs2] -print("links: ", links) - -update_netmap(netmap, scenario_name, links) - - -# setup handler to intercept ctrl c -def signal_handler(sig, frame): - global args - print("You pressed Ctrl+C") fixed = plan.fixed for contact in fixed: - print("Deactivating fixed contact %s" % contact) - set_link(contact, command="del") + print("Activating fixed contact %s" % contact) + set_link(nodes, contact, command="add") + + cur_time = 0 + + # Open a UDP socket for reading control messages on localhost + control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + control_socket.bind(("localhost", 9966)) + control_socket.setblocking(False) + + while True: + if ( + plan.next_activation(cur_time) == None + and plan.next_deactivation(cur_time) == None + ): + if plan.loop or args.loop: + print("Looping") + cur_time = 0 + plan.reset() + continue + else: + print("No more events") + break + next_event = min( + [ + t + for t in [ + plan.next_activation(cur_time), + plan.next_deactivation(cur_time), + ] + if t is not None + ] + ) + print("[ %d ] Next event(s) at %d" % (cur_time, next_event)) + sleep_time = next_event - cur_time + time_slept = 0 + SLEEP_DELAY = 0.1 + paused = False + while time_slept < sleep_time: + try: + data, addr = control_socket.recvfrom(1024) + data = data.strip() + print(f"Received control message: {data}") + if data == b"resume" and paused: + paused = False + print("cmd: Resuming normal operation") + continue + if data == b"pause" and not paused: + paused = True + print("cmd: Pausing, waiting for 'resume' message to continue") + if data == b"next": + print("cmd: Skipping to next") + break + if data == b"time": + print(f"cmd: Current time is {cur_time + time_slept}") + control_socket.sendto( + f"{cur_time + int(time_slept)} {next_event}".encode(), addr + ) + if data == b"scenario": + print(f"cmd: Current scenario is {args.scenario} with {args.ccp}") + response = f"{args.scenario} {args.ccp}" + control_socket.sendto(response.encode(), addr) + + if data == b"links": + pure_node_links = get_pure_node_links(links) + print(f"cmd: Current links are {pure_node_links}") + response = "\n".join( + [f"{l[0]} {l[2]} {l[1]}" for l in pure_node_links] + ) + control_socket.sendto(response.encode(), addr) + + except socket.error as e: + pass + + if sleep_time - time_slept < 1: + time.sleep(sleep_time - time_slept) + break + else: + time.sleep(SLEEP_DELAY) + if not paused: + time_slept += SLEEP_DELAY + cur_time = next_event + for contact, state in plan.need_activation(cur_time): + print("[ %d ] Activating %s" % (cur_time, contact)) + set_link(nodes, contact) + l = sorted([contact.nodes[0], contact.nodes[1]]) + l.append(".") + static_link = (l[0], l[1], "-") + if static_link in links: + links.remove(static_link) + links.append(tuple(l)) + + plan.contacts[contact] = ContactState.LIVE + + for contact, state in plan.need_deactivation(cur_time): + print("[ %d ] Deactivating %s" % (cur_time, contact)) + set_link(nodes, contact, deactivate=True) + l = sorted([contact.nodes[0], contact.nodes[1]]) + l.append(".") + try: + links.remove(tuple(l)) + except ValueError: + pass + plan.contacts[contact] = ContactState.POST + + links = list(set([tuple(l) for l in links])) + update_netmap(netmap, scenario_name, links) + + # setting packet loss to 0% for all dynamic contacts, remove netem for c, d in container_devs: print(f"Removing tc netem for {c} on device {d}") set_on_interface(c, d, command="del", loss=0.0) - sys.exit(0) - - -# setting packet loss to 100% for all dynamic contacts -for c, d in container_devs: - print(f"Setting up tc for {c} on device {d} with 100% loss") - set_on_interface(c, d, command="add", loss=100.0) - -signal.signal(signal.SIGINT, signal_handler) - -fixed = plan.fixed -for contact in fixed: - print("Activating fixed contact %s" % contact) - set_link(contact, command="add") - -cur_time = 0 - -# Open a UDP socket for reading control messages on localhost -control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -control_socket.bind(("localhost", 9966)) -control_socket.setblocking(False) - -while True: - if ( - plan.next_activation(cur_time) == None - and plan.next_deactivation(cur_time) == None - ): - if plan.loop or args.loop: - print("Looping") - cur_time = 0 - plan.reset() - continue - else: - print("No more events") - break - next_event = min( - [ - t - for t in [plan.next_activation(cur_time), plan.next_deactivation(cur_time)] - if t is not None - ] - ) - print("[ %d ] Next event(s) at %d" % (cur_time, next_event)) - sleep_time = next_event - cur_time - time_slept = 0 - SLEEP_DELAY = 0.1 - paused = False - while time_slept < sleep_time: - try: - data, addr = control_socket.recvfrom(1024) - data = data.strip() - print(f"Received control message: {data}") - if data == b"resume" and paused: - paused = False - print("cmd: Resuming normal operation") - continue - if data == b"pause" and not paused: - paused = True - print("cmd: Pausing, waiting for 'resume' message to continue") - if data == b"next": - print("cmd: Skipping to next") - break - if data == b"time": - print(f"cmd: Current time is {cur_time + time_slept}") - control_socket.sendto( - f"{cur_time + int(time_slept)} {next_event}".encode(), addr - ) - if data == b"scenario": - print(f"cmd: Current scenario is {args.scenario} with {args.ccp}") - response = f"{args.scenario} {args.ccp}" - control_socket.sendto(response.encode(), addr) - - if data == b"links": - pure_node_links = get_pure_node_links(links) - print(f"cmd: Current links are {pure_node_links}") - response = "\n".join([f"{l[0]} {l[2]} {l[1]}" for l in pure_node_links]) - control_socket.sendto(response.encode(), addr) - - except socket.error as e: - pass - - if sleep_time - time_slept < 1: - time.sleep(sleep_time - time_slept) - break - else: - time.sleep(SLEEP_DELAY) - if not paused: - time_slept += SLEEP_DELAY - cur_time = next_event - for contact, state in plan.need_activation(cur_time): - print("[ %d ] Activating %s" % (cur_time, contact)) - set_link(contact) - l = sorted([contact.nodes[0], contact.nodes[1]]) - l.append(".") - static_link = (l[0], l[1], "-") - if static_link in links: - links.remove(static_link) - links.append(tuple(l)) - - plan.contacts[contact] = ContactState.LIVE - - for contact, state in plan.need_deactivation(cur_time): - print("[ %d ] Deactivating %s" % (cur_time, contact)) - set_link(contact, deactivate=True) - l = sorted([contact.nodes[0], contact.nodes[1]]) - l.append(".") - try: - links.remove(tuple(l)) - except ValueError: - pass - plan.contacts[contact] = ContactState.POST - - links = list(set([tuple(l) for l in links])) + links = [] update_netmap(netmap, scenario_name, links) -# setting packet loss to 0% for all dynamic contacts, remove netem -for c, d in container_devs: - print(f"Removing tc netem for {c} on device {d}") - set_on_interface(c, d, command="del", loss=0.0) - -links = [] -update_netmap(netmap, scenario_name, links) +if __name__ == "__main__": + main()