diff --git a/examples/16_docker_network_python/config/config.json b/examples/16_docker_network_python/config/config.json index 2c55afd..4e213b2 100644 --- a/examples/16_docker_network_python/config/config.json +++ b/examples/16_docker_network_python/config/config.json @@ -10,48 +10,90 @@ "autograding" : { "work_to_details" : [ "**/*.txt" ] }, + "resource_limits" : { + "RLIMIT_NPROC" : 100, + "RLIMIT_STACK" : 10000000, + "RLIMIT_DATA" : 2000000000 + }, // Each testcase creates a new, unique set of docker containers and networks. "testcases" : [ { //Despite the default being true, this testcase will not use a router. "use_router" : false, - "title" : "Ping Pong", + "title" : "Simple Testcase, No Router", "containers" : [ { // Setting container_name allows you to later refer to the container by this handle // (e.g. when specifying outgoing connections). By default containers are named // container0, container1, container2, etc. - "container_name" : "server", - "commands" : ["python3 server.py server"], + "container_name" : "alpha", + "commands" : ["python3 server.py"], //outgoing_connections list the containers that this container is allowed to network to. - "outgoing_connections" : ["client"] + "outgoing_connections" : ["beta", "charlie"] // You can specify a docker image here, and if it is built on submitty, this container // will use it. If unset, this field defaults to ubuntu:custom -- the default // submitty image. //container_image : }, + { + "container_name" : "beta", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "charlie"] + }, + { + "container_name" : "charlie", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "beta"] + }, { "container_name" : "client", // It can be important to ensure your container's start in the correct order. // In this example, we want the server to start before the client, so we add a sleep command. - "commands" : ["sleep 2", "python3 client.py client 0"], - "outgoing_connections" : ["server"] + "commands" : ["python3 client.py"] + // By not specifying a outgoing_connections array, we allow ourselves to connect to all nodes on the network } ], + "dispatcher_actions" : [ + { + "action" : "delay", + "seconds" : 3 + }, + { + "containers" : ["client"], + "action" : "stdin", + "string" : "alpha-beta:charlie\n" + }, + { + "action" : "delay", + "seconds" : 5 + }, + { + "containers" : ["alpha", "beta", "charlie", "client"], + "action" : "stop" + } + ], "points" : 5, "validation": [ { "method" : "diff", - "actual_file" : "server/STDOUT.txt", - "expected_file" : "expected_server_output_0.txt", + "actual_file" : "alpha/STDOUT.txt", + "expected_file" : "alpha_simple.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, { "method" : "diff", - "actual_file" : "client/STDOUT_1.txt", - "expected_file" : "expected_client_output_0.txt", + "actual_file" : "beta/STDOUT.txt", + "expected_file" : "beta_simple.txt", + "failure_message" : "ERROR: Your code did not match the expected output.", + "show_message" : "on_failure", + "deduction" : 0.5 + }, + { + "method" : "diff", + "actual_file" : "charlie/STDOUT.txt", + "expected_file" : "charlie_simple.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 @@ -59,174 +101,256 @@ ] }, { - "title" : "Ping Pong, Ping Pong", + //Despite the default being true, this testcase will not use a router. + "use_router" : true, + "title" : "Simple Testcase, With Router", "containers" : [ - //This testcase is using a router, which will be injected into the network. { - "container_name" : "server", - "commands" : ["python3 server.py server"], - "outgoing_connections" : ["client"] + // Setting container_name allows you to later refer to the container by this handle + // (e.g. when specifying outgoing connections). By default containers are named + // container0, container1, container2, etc. + "container_name" : "alpha", + "commands" : ["python3 server.py"], + //outgoing_connections list the containers that this container is allowed to network to. + "outgoing_connections" : ["beta", "charlie"] + // You can specify a docker image here, and if it is built on submitty, this container + // will use it. If unset, this field defaults to ubuntu:custom -- the default + // submitty image. + //container_image : + }, + { + "container_name" : "beta", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "charlie"] + }, + { + "container_name" : "charlie", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "beta"] }, { "container_name" : "client", - "commands" : ["sleep 2", "python3 client.py client 1"], - "outgoing_connections" : ["server"] + // It can be important to ensure your container's start in the correct order. + // In this example, we want the server to start before the client, so we add a sleep command. + "commands" : ["python3 client.py"] + // By not specifying a outgoing_connections array, we allow ourselves to connect to all nodes on the network } ], + "dispatcher_actions" : [ + { + "action" : "delay", + "seconds" : 3 + }, + { + "containers" : ["client"], + "action" : "stdin", + "string" : "alpha-beta:charlie\n" + }, + { + "action" : "delay", + "seconds" : 5 + }, + { + "containers" : ["alpha", "beta", "charlie", "client"], + "action" : "stop" + } + ], "points" : 5, "validation": [ { "method" : "diff", - "actual_file" : "server/STDOUT.txt", - "expected_file" : "expected_server_output_1.txt", + "actual_file" : "alpha/STDOUT.txt", + "expected_file" : "alpha_simple.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, { "method" : "diff", - "actual_file" : "client/STDOUT_1.txt", - "expected_file" : "expected_client_output_1.txt", + "actual_file" : "beta/STDOUT.txt", + "expected_file" : "beta_simple.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, - //Adding this "sequence_diagram" filecheck will display a sequence diagram - // of messages passed to the student. { - "sequence_diagram" : true, - "type" : "FileCheck", - "title" : "Sequence Diagram Text File", - "actual_file" : "router/sequence_diagram.txt", - "points" : 0 + "method" : "diff", + "actual_file" : "charlie/STDOUT.txt", + "expected_file" : "charlie_simple.txt", + "failure_message" : "ERROR: Your code did not match the expected output.", + "show_message" : "on_failure", + "deduction" : 0.5 } ] }, { - "title" : "Not Ping", + //Despite the default being true, this testcase will not use a router. + "use_router" : true, + "title" : "Moderate Testcase, With Router", "containers" : [ { - "container_name" : "server", - "commands" : ["python3 server.py server"], - "outgoing_connections" : ["client"] + // Setting container_name allows you to later refer to the container by this handle + // (e.g. when specifying outgoing connections). By default containers are named + // container0, container1, container2, etc. + "container_name" : "alpha", + "commands" : ["python3 server.py"], + //outgoing_connections list the containers that this container is allowed to network to. + "outgoing_connections" : ["beta", "charlie"] + // You can specify a docker image here, and if it is built on submitty, this container + // will use it. If unset, this field defaults to ubuntu:custom -- the default + // submitty image. + //container_image : + }, + { + "container_name" : "beta", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "charlie"] + }, + { + "container_name" : "charlie", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "beta"] }, { "container_name" : "client", - "commands" : ["sleep 2", "python3 client.py client 2"], - "outgoing_connections" : ["server"] + // It can be important to ensure your container's start in the correct order. + // In this example, we want the server to start before the client, so we add a sleep command. + "commands" : ["python3 client.py"] + // By not specifying a outgoing_connections array, we allow ourselves to connect to all nodes on the network } ], + "dispatcher_actions" : [ + { + "action" : "delay", + "seconds" : 3 + }, + { + "containers" : ["client"], + "action" : "stdin", + "string" : "alpha-beta:charlie:alpha:beta:charlie\n" + }, + { + "action" : "delay", + "seconds" : 5 + }, + { + "containers" : ["alpha", "beta", "charlie", "client"], + "action" : "stop" + } + ], "points" : 5, "validation": [ { "method" : "diff", - "actual_file" : "server/STDOUT.txt", - "expected_file" : "expected_server_output_2.txt", + "actual_file" : "alpha/STDOUT.txt", + "expected_file" : "alpha_moderate.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, { "method" : "diff", - "actual_file" : "client/STDOUT_1.txt", - "expected_file" : "expected_client_output_2.txt", + "actual_file" : "beta/STDOUT.txt", + "expected_file" : "beta_moderate.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, - //Adding this "sequence_diagram" filecheck will display a sequence diagram - // of messages passed to the student. { - "sequence_diagram" : true, - "type" : "FileCheck", - "title" : "Sequence Diagram Text File", - "actual_file" : "router/sequence_diagram.txt", - "points" : 0 + "method" : "diff", + "actual_file" : "charlie/STDOUT.txt", + "expected_file" : "charlie_moderate.txt", + "failure_message" : "ERROR: Your code did not match the expected output.", + "show_message" : "on_failure", + "deduction" : 0.5 } ] }, { - "title" : "Not Ping, Ping, Ping", + //Despite the default being true, this testcase will not use a router. + "use_router" : true, + "title" : "Large Testcase, With Router", "containers" : [ { - "container_name" : "server", - "commands" : ["python3 server.py server"], - "outgoing_connections" : ["client"] + "container_name" : "alpha", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["beta", "charlie", "delta"] + }, + { + "container_name" : "beta", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "charlie", "delta"] + }, + { + "container_name" : "charlie", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "beta", "delta"] + }, + { + "container_name" : "delta", + "commands" : ["python3 server.py"], + "outgoing_connections" : ["alpha", "beta", "charlie"] }, { "container_name" : "client", - "commands" : ["sleep 2", "python3 client.py client 3"], - "outgoing_connections" : ["server"] + // It can be important to ensure your container's start in the correct order. + // In this example, we want the server to start before the client, so we add a sleep command. + "commands" : ["python3 client.py"] + // By not specifying a outgoing_connections array, we allow ourselves to connect to all nodes on the network } ], + "dispatcher_actions" : [ + { + "action" : "delay", + "seconds" : 3 + }, + { + "containers" : ["client"], + "action" : "stdin", + "string" : "beta-alpha:charlie:delta:beta:charlie:alpha:beta:delta:charlie:alpha\n" + }, + { + "action" : "delay", + "seconds" : 5 + }, + { + "containers" : ["alpha", "beta", "charlie", "delta", "client"], + "action" : "stop" + } + ], "points" : 5, "validation": [ { "method" : "diff", - "actual_file" : "server/STDOUT.txt", - "expected_file" : "expected_server_output_3.txt", + "actual_file" : "alpha/STDOUT.txt", + "expected_file" : "alpha_large.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, { "method" : "diff", - "actual_file" : "client/STDOUT_1.txt", - "expected_file" : "expected_client_output_3.txt", + "actual_file" : "beta/STDOUT.txt", + "expected_file" : "beta_large.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", "deduction" : 0.5 }, - //Adding this "sequence_diagram" filecheck will display a sequence diagram - // of messages passed to the student. - { - "sequence_diagram" : true, - "type" : "FileCheck", - "title" : "Sequence Diagram Text File", - "actual_file" : "router/sequence_diagram.txt", - "points" : 0 - } - ] - }, - { - "title" : "UDP Test", - "extra_credit" : true, - "containers" : [ - { - "container_name" : "server", - "commands" : ["python3 server.py server udp_enabled"], - "outgoing_connections" : ["client"] - }, - { - "container_name" : "client", - "commands" : ["sleep 2", "python3 client.py client 4 udp_enabled"], - "outgoing_connections" : ["server"] - }, - //This testcase uses a custom router. This container must be named router - // and will automatically be connected up correctly. - { - "container_name" : "router", - "commands" : ["python3 custom_router.py"] - } - ], - "points" : 5, - "validation": [ { "method" : "diff", - "actual_file" : "client/STDOUT_1.txt", - "expected_file" : "expected_client_output_4.txt", + "actual_file" : "charlie/STDOUT.txt", + "expected_file" : "charlie_large.txt", "failure_message" : "ERROR: Your code did not match the expected output.", "show_message" : "on_failure", - "deduction" : 1.0 + "deduction" : 0.5 }, - //Adding this "sequence_diagram" filecheck will display a sequence diagram - // of messages passed to the student. { - "sequence_diagram" : true, - "type" : "FileCheck", - "title" : "Sequence Diagram Text File", - "actual_file" : "router/sequence_diagram.txt", - "points" : 0 + "method" : "diff", + "actual_file" : "delta/STDOUT.txt", + "expected_file" : "delta_large.txt", + "failure_message" : "ERROR: Your code did not match the expected output.", + "show_message" : "on_failure", + "deduction" : 0.5 } ] } diff --git a/examples/16_docker_network_python/config/test_input/client.py b/examples/16_docker_network_python/config/test_input/client.py index f455d44..3a08a9e 100644 --- a/examples/16_docker_network_python/config/test_input/client.py +++ b/examples/16_docker_network_python/config/test_input/client.py @@ -1,281 +1,66 @@ #!/usr/bin/env python3 import socket import os -import csv import sys import time +import json import traceback +import threading +import argparse MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' -KNOWN_HOSTS_UDP = 'knownhosts_udp.txt' -USE_UDP = False - - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -server_name = "" - -incoming_tcp_port = 0 -outgoing_tcp_port = 1 -incoming_udp_port = 2 -outgoing_udp_port = 3 - -outgoing_tcp_socket = None -incoming_tcp_socket = None -open_tcp_connection = None - -outgoing_udp_socket = None -incoming_udp_socket = None - - - -def init(): - read_known_hosts_csv() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_csv(): - global server_name - global incoming_tcp_port, outgoing_tcp_port - global incoming_udp_port, outgoing_udp_port +def read_known_hosts_json(): + global ADDRESS_BOOK, MY_NAME + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() - - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_tcp_port =port - server_name = recv - elif recv == MY_NAME: - incoming_tcp_port = port - server_name = sender - else: - continue - - if USE_UDP: - with open(KNOWN_HOSTS_UDP) as infile: - content = infile.readlines() - - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_udp_port =port - server_name = recv - elif recv == MY_NAME: - incoming_udp_port = port - server_name = sender - else: - continue - -def initialize_incoming_connections(): - global incoming_tcp_socket, incoming_udp_socket - incoming_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_tcp_port)) - incoming_tcp_socket.bind(server_address) - incoming_tcp_socket.listen(5) - incoming_tcp_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_tcp_port)) - - if USE_UDP: - incoming_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - incoming_udp_socket.setblocking(False) - server_address = ('', int(incoming_udp_port)) - incoming_udp_socket.bind(server_address) - print('Listening on port {0} for incoming udp connections'.format(incoming_udp_port)) - -def init_outgoing_connection(connection_type): - global outgoing_tcp_socket, outgoing_tcp_port - global outgoing_udp_socket, outgoing_udp_port - - if connection_type == 'tcp': - if outgoing_tcp_socket != None: - return True - server_address = (server_name, int(outgoing_tcp_port)) - outgoing_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - outgoing_tcp_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0} (tcp)'.format(server_address)) - traceback.print_exc() - return False - elif connection_type == 'udp': - if outgoing_udp_socket != None: - return True - server_address = (server_name, int(outgoing_udp_port)) - outgoing_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return True - return False + for host, info in content['hosts'].items(): + tcp_port = info['tcp_start_port'] + udp_port = info['udp_start_port'] + if host == MY_NAME: + continue + else: + ADDRESS_BOOK[host] = {'udp_port' : udp_port, 'tcp_port' : tcp_port } -def send_out_message(msg, tcp=True): - global outgoing_tcp_socket, outgoing_udp_socket - if tcp and outgoing_tcp_socket == None: - print("ERROR! Could not establish outgoing tcp connection to {0}".format(server_name)) - sys.exit(1) - elif tcp == False and outgoing_udp_socket == None: - print("ERROR! Could not establish outgoing udp connection to {0}".format(server_name)) - sys.exit(1) +def main(): + read_known_hosts_json() + stdin = input('') + parts = stdin.split('-') + next_host = parts[0] + next_message = ':'.join(parts[1:]) + next_message += ':FINISHED!' + next_message = next_message.encode('utf-8') - if tcp: - print("Sending '{0}' to {1}".format(msg, server_name, outgoing_tcp_port)) - sys.stdout.flush() - outgoing_tcp_socket.sendall(msg.encode('utf-8')) - else: - sys.stdout.flush() - destination_address = (server_name, int(outgoing_udp_port)) - outgoing_udp_socket.sendto(msg.encode('utf-8'),destination_address) - -def cleanup(): - print("Cleaning up after myself.") - global incoming_tcp_socket, outgoing_tcp_socket, open_tcp_connection, outgoing_udp_socket, incoming_udp_socket - if incoming_tcp_socket != None: - incoming_tcp_socket.close() - if outgoing_tcp_socket != None: - outgoing_tcp_socket.close() - if open_tcp_connection != None: - open_tcp_connection.close() - if USE_UDP: - if outgoing_udp_socket != None: - outgoing_udp_socket.close() - if incoming_udp_socket != None: - incoming_udp_socket.close() - -def check_for_request(tcp_allowed=True): - global open_tcp_connection, incoming_tcp_socket, incoming_udp_socket - - if tcp_allowed: - try: - if open_tcp_connection == None: - open_tcp_connection, client_address = incoming_tcp_socket.accept() - open_tcp_connection.setblocking(False) - - message = open_tcp_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - #print("Exception encountered. Shouldn't be a big deal.") - #traceback.print_exc() - pass - except Exception as e: - print(traceback.format_exc()) - - if USE_UDP: - try: - message = incoming_udp_socket.recv(1024) - return message.decode('utf-8') - except socket.error: - pass - except Exception as e: - print(traceback.format_exc()) - - return None - -def delay_seconds(secs=.5): - time.sleep(secs) - -def send_message_and_wait_for_response(message,tcp=True): - connection_type = 'tcp' if tcp else 'udp' - while not init_outgoing_connection(connection_type): + # First, try udp: + try: + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + addr = (next_host, ADDRESS_BOOK[next_host]['udp_port']) + outgoing_socket.sendto(next_message, addr) + except Exception as e: pass - send_out_message(message,tcp) - - response = None - while response == None: - response = check_for_request() - time.sleep(.1) - print("Recieved '{0}' from {1}".format(response, server_name)) - sys.stdout.flush() - -def just_send_message(message,tcp=True): - connection_type = 'tcp' if tcp else 'udp' - while not init_outgoing_connection(connection_type): + #Then, try tcp + try: + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host]['tcp_port'])) + outgoing_socket.sendall(next_message) + except Exception as e: pass - send_out_message(message,tcp) - -def auto_run_zero(): - delay_seconds(1) - send_message_and_wait_for_response('ping') - -def auto_run_one(): - delay_seconds(1) - send_message_and_wait_for_response('ping') - send_message_and_wait_for_response('ping') - -def auto_run_two(): - send_message_and_wait_for_response('not ping') - -def auto_run_three(): - send_message_and_wait_for_response('not ping') - send_message_and_wait_for_response('ping') - send_message_and_wait_for_response('ping') - -def auto_run_four(): - print("We are sending messages to the server...") - just_send_message('ping',tcp=False,) - just_send_message('not ping',tcp=False) - just_send_message('ping',tcp=False) - just_send_message('not ping',tcp=False) - just_send_message('not ping',tcp=False) - just_send_message('ping',tcp=False) - just_send_message('ping',tcp=False) - - responses = [] - for count in range(7): - delay_seconds() - #We don't want tcp responses. - response = check_for_request(tcp_allowed=False) - if response != None: - responses.append(response) - pongs = 0 - not_pongs = 0 - for response in responses: - if response == "pong": - pongs += 1 - else: - not_pongs += 1 - - if pongs > 0: - print("We sent 4 pings to the server...") - print('We recieved some pongs! It looks like the student was using UDP!'.format(pongs)) - if not_pongs > 0: - print("We sent 3 'not pings' to the server...") - print('We recieved some "not pongs!" It looks like the student was using UDP!'.format(not_pongs)) - - -if __name__ == "__main__": - if len(sys.argv) < 3: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.\n\t3:The name of the run you want to do.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - RUN_NAME = sys.argv[2] - - if len(sys.argv) > 3: - if sys.argv[3].strip() == 'udp_enabled': - USE_UDP=True - init() - - if RUN_NAME == "0": - auto_run_zero() - elif RUN_NAME == "1": - auto_run_one() - elif RUN_NAME == "2": - auto_run_two() - elif RUN_NAME == "3": - auto_run_three() - elif RUN_NAME == "4": - auto_run_four() - - cleanup() - print('Shutting down.') +if __name__ == '__main__': + MY_NAME = socket.gethostname() + try: + main() + except KeyboardInterrupt: + sys.exit(0) diff --git a/examples/16_docker_network_python/config/test_input/router.py b/examples/16_docker_network_python/config/test_input/router.py deleted file mode 100644 index 94a5f89..0000000 --- a/examples/16_docker_network_python/config/test_input/router.py +++ /dev/null @@ -1,295 +0,0 @@ -import socket -import sys -import csv -import traceback -import queue -import datetime -import errno -from time import sleep -import os - -LOG_FILE = 'router_log.txt' - -''' -SWITCHBOARD is a dict of the form -{ - PORT_NUMBER : { - sender : HOST_NAME - recipient : HOST_NAME - } - ... -} -''' -SWITCHBOARD = {} - -#PORTS contains a list of all ports that we have to listen at. -PORTS = list() - -# A queue determining message processing order. -QUEUE = queue.PriorityQueue() -# Global control variable -RUNNING = True - - -################################################################################################################## -# HELPER FUNCTIONS -################################################################################################################## -def convert_queue_obj_to_string(obj): - str = '\tSENDER: {0}\n\tRECIPIENT: {1}\n\tPORT: {2}\n\tCONTENT: {3}'.format(obj['sender'], obj['recipient'], obj['port'], obj['message']) - return str - -def log(line): - if os.path.exists(LOG_FILE): - append_write = 'a' # append if already exists - else: - append_write = 'w' # make a new file if not - with open(LOG_FILE, mode=append_write) as out_file: - out_file.write(line + '\n') - out_file.flush() - print(line) - sys.stdout.flush() - - -#knownhosts_tcp.csv and knownhosts_udp.csv are of the form -#sender,recipient,port_number -# such that sender sends all communications to recipient via port_number. -def build_switchboard(): - try: - #Read the known_hosts.csv see the top of the file for the specification - for connection_type in ["tcp", "udp"]: - filename = 'knownhosts_{0}.txt'.format(connection_type) - with open(filename, 'r') as infile: - content = infile.readlines() - - for line in content: - sender, recipient, port = line.split() - #Strip away trailing or leading whitespace - sender = '{0}_Actual'.format(sender.strip()) - recipient = '{0}_Actual'.format(recipient.strip()) - port = port.strip() - - if not port in PORTS: - PORTS.append(port) - else: - raise SystemExit("ERROR: port {0} was encountered twice. Please keep all ports independant.".format(port)) - - SWITCHBOARD[port] = {} - SWITCHBOARD[port]['connection_type'] = connection_type - SWITCHBOARD[port]['sender'] = sender - SWITCHBOARD[port]['recipient'] = recipient - SWITCHBOARD[port]['connected'] = False - SWITCHBOARD[port]['connection'] = None - - - except IOError as e: - log("ERROR: Could not read {0}.".format(filename)) - log(traceback.format_exc()) - except ValueError as e: - log("ERROR: {0} was improperly formatted. Please include lines of the form (SENDER, RECIPIENT, PORT)".format(filename)) - except Exception as e: - log('Encountered an error while reading and parsing {0}'.format(filename)) - log(traceback.format_exc()) - - -################################################################################################################## -# OUTGOING CONNECTION/QUEUE FUNCTIONS -################################################################################################################## - - -def connect_outgoing_socket(port): - if SWITCHBOARD[port]['connected']: - return - - connection_type = SWITCHBOARD[port]["connection_type"] - - recipient = SWITCHBOARD[port]['recipient'] - server_address = (recipient, int(port)) - - if connection_type == 'tcp': - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(server_address) - else: - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - #We catch errors one level up. - name = recipient.replace('_Actual', '') - log("Established outgoing connection to {0} on port {1}".format(name, port)) - SWITCHBOARD[port]['connected'] = True - SWITCHBOARD[port]['outgoing_socket'] = sock - -def send_outgoing_message(data): - try: - port = data['port'] - message = data['message'] - sock = SWITCHBOARD[port]['outgoing_socket'] - recipient = data['recipient'] - except: - log("An error occurred internal to the router. Please report the following error to a Submitty Administrator") - log(traceback.format_exc()) - try: - if SWITCHBOARD[port]['connection_type'] == 'tcp': - sock.sendall(message) - else: - destination_address = (recipient, int(port)) - sock.sendto(message,destination_address) - log('Sent message {!r} to {}'.format(message,recipient.replace('_Actual', ''))) - except: - log('Could not deliver message {!r} to {}'.format(message,recipient)) - SWITCHBOARD[port]['connected'] = False - SWITCHBOARD[port]['connection'].close() - SWITCHBOARD[port]['connection'] = None - -def process_queue(): - still_going = True - while still_going: - try: - now = datetime.datetime.now() - #priority queue has no peek function due to threading issues. - # as a result, pull it off, check it, then put it back on. - value = QUEUE.get_nowait() - if value[0] <= now: - send_outgoing_message(value[1]) - else: - QUEUE.put(value) - still_going = False - except queue.Empty: - still_going = False - - -################################################################################################################## -# INCOMING CONNECTION FUNCTIONS -################################################################################################################## - -def connect_incoming_sockets(): - for port in PORTS: - open_incoming_socket(port) - -def open_incoming_socket(port): - # Create a TCP/IP socket - - connection_type = SWITCHBOARD[port]['connection_type'] - sender = SWITCHBOARD[port]['sender'] - - if connection_type == "tcp": - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - elif connection_type == "udp": - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - else: - log("ERROR: bad connection type {0}. Please contact an administrator".format(connection_type)) - sys.exit(1) - - #Bind the socket to the port - server_address = ('', int(port)) - sock.bind(server_address) - sock.setblocking(False) - - log('Bound socket port {0}'.format(port)) - - if connection_type == 'tcp': - #listen for at most 1 incoming connections at a time. - sock.listen(1) - - SWITCHBOARD[port]['incoming_socket'] = sock - - if connection_type == 'udp': - SWITCHBOARD[port]['connection'] = sock - -def listen_to_sockets(): - for port in PORTS: - - try: - connection_type = SWITCHBOARD[port]["connection_type"] - if connection_type == 'tcp': - if SWITCHBOARD[port]["connection"] == None: - sock = SWITCHBOARD[port]['incoming_socket'] - connection, client_address = sock.accept() - connection.setblocking(False) - SWITCHBOARD[port]['connection'] = connection - name = SWITCHBOARD[port]['sender'].replace('_Actual', '') - log('established connection with {0} on port {1}'.format(name, port)) - else: - connection = SWITCHBOARD[port]['connection'] - elif connection_type == 'udp': - connection = SWITCHBOARD[port]["connection"] - else: - log('Invalid connection type {0}. Please contact an administrator with this error.'.format(connection_type)) - sys.exit(1) - - #TODO: May have to the max recvfrom size. - #The recvfrom call will raise a OSError if there is nothing to recieve. - message, snd = connection.recvfrom(4096) - sender = SWITCHBOARD[port]['sender'].replace("_Actual", "") - - if message.decode('utf-8') == '' and connection_type == 'tcp': - log('Host {0} disconnected on port {1}.'.format(sender,port)) - SWITCHBOARD[port]['connected'] = False - SWITCHBOARD[port]['connection'].close() - SWITCHBOARD[port]['connection'] = None - continue - - log('Recieved message {!r} from {} on port {}'.format(message,sender,port)) - - #if we did not error: - connect_outgoing_socket(port) - recipient = SWITCHBOARD[port]['recipient'] - - data = { - 'sender' : sender, - 'recipient' : recipient, - 'port' : port, - 'message' : message - } - - #TODO allow rules to change what time is used for the priority queue. - currentTime = datetime.datetime.now() - tup = (currentTime, data) - QUEUE.put(tup) - except socket.timeout as e: - #This is likely an acceptable error caused by non-blocking sockets having nothing to read. - err = e.args[0] - if err == 'timed out': - log('no data') - else: - log('real error!') - log(traceback.format_exc()) - except BlockingIOError as e: - pass - except ConnectionRefusedError as e: - #this means that connect_outgoing_tcp didn't work. - log('Connection on outgoing channel not established. Message dropped.') - log(traceback.format_exc()) - SWITCHBOARD[port]['connected'] = False - except socket.gaierror as e: - log("Unable to connect to unknown/not set up entity.") - log(traceback.format_exc()) - except Exception as e: - log("ERROR: error listening to socket {0}".format(port)) - log(traceback.format_exc()) - - -################################################################################################################## -# CONTROL FUNCTIONS -################################################################################################################## - - -#Do everything that should happen before multiprocessing kicks in. -def init(): - log('Booting up the router...') - build_switchboard() - #Only supporting tcp at the moment. - log('Connecting incoming sockets...') - connect_incoming_sockets() - -def run(): - running = True - sleep(1) - log('Listening for incoming connections...') - while RUNNING: - listen_to_sockets() - process_queue() - - -if __name__ == '__main__': - init() - run() - diff --git a/examples/16_docker_network_python/config/test_output/alpha_large.txt b/examples/16_docker_network_python/config/test_output/alpha_large.txt new file mode 100644 index 0000000..3943934 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/alpha_large.txt @@ -0,0 +1,3 @@ +Forwarding delta:beta:charlie:alpha:beta:delta:charlie:alpha:FINISHED! to charlie +Forwarding delta:charlie:alpha:FINISHED! to beta +Finished! diff --git a/examples/16_docker_network_python/config/test_output/alpha_moderate.txt b/examples/16_docker_network_python/config/test_output/alpha_moderate.txt new file mode 100644 index 0000000..1fab333 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/alpha_moderate.txt @@ -0,0 +1,2 @@ +Forwarding charlie:alpha:beta:charlie:FINISHED! to beta +Forwarding charlie:FINISHED! to beta diff --git a/examples/16_docker_network_python/config/test_output/alpha_simple.txt b/examples/16_docker_network_python/config/test_output/alpha_simple.txt new file mode 100644 index 0000000..94579db --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/alpha_simple.txt @@ -0,0 +1 @@ +Forwarding charlie:FINISHED! to beta diff --git a/examples/16_docker_network_python/config/test_output/beta_large.txt b/examples/16_docker_network_python/config/test_output/beta_large.txt new file mode 100644 index 0000000..a05035a --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/beta_large.txt @@ -0,0 +1,3 @@ +Forwarding charlie:delta:beta:charlie:alpha:beta:delta:charlie:alpha:FINISHED! to alpha +Forwarding alpha:beta:delta:charlie:alpha:FINISHED! to charlie +Forwarding charlie:alpha:FINISHED! to delta diff --git a/examples/16_docker_network_python/config/test_output/beta_moderate.txt b/examples/16_docker_network_python/config/test_output/beta_moderate.txt new file mode 100644 index 0000000..0c47329 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/beta_moderate.txt @@ -0,0 +1,2 @@ +Forwarding alpha:beta:charlie:FINISHED! to charlie +Forwarding FINISHED! to charlie diff --git a/examples/16_docker_network_python/config/test_output/beta_simple.txt b/examples/16_docker_network_python/config/test_output/beta_simple.txt new file mode 100644 index 0000000..9f7089b --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/beta_simple.txt @@ -0,0 +1 @@ +Forwarding FINISHED! to charlie diff --git a/examples/16_docker_network_python/config/test_output/charlie_large.txt b/examples/16_docker_network_python/config/test_output/charlie_large.txt new file mode 100644 index 0000000..f2f4616 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/charlie_large.txt @@ -0,0 +1,3 @@ +Forwarding beta:charlie:alpha:beta:delta:charlie:alpha:FINISHED! to delta +Forwarding beta:delta:charlie:alpha:FINISHED! to alpha +Forwarding FINISHED! to alpha diff --git a/examples/16_docker_network_python/config/test_output/charlie_moderate.txt b/examples/16_docker_network_python/config/test_output/charlie_moderate.txt new file mode 100644 index 0000000..1302ef0 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/charlie_moderate.txt @@ -0,0 +1,2 @@ +Forwarding beta:charlie:FINISHED! to alpha +Finished! diff --git a/examples/16_docker_network_python/config/test_output/charlie_simple.txt b/examples/16_docker_network_python/config/test_output/charlie_simple.txt new file mode 100644 index 0000000..1264c94 --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/charlie_simple.txt @@ -0,0 +1 @@ +Finished! diff --git a/examples/16_docker_network_python/config/test_output/delta_large.txt b/examples/16_docker_network_python/config/test_output/delta_large.txt new file mode 100644 index 0000000..150509e --- /dev/null +++ b/examples/16_docker_network_python/config/test_output/delta_large.txt @@ -0,0 +1,2 @@ +Forwarding charlie:alpha:beta:delta:charlie:alpha:FINISHED! to beta +Forwarding alpha:FINISHED! to charlie diff --git a/examples/16_docker_network_python/config/test_output/expected_client_output_0.txt b/examples/16_docker_network_python/config/test_output/expected_client_output_0.txt deleted file mode 100644 index 1549964..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_client_output_0.txt +++ /dev/null @@ -1,5 +0,0 @@ -Listening on port 9001 for incoming tcp connections -Sending 'ping' to server -Recieved 'pong' from server -Cleaning up after myself. -Shutting down. diff --git a/examples/16_docker_network_python/config/test_output/expected_client_output_1.txt b/examples/16_docker_network_python/config/test_output/expected_client_output_1.txt deleted file mode 100644 index 6ff9cbf..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_client_output_1.txt +++ /dev/null @@ -1,7 +0,0 @@ -Listening on port 9001 for incoming tcp connections -Sending 'ping' to server -Recieved 'pong' from server -Sending 'ping' to server -Recieved 'pong' from server -Cleaning up after myself. -Shutting down. diff --git a/examples/16_docker_network_python/config/test_output/expected_client_output_2.txt b/examples/16_docker_network_python/config/test_output/expected_client_output_2.txt deleted file mode 100644 index 010751b..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_client_output_2.txt +++ /dev/null @@ -1,5 +0,0 @@ -Listening on port 9001 for incoming tcp connections -Sending 'not ping' to server -Recieved 'Invalid Message 'not ping'' from server -Cleaning up after myself. -Shutting down. diff --git a/examples/16_docker_network_python/config/test_output/expected_client_output_3.txt b/examples/16_docker_network_python/config/test_output/expected_client_output_3.txt deleted file mode 100644 index 2cf5756..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_client_output_3.txt +++ /dev/null @@ -1,9 +0,0 @@ -Listening on port 9001 for incoming tcp connections -Sending 'not ping' to server -Recieved 'Invalid Message 'not ping'' from server -Sending 'ping' to server -Recieved 'pong' from server -Sending 'ping' to server -Recieved 'pong' from server -Cleaning up after myself. -Shutting down. diff --git a/examples/16_docker_network_python/config/test_output/expected_client_output_4.txt b/examples/16_docker_network_python/config/test_output/expected_client_output_4.txt deleted file mode 100644 index 558f51b..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_client_output_4.txt +++ /dev/null @@ -1,9 +0,0 @@ -Listening on port 9001 for incoming tcp connections -Listening on port 15001 for incoming udp connections -We are sending messages to the server... -We sent 4 pings to the server... -We recieved some pongs! It looks like the student was using UDP! -We sent 3 'not pings' to the server... -We recieved some "not pongs!" It looks like the student was using UDP! -Cleaning up after myself. -Shutting down. \ No newline at end of file diff --git a/examples/16_docker_network_python/config/test_output/expected_server_output_0.txt b/examples/16_docker_network_python/config/test_output/expected_server_output_0.txt deleted file mode 100644 index 643a2d0..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_server_output_0.txt +++ /dev/null @@ -1,4 +0,0 @@ -Listening on port 9000 for incoming tcp connections -Recieved 'ping' from client -Sending 'pong' to client -client disconnected. diff --git a/examples/16_docker_network_python/config/test_output/expected_server_output_1.txt b/examples/16_docker_network_python/config/test_output/expected_server_output_1.txt deleted file mode 100644 index 7ea3f20..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_server_output_1.txt +++ /dev/null @@ -1,6 +0,0 @@ -Listening on port 9000 for incoming tcp connections -Recieved 'ping' from client -Sending 'pong' to client -Recieved 'ping' from client -Sending 'pong' to client -client disconnected. diff --git a/examples/16_docker_network_python/config/test_output/expected_server_output_2.txt b/examples/16_docker_network_python/config/test_output/expected_server_output_2.txt deleted file mode 100644 index 4ac4d83..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_server_output_2.txt +++ /dev/null @@ -1,5 +0,0 @@ -Listening on port 9000 for incoming tcp connections -Recieved 'not ping' from client -Sending 'Invalid Message not ping' to client -Sending 'Invalid Message 'not ping'' to client -client disconnected. diff --git a/examples/16_docker_network_python/config/test_output/expected_server_output_3.txt b/examples/16_docker_network_python/config/test_output/expected_server_output_3.txt deleted file mode 100644 index 4e3ce06..0000000 --- a/examples/16_docker_network_python/config/test_output/expected_server_output_3.txt +++ /dev/null @@ -1,9 +0,0 @@ -Listening on port 9000 for incoming tcp connections -Recieved 'not ping' from client -Sending 'Invalid Message not ping' to client -Sending 'Invalid Message 'not ping'' to client -Recieved 'ping' from client -Sending 'pong' to client -Recieved 'ping' from client -Sending 'pong' to client -client disconnected. diff --git a/examples/16_docker_network_python/submissions/correct/server.py b/examples/16_docker_network_python/submissions/correct/server.py index 268c3a4..f0fcea6 100644 --- a/examples/16_docker_network_python/submissions/correct/server.py +++ b/examples/16_docker_network_python/submissions/correct/server.py @@ -3,147 +3,82 @@ import os import sys import time +import json import traceback +import threading MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -client_name = "" -incoming_port = 0 -outgoing_port = 1 -outgoing_socket = None -incoming_socket = None -open_connection = None - -def init(): - read_known_hosts_tcp() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_tcp(): - global client_name - global incoming_port - global outgoing_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() - - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_port =port - client_name = recv - elif recv == MY_NAME: - incoming_port = port - client_name = sender +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) + + for host, info in content['hosts'].items(): + port = info['tcp_start_port'] + IP_LOOKUP[info['ip_address']] = host + + if host == MY_NAME: + INCOMING_PORT = int(port) else: - continue + ADDRESS_BOOK[host] = int(port) -def initialize_incoming_connections(): - global incoming_socket + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') - incoming_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_port)) - incoming_socket.bind(server_address) - incoming_socket.listen(5) - incoming_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_port)) -def init_outgoing_connection(): - global outgoing_socket - if outgoing_socket != None: - return True +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK - server_address = (client_name, int(outgoing_port)) - outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(('', INCOMING_PORT)) + serversocket.listen(5) - try: - outgoing_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0}'.format(server_address)) - return False - -def send_out_message(msg): - global outgoing_socket - if outgoing_socket == None: - print("ERROR! Could not establish outgoing connection to {0}".format(client_name)) - sys.exit(1) - print("Sending '{0}' to {1}".format(msg, client_name)) - outgoing_socket.sendall(msg.encode('utf-8')) - -def cleanup(): - global outgoing_socket - global incoming_socket - global open_connection - - if outgoing_socket != None: - outgoing_socket.close() - if incoming_socket != None: - incoming_socket.close() - if open_connection != None: - open_connection.close() - -def check_for_request(): - global open_connection - global incoming_socket + while RUNNING: + try: + (clientsocket, address) = serversocket.accept() + except socket.timeout as e: + print("ERROR!") + continue + message = clientsocket.recv(1024).decode('utf-8') - try: - if open_connection == None: - open_connection, client_address = incoming_socket.accept() - open_connection.setblocking(False) + parts = message.split(':') + + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) + + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = ':'.join(parts[1:]) + next_host = parts[0] + print(f'Forwarding {next_message} to {parts[0]}') + + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host])) + outgoing_socket.sendall(next_message.encode('utf-8')) - message = open_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - pass - except Exception as e: - print(traceback.format_exc()) - sys.stdout.flush() - return None - -def sendPong(): - if init_outgoing_connection(): - send_out_message("pong") - else: - print("ERROR: could not send pong.") - -def send_err(old_message): - if init_outgoing_connection(): - print("Sending 'Invalid Message {0}' to {1}".format(old_message, client_name)) - send_out_message("Invalid Message '{0}'".format(old_message)) - else: - print("ERROR: could not send error message.") - -def run(): - running = True - while running: - message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - if message == "ping": - sendPong() - else: - send_err(message) - sys.stdout.flush() - time.sleep(.1) if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - - init() - run() - cleanup() \ No newline at end of file + + MY_NAME = socket.gethostname() + RUNNING = True + + read_known_hosts_json() + + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + pass + finally: + sys.exit(0) \ No newline at end of file diff --git a/examples/16_docker_network_python/submissions/incorrect/server.py b/examples/16_docker_network_python/submissions/incorrect/server.py index 4394575..452ff10 100644 --- a/examples/16_docker_network_python/submissions/incorrect/server.py +++ b/examples/16_docker_network_python/submissions/incorrect/server.py @@ -3,146 +3,81 @@ import os import sys import time +import json import traceback +import threading +import random MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -client_name = "" -incoming_port = 0 -outgoing_port = 1 -outgoing_socket = None -incoming_socket = None -open_connection = None - -def init(): - read_known_hosts_tcp() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_tcp(): - global client_name - global incoming_port - global outgoing_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_port =port - client_name = recv - elif recv == MY_NAME: - incoming_port = port - client_name = sender + for host, info in content['hosts'].items(): + port = info['tcp_start_port'] + IP_LOOKUP[info['ip_address']] = host + + if host == MY_NAME: + INCOMING_PORT = int(port) else: - continue + ADDRESS_BOOK[host] = int(port) -def initialize_incoming_connections(): - global incoming_socket + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') - incoming_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_port)) - incoming_socket.bind(server_address) - incoming_socket.listen(5) - incoming_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_port)) - -def init_outgoing_connection(): - global outgoing_socket - if outgoing_socket != None: - return True - - server_address = (client_name, int(outgoing_port)) - outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - outgoing_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0}'.format(server_address)) - return False -def send_out_message(msg): - global outgoing_socket - if outgoing_socket == None: - print("ERROR! Could not establish outgoing connection to {0}".format(client_name)) - sys.exit(1) - print("Sending '{0}' to {1}".format(msg, client_name)) - outgoing_socket.sendall(msg.encode('utf-8')) +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK -def cleanup(): - global outgoing_socket - global incoming_socket - global open_connection + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(('', INCOMING_PORT)) + serversocket.listen(5) - if outgoing_socket != None: - outgoing_socket.close() - if incoming_socket != None: - incoming_socket.close() - if open_connection != None: - open_connection.close() + while RUNNING: + try: + (clientsocket, address) = serversocket.accept() + except socket.timeout as e: + print("ERROR!") + continue + message = clientsocket.recv(1024).decode('utf-8') -def check_for_request(): - global open_connection - global incoming_socket + parts = message.split(':') - try: - if open_connection == None: - open_connection, client_address = incoming_socket.accept() - open_connection.setblocking(False) + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) - message = open_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - pass - except Exception as e: - print(traceback.format_exc()) - sys.stdout.flush() - return None + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = ':'.join(parts[1:]) + # ERROR: Randomly select the next host + next_host = random.choice(ADDRESS_BOOK.keys()) + print(f'Forwarding {next_message} to {parts[0]}') + + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host])) + outgoing_socket.sendall(next_message.encode('utf-8')) -def sendPong(): - if init_outgoing_connection(): - send_out_message("Hello, World.") - else: - print("ERROR: could not send pong.") -def send_err(old_message): - if init_outgoing_connection(): - sendPong() - else: - print("ERROR: could not send error message.") -def run(): - running = True - while running: - message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - if message == "ping": - sendPong() - else: - send_err(message) - sys.stdout.flush() - time.sleep(.1) +if __name__ == "__main__": + MY_NAME = socket.gethostname() + RUNNING = True -if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - - init() - run() - cleanup() \ No newline at end of file + read_known_hosts_json() + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + sys.exit(0) \ No newline at end of file diff --git a/examples/16_docker_network_python/submissions/infinite_response/server.py b/examples/16_docker_network_python/submissions/infinite_response/server.py index 5bbe194..99be04b 100644 --- a/examples/16_docker_network_python/submissions/infinite_response/server.py +++ b/examples/16_docker_network_python/submissions/infinite_response/server.py @@ -3,148 +3,80 @@ import os import sys import time +import json import traceback +import threading MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -client_name = "" -incoming_port = 0 -outgoing_port = 1 -outgoing_socket = None -incoming_socket = None -open_connection = None - -def init(): - read_known_hosts_tcp() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_tcp(): - global client_name - global incoming_port - global outgoing_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_port =port - client_name = recv - elif recv == MY_NAME: - incoming_port = port - client_name = sender + for host, info in content['hosts'].items(): + port = info['tcp_start_port'] + IP_LOOKUP[info['ip_address']] = host + + if host == MY_NAME: + INCOMING_PORT = int(port) else: - continue - -def initialize_incoming_connections(): - global incoming_socket + ADDRESS_BOOK[host] = int(port) - incoming_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_port)) - incoming_socket.bind(server_address) - incoming_socket.listen(5) - incoming_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_port)) - -def init_outgoing_connection(): - global outgoing_socket - if outgoing_socket != None: - return True - - server_address = (client_name, int(outgoing_port)) - outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - outgoing_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0}'.format(server_address)) - return False + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') -def send_out_message(msg): - global outgoing_socket - if outgoing_socket == None: - print("ERROR! Could not establish outgoing connection to {0}".format(client_name)) - sys.exit(1) - print("Sending '{0}' to {1}".format(msg, client_name)) - outgoing_socket.sendall(msg.encode('utf-8')) -def cleanup(): - global outgoing_socket - global incoming_socket - global open_connection +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK - if outgoing_socket != None: - outgoing_socket.close() - if incoming_socket != None: - incoming_socket.close() - if open_connection != None: - open_connection.close() + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(('', INCOMING_PORT)) + serversocket.listen(5) -def check_for_request(): - global open_connection - global incoming_socket + while RUNNING: + try: + (clientsocket, address) = serversocket.accept() + except socket.timeout as e: + print("ERROR!") + continue + message = clientsocket.recv(1024).decode('utf-8') - try: - if open_connection == None: - open_connection, client_address = incoming_socket.accept() - open_connection.setblocking(False) + parts = message.split(':') - message = open_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - pass - except Exception as e: - print(traceback.format_exc()) - sys.stdout.flush() - return None + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) -def sendPong(): - if init_outgoing_connection(): - while True: - send_out_message("pong") - else: - print("ERROR: could not send pong.") + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = ':'.join(parts[1:]) + next_host = parts[0] + print(f'Forwarding {next_message} to {parts[0]}') + + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host])) + while True: + outgoing_socket.sendall(next_message.encode('utf-8')) + time.sleep(.01) -def send_err(old_message): - if init_outgoing_connection(): - print("Sending 'Invalid Message {0}' to {1}".format(old_message, client_name)) - send_out_message("Invalid Message '{0}'".format(old_message)) - else: - print("ERROR: could not send error message.") -def run(): - running = True - while running: - message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - if message == "ping": - sendPong() - else: - send_err(message) - sys.stdout.flush() - time.sleep(.1) +if __name__ == "__main__": + MY_NAME = socket.gethostname() + RUNNING = True -if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - - init() - run() - cleanup() \ No newline at end of file + read_known_hosts_json() + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + sys.exit(0) \ No newline at end of file diff --git a/examples/16_docker_network_python/submissions/large_response/server.py b/examples/16_docker_network_python/submissions/large_response/server.py index cad3e42..297c396 100644 --- a/examples/16_docker_network_python/submissions/large_response/server.py +++ b/examples/16_docker_network_python/submissions/large_response/server.py @@ -3,147 +3,79 @@ import os import sys import time +import json import traceback +import threading MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -client_name = "" -incoming_port = 0 -outgoing_port = 1 -outgoing_socket = None -incoming_socket = None -open_connection = None - -def init(): - read_known_hosts_tcp() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_tcp(): - global client_name - global incoming_port - global outgoing_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_port =port - client_name = recv - elif recv == MY_NAME: - incoming_port = port - client_name = sender + for host, info in content['hosts'].items(): + port = info['tcp_start_port'] + IP_LOOKUP[info['ip_address']] = host + + if host == MY_NAME: + INCOMING_PORT = int(port) else: - continue + ADDRESS_BOOK[host] = int(port) -def initialize_incoming_connections(): - global incoming_socket + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') - incoming_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_port)) - incoming_socket.bind(server_address) - incoming_socket.listen(5) - incoming_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_port)) - -def init_outgoing_connection(): - global outgoing_socket - if outgoing_socket != None: - return True - - server_address = (client_name, int(outgoing_port)) - outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - outgoing_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0}'.format(server_address)) - return False -def send_out_message(msg): - global outgoing_socket - if outgoing_socket == None: - print("ERROR! Could not establish outgoing connection to {0}".format(client_name)) - sys.exit(1) - outgoing_socket.sendall(msg.encode('utf-8')) +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK -def cleanup(): - global outgoing_socket - global incoming_socket - global open_connection + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(('', INCOMING_PORT)) + serversocket.listen(5) - if outgoing_socket != None: - outgoing_socket.close() - if incoming_socket != None: - incoming_socket.close() - if open_connection != None: - open_connection.close() + while RUNNING: + try: + (clientsocket, address) = serversocket.accept() + except socket.timeout as e: + print("ERROR!") + continue + message = clientsocket.recv(1024).decode('utf-8') -def check_for_request(): - global open_connection - global incoming_socket + parts = message.split(':') - try: - if open_connection == None: - open_connection, client_address = incoming_socket.accept() - open_connection.setblocking(False) + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) - message = open_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - pass - except Exception as e: - print(traceback.format_exc()) - sys.stdout.flush() - return None + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = 'LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGELONG MESSAGE LONG MESSAGE LONG MESSAGE LONG MESSAGE' + next_host = parts[0] + print(f'Forwarding {next_message} to {parts[0]}') + + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host])) + outgoing_socket.sendall(next_message.encode('utf-8')) -def sendPong(): - if init_outgoing_connection(): - response = "pong" * 1000000 - send_out_message(response) - else: - print("ERROR: could not send pong.") -def send_err(old_message): - if init_outgoing_connection(): - print("Sending 'Invalid Message {0}' to {1}".format(old_message, client_name)) - send_out_message("Invalid Message '{0}'".format(old_message)) - else: - print("ERROR: could not send error message.") -def run(): - running = True - while running: - message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - if message == "ping": - sendPong() - else: - send_err(message) - sys.stdout.flush() - time.sleep(.1) +if __name__ == "__main__": + MY_NAME = socket.gethostname() + RUNNING = True -if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - - init() - run() - cleanup() \ No newline at end of file + read_known_hosts_json() + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + sys.exit(0) \ No newline at end of file diff --git a/examples/16_docker_network_python/submissions/syntax_error/server.py b/examples/16_docker_network_python/submissions/syntax_error/server.py index 096b543..0db2280 100644 --- a/examples/16_docker_network_python/submissions/syntax_error/server.py +++ b/examples/16_docker_network_python/submissions/syntax_error/server.py @@ -3,147 +3,79 @@ import os import sys import time +import json import traceback +import threading MY_NAME = "" -KNOWN_HOSTS_TCP = 'knownhosts_tcp.txt' - -# This tutorial is kept simple intentionally rather than using data structures or -# dictionaries to store these values. -client_name = "" -incoming_port = 0 -outgoing_port = 1 -outgoing_socket = None -incoming_socket = None -open_connection = None - -def init(): - read_known_hosts_tcp() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_tcp(): - global client_name - global incoming_port - global outgoing_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_port =port - client_name = recv - elif recv == MY_NAME: - incoming_port = port - client_name = sender + for host, info in content['hosts'].items(): + port = info['tcp_start_port'] + IP_LOOKUP[info['ip_address']] = host + + if host == MY_NAME: + INCOMING_PORT = int(port) else: - continue + ADDRESS_BOOK[host] = int(port) -def initialize_incoming_connections(): - global incoming_socket + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') - incoming_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_port)) - incoming_socket.bind(server_address) - incoming_socket.listen(5) - incoming_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_port)) - -def init_outgoing_connection(): - global outgoing_socket - if outgoing_socket != None: - return True - - server_address = (client_name, int(outgoing_port)) - outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - try: - outgoing_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0}'.format(server_address)) - return False -def send_out_message(msg): - global outgoing_socket - if outgoing_socket == None: - print("ERROR! Could not establish outgoing connection to {0}".format(client_name)) - sys.exit(1) - print("Sending '{0}' to {1}".format(msg, client_name)) - outgoing_socket.sendall(msg.encode('utf-8')) +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK -def cleanup(): - global outgoing_socket - global incoming_socket - global open_connection + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(('', INCOMING_PORT)) + serversocket.listen(5) - if outgoing_socket != None: - outgoing_socket.close() - if incoming_socket != None: - incoming_socket.close() - if open_connection != None: - open_connection.close() + while RUNNING: + try: + (clientsocket, address) = serversocket.accept() + except socket.timeout as e: + print("ERROR!") + continue + message = clientsocket.recv(1024).decode('utf-8') -def check_for_request(): - global open_connection - global incoming_socket + parts = message.split(':') - try: - if open_connection == None: - open_connection, client_address = incoming_socket.accept() - open_connection.setblocking(False) + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) - message = open_connection.recv(1024) - return message.decode('utf-8') - except BlockingIOError as e: - pass - except Exception as e: - print(traceback.format_exc()) - sys.stdout.flush() - return None + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = ':'.join(parts[1:]) + next_host = parts[0] + print(f'Forwarding {next_message} to {parts[0]}') + + outgoing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outgoing_socket.connect((next_host, ADDRESS_BOOK[next_host])) + outgoing_socket.sendall(next_message.encode('utf-8')) -def sendPong(): - if init_outgoing_connection(): - send_out_message("pong") - else: - print("ERROR: could not send pong.") -def send_err(old_message): - if init_outgoing_connection(): - print("Sending 'Invalid Message {0}' to {1}".format(old_message, client_name)) - send_out_message("Invalid Message '{0}'".format(old_message, syntax_error)) - else: - print("ERROR: could not send error message.") -def run(): - running = True - while running: - message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - if message == "ping": - sendPong() - else: - send_err(message) - sys.stdout.flush() - time.sleep(.1) +if __name__ == "__main__": + MY_NAME = socket.gethostname() + RUNNING = True -if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - - init() - run() - cleanup() \ No newline at end of file + read_known_hosts_json() + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + sys.exit(0) \ No newline at end of file diff --git a/examples/16_docker_network_python/submissions/udp_correct/server.py b/examples/16_docker_network_python/submissions/udp_correct/server.py index 344a365..0d44933 100644 --- a/examples/16_docker_network_python/submissions/udp_correct/server.py +++ b/examples/16_docker_network_python/submissions/udp_correct/server.py @@ -1,220 +1,78 @@ #!/usr/bin/env python3 import socket import os -import csv import sys import time +import json import traceback - -KNOWN_HOSTS_TCP = "knownhosts_tcp.txt" -KNOWN_HOSTS_UDP = "knownhosts_udp.txt" +import threading MY_NAME = "" -USE_UDP = False - -client_name = "" - -incoming_tcp_port = 0 -outgoing_tcp_port = 1 -incoming_udp_port = 2 -outgoing_udp_port = 3 - -outgoing_tcp_socket = None -incoming_tcp_socket = None -open_tcp_connection = None - -outgoing_udp_socket = None -incoming_udp_socket = None - -def init(): - read_known_hosts_csv() - initialize_incoming_connections() - +KNOWN_HOSTS_JSON = 'knownhosts.json' +ADDRESS_BOOK = dict() +IP_LOOKUP = dict() +RUNNING = False +INCOMING_PORT = -1 #knownhosts_tcp.csv and knownhosts_udp.csv are of the form #sender,recipient,port_number # such that sender sends all communications to recipient via port_number. -def read_known_hosts_csv(): - global client_name - global incoming_tcp_port, outgoing_tcp_port - global incoming_udp_port, outgoing_udp_port - with open(KNOWN_HOSTS_TCP) as infile: - content = infile.readlines() +def read_known_hosts_json(): + global INCOMING_PORT, ADDRESS_BOOK, IP_LOOKUP + with open(KNOWN_HOSTS_JSON) as infile: + content = json.load(infile) - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_tcp_port =port - client_name = recv - elif recv == MY_NAME: - incoming_tcp_port = port - client_name = sender - else: - continue - - if USE_UDP: - with open(KNOWN_HOSTS_UDP) as infile: - content = infile.readlines() + for host, info in content['hosts'].items(): + port = info['udp_start_port'] + IP_LOOKUP[info['ip_address']] = host - for line in content: - sender, recv, port = line.split() - if sender == MY_NAME: - outgoing_udp_port = port - client_name = recv - elif recv == MY_NAME: - incoming_udp_port = port - client_name = sender - else: - continue - -def initialize_incoming_connections(): - global incoming_tcp_socket, incoming_udp_socket - incoming_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = ('', int(incoming_tcp_port)) - incoming_tcp_socket.bind(server_address) - incoming_tcp_socket.listen(5) - incoming_tcp_socket.setblocking(False) - print('Listening on port {0} for incoming tcp connections'.format(incoming_tcp_port)) - - if USE_UDP: - incoming_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - incoming_udp_socket.setblocking(False) - server_address = ('', int(incoming_udp_port)) - incoming_udp_socket.bind(server_address) - print('Listening on port {0} for incoming udp connections'.format(incoming_udp_port)) - -def init_outgoing_connection(connection_type): - global outgoing_tcp_socket, outgoing_tcp_port - global outgoing_udp_socket, outgoing_udp_port - - if connection_type == 'tcp': - if outgoing_tcp_socket != None: - return True - server_address = (client_name, int(outgoing_tcp_port)) - outgoing_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - outgoing_tcp_socket.connect(server_address) - return True - except Exception as e: - print('Unable to connect on {0} (tcp)'.format(server_address)) - traceback.print_exc() - return False - elif connection_type == 'udp': - if outgoing_udp_socket != None: - return True - server_address = (client_name, int(outgoing_udp_port)) - outgoing_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return True - return False - - - -def send_out_message(msg, tcp=True): - global outgoing_tcp_socket, outgoing_udp_socket - if tcp and outgoing_tcp_socket == None: - print("ERROR! Could not establish outgoing tcp connection to {0}".format(client_name)) - sys.exit(1) - elif tcp == False and outgoing_udp_socket == None: - print("ERROR! Could not establish outgoing udp connection to {0}".format(client_name)) - sys.exit(1) - - if tcp: - print("Sending '{0}' to {1}".format(msg, client_name, outgoing_tcp_port)) - sys.stdout.flush() - outgoing_tcp_socket.sendall(msg.encode('utf-8')) - else: - print("Sending '{0}' to {1}".format(msg, client_name, outgoing_udp_port)) - sys.stdout.flush() - destination_address = (client_name, int(outgoing_udp_port)) - outgoing_udp_socket.sendto(msg.encode('utf-8'),destination_address) + if host == MY_NAME: + INCOMING_PORT = int(port) + else: + ADDRESS_BOOK[host] = int(port) + if INCOMING_PORT == -1: + raise SystemExit(f'ERROR: No entry for this host in {KNOWN_HOSTS_JSON}') -def cleanup(): - global incoming_tcp_socket, outgoing_tcp_socket, open_tcp_connection, outgoing_udp_socket, incoming_udp_socket - if incoming_tcp_socket != None: - incoming_tcp_socket.close() - if outgoing_tcp_socket != None: - outgoing_tcp_socket.close() - if open_tcp_connection != None: - open_tcp_connection.close() - if outgoing_udp_socket != None: - outgoing_udp_socket.close() - if incoming_udp_socket != None: - incoming_udp_socket.close() -def check_for_request(): - global open_tcp_connection, incoming_tcp_socket, incoming_udp_socket - try: - if open_tcp_connection == None: - open_tcp_connection, client_address = incoming_tcp_socket.accept() - open_tcp_connection.setblocking(False) +def listen_for_incoming_messages(): + global RUNNING, IP_LOOKUP, ADDRESS_BOOK - message = open_tcp_connection.recv(1024) - return message.decode('utf-8'), True - except BlockingIOError as e: - #print("Exception encountered. Shouldn't be a big deal.") - pass - except Exception as e: - print(traceback.format_exc()) + udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_socket.bind(('', INCOMING_PORT)) + udp_socket.settimeout(1) - if USE_UDP: + while RUNNING: try: - message = incoming_udp_socket.recv(1024) - return message.decode('utf-8'), False - except socket.error: - pass - except Exception as e: - print(traceback.format_exc()) + msg, addr = udp_socket.recvfrom(1024) + except socket.timeout as e: + continue + message = msg.decode('utf-8') - return None, None + parts = message.split(':') -def sendPong(tcp=True): - connection_type = 'tcp' if tcp else 'udp' - if init_outgoing_connection(connection_type): - send_out_message("pong",tcp=tcp) - else: - print("ERROR: could not send pong.") + while parts[0] == MY_NAME: + print(f'Forwarding {parts} to myself.') + parts.pop(0) -def send_err(old_message,tcp=True): - connection_type = 'tcp' if tcp else 'udp' - if init_outgoing_connection(connection_type): - print("Sending 'Invalid Message {0}' to {1}".format(old_message, client_name)) - send_out_message("Invalid Message '{0}'".format(old_message),tcp=tcp) - else: - print("ERROR: could not send error message.") + if len(parts) == 0 or parts[0] == 'FINISHED!': + print('Finished!') + else: + next_message = ':'.join(parts[1:]) + next_host = parts[0] + print(f'Forwarding {next_message} to {parts[0]}') + + udp_socket.sendto(next_message.encode('utf-8'), (parts[0], ADDRESS_BOOK[next_host])) -def run(): - running = True - while running: - sys.stdout.flush() - message, tcp_message = check_for_request() - if message == '': - print('{0} disconnected.'.format(client_name)) - sys.stdout.flush() - running = False - continue - if message != None: - print("Recieved '{0}' from {1}".format(message, client_name)) - sys.stdout.flush() - if message == "ping": - sendPong(tcp=tcp_message) - else: - send_err(message,tcp=tcp_message) - sys.stdout.flush() - time.sleep(.1) if __name__ == "__main__": - if len(sys.argv) < 2: - print("ERROR: Please pass the following arguments:\n\t1: The name of this host in the knownhosts.csv file.") - sys.stdout.flush() - sys.exit(1) - MY_NAME = sys.argv[1] - if len(sys.argv) > 2: - if sys.argv[2].strip() == 'udp_enabled': - USE_UDP=True - init() + MY_NAME = socket.gethostname() + RUNNING = True - run() - cleanup() \ No newline at end of file + read_known_hosts_json() + try: + listen_for_incoming_messages() + except KeyboardInterrupt: + sys.exit(0) \ No newline at end of file