diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..e54e30b --- /dev/null +++ b/docs.md @@ -0,0 +1,77 @@ +Purpose +------- + +The purpose of this network simulation package is to provide a simplified network interface abstraction for Python programs implementing cryptographic protocols and thus to help focusing on the crypto stuff instead of worrying about complicated networking issues. + + +The network interface abstraction +--------------------------------- + +Programs can instantiate the network_interface class (provided in the netinterface.py module) to create and use a network interface for communications. The network_interface class provides message sending and receiving abstractions via two functions: send_msg() and receive_msg(). + +The function send_msg(dst, msg) sends out message msg to destination dst, where msg must be a byte string and dst must be a string of valid destination addresses. + +Network addresses are capital letters A, B, C, ... Z. Destination dst may contain a single address (e.g., 'A', 'B', ...) or multiple addresses (e.g., 'ABC' means sending the message to A, B, and C). The special broadcast address is +, so dst = '+' will result in sending the message to all addresses. + +The function receive_msg(blocking) returns a status flag (Boolean) and a received message (byte string). It can be called in blocking or in non-blocking mode. Blocking mode (calling with blocking=True) means that the function will return only when a new message is available, and in this case, status=True and the received message will be returned. Non-blocking mode (calling with blocking=False) means that the function returns immediately, and if a message was available then status=True and the message will be returned, otherwise status=False and an empty byte string will be returned. + +An example for calling receive_msg() in blocking mode is the following: + +from netinterface import network_interface +netif = network_interface(NET_PATH, OWN_ADDR) # create network interface netif +status, msg = netif.receive_msg(blocking=True) # when returns, status is True and msg contains a message +print(msg) + +An example for calling receive_msg() in non-blocking mode is the following: + +from netinterface import network_interface +netif = network_interface(NET_PATH, OWN_ADDR) # create network interface netif +status, msg = netif.receive_msg(blocking=False) +if status: print(msg) # if status is True, then a message was returned in msg, and we can print it +else: ... # otherwise do something else, e.g., wait and try again + +Creating a new network interface is done with the constructor of the network_interface class. The constructor takes two input parameters: +- a path where the messages sent to the various addresses are saved in files (e.g., './' or 'C:/network/') +- the address of the new interface being created (e.g., 'A', 'B', ... or 'Z'). + + +The newtork module +------------------ + +The network is simulated by running the network.py program. This will copy files representing messages from the outgoing folder of the source to the incoming folders of the destinations. The network.py program should be started before any other program relying on the network_interface abstraction described above. + +The network.py program is a command line application that can recieve the following parameters as inputs: +- a path where the messages sent to the various addresses are saved in files (e.g., './' or 'C:/network/'); this is provided with command line option -p or --path +- a string containing the valid addresses (e.g., 'ABCDE'); this is provided with command line option -a or --addrspace + +If the path is not given as input, it will take deafult value './'. If the address space is not given as input, it will take default value 'ABC'. + +The network.py program can also take an optional command line option -c or --clean. When calling with this option, it will delete all previous messages from the incoming and outgoing folders belonging to the network addresses on the given network path. + +Examples: + +python3 network.py + running the network simulation with default path './' and default address space 'ABC' (i.e., addresses A, B, and C); + no clean-up, so messages from a previous run remain in the folders of path './' + +python3 network.py -p './network/' -a 'ABCDE' + running the network simulation such that it looks for files representing messages on path './network/' + and allowing five addresses to be used A, B, C, D, and E; + no clean-up, so messages from a previous run remain in the folders of path './network/' + +python3 network.py -p './network/' -a 'ABCDE' --clean + same as above but cleaning-up all folders on path './network/' + +Note: Programs using the network_interface class should provide the same network path to the constructor of network_interface as the network path used to start the network simulation program network.py. Also, programs should create network interfaces with addresses that are contained in the address space provided as input to the network simulation program network.py. + + +More examples +------------- + +An example sender and an example receiver applications are provided too (sender.py and receiver.py). Here's how to use them: + +python3 network.py -p './network/' -a 'ABCDE' --clean +python3 sender.py -p './network/' -a A +python3 receiver.py -p './network/' -a B + +Now, A can send messages to B (given that network.py is running)... diff --git a/netinterface.py b/netinterface.py new file mode 100644 index 0000000..c816423 --- /dev/null +++ b/netinterface.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +#netinterface.py + +import os, time + +class network_interface: + timeout = 0.800 # 800 millisec + addr_space = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + own_addr = '' + net_path = '' + last_read = -1 + + def __init__(self, path, addr): + self.net_path = path + self.own_addr = addr + + addr_dir = self.net_path + self.own_addr + if not os.path.exists(addr_dir): + os.mkdir(addr_dir) + os.mkdir(addr_dir + '/IN') + os.mkdir(addr_dir + '/OUT') + + in_dir = addr_dir + '/IN' + msgs = sorted(os.listdir(in_dir)) + self.last_read = len(msgs) - 1 + + + def send_msg(self, dst, msg): + + out_dir = self.net_path + self.own_addr + '/OUT' + msgs = sorted(os.listdir(out_dir)) + + if len(msgs) > 0: + last_msg = msgs[-1].split('--')[0] + next_msg = (int.from_bytes(bytes.fromhex(last_msg), byteorder='big') + 1).to_bytes(2, byteorder='big').hex() + else: + next_msg = '0000' + + next_msg += '--' + dst + with open(out_dir + '/' + next_msg, 'wb') as f: f.write(msg) + + return True + + def receive_msg(self, blocking=False): + + in_dir = self.net_path + self.own_addr + '/IN' + + status = False + msg = b'' + + while True: + msgs = sorted(os.listdir(in_dir)) + if len(msgs) - 1 > self.last_read: + with open(in_dir + '/' + msgs[self.last_read + 1], 'rb') as f: msg = f.read() + status = True + self.last_read += 1 + + if not blocking or status: return status, msg + else: time.sleep(self.timeout) + \ No newline at end of file diff --git a/network.py b/network.py new file mode 100644 index 0000000..7ce1789 --- /dev/null +++ b/network.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +#network.py + +import os, sys, getopt, time + +NET_PATH = './' +ADDR_SPACE = 'ABC' +CLEAN = False +TIMEOUT = 0.500 # 500 millisec + +def read_msg(src): + global last_read + + out_dir = NET_PATH + src + '/OUT' + msgs = sorted(os.listdir(out_dir)) + + if len(msgs) - 1 <= last_read[src]: return '', '' + + next_msg = msgs[last_read[src] + 1] + dsts = next_msg.split('--')[1] + with open(out_dir + '/' + next_msg, 'rb') as f: msg = f.read() + + last_read[src] += 1 + return msg, dsts + + +def write_msg(dst, msg): + + in_dir = NET_PATH + dst + '/IN' + msgs = sorted(os.listdir(in_dir)) + + if len(msgs) > 0: + last_msg = msgs[-1] + next_msg = (int.from_bytes(bytes.fromhex(last_msg), byteorder='big') + 1).to_bytes(2, byteorder='big').hex() + else: + next_msg = '0000' + + with open(in_dir + '/' + next_msg, 'wb') as f: f.write(msg) + + return + +# ------------ +# main program +# ------------ + +try: + opts, args = getopt.getopt(sys.argv[1:], shortopts='hp:a:c', longopts=['help', 'path=', 'addrspace=', 'clean']) +except getopt.GetoptError: + print('Usage: python network.py -p -a
[--clean]') + sys.exit(1) + +#if len(opts) == 0: +# print('Usage: python network.py -p -a
[--clean]') +# sys.exit(1) + +for opt, arg in opts: + if opt == '-h' or opt == '--help': + print('Usage: python network.py -p -a
[--clean]') + sys.exit(0) + elif opt == '-p' or opt == '--path': + NET_PATH = arg + elif opt == '-a' or opt == '--addrspace': + ADDR_SPACE = arg + elif opt == '-c' or opt == '--clean': + CLEAN = True + +ADDR_SPACE = ''.join(sorted(set(ADDR_SPACE))) + +if len(ADDR_SPACE) < 2: + print('Error: Address space must contain at least 2 addresses.') + sys.exit(1) + +for addr in ADDR_SPACE: + if addr not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + print('Error: Addresses must be capital letters from the 26-element English alphabet.') + sys.exit(1) + +if (NET_PATH[-1] != '/') and (NET_PATH[-1] != '\\'): NET_PATH += '/' + +if not os.access(NET_PATH, os.F_OK): + print('Error: Cannot access path ' + NET_PATH) + sys.exit(1) + +print('--------------------------------------------') +print('Network is running with the following input:') +print(' Network path: ' + NET_PATH) +print(' Address space: ' + ADDR_SPACE) +print(' Clean-up requested: ', CLEAN) +print('--------------------------------------------') + +# create folders for addresses if needed +for addr in ADDR_SPACE: + addr_dir = NET_PATH + addr + if not os.path.exists(addr_dir): + print('Folder for address ' + addr + ' does not exist. Trying to create it... ', end='') + os.mkdir(addr_dir) + os.mkdir(addr_dir + '/IN') + os.mkdir(addr_dir + '/OUT') + print('Done.') + +# if program was called with --clean, perform clean-up here +# go through the addr folders and delete messages +if CLEAN: + for addr in ADDR_SPACE: + in_dir = NET_PATH + addr + '/IN' + for f in os.listdir(in_dir): os.remove(in_dir + '/' + f) + out_dir = NET_PATH + addr + '/OUT' + for f in os.listdir(out_dir): os.remove(out_dir + '/' + f) + +# initialize state (needed for tracking last read messages from OUT dirs) +last_read = {} +for addr in ADDR_SPACE: + out_dir = NET_PATH + addr + '/OUT' + msgs = sorted(os.listdir(out_dir)) + last_read[addr] = len(msgs) - 1 + +# main loop +print('Main loop started, quit with pressing CTRL-C...') +while True: + time.sleep(TIMEOUT) + for src in ADDR_SPACE: + msg, dsts = read_msg(src) # read outgoing message + if dsts != '': # if read returned a message... + if dsts == '+': dsts = ADDR_SPACE # handle broadcast address + + for dst in dsts: # for all destinations of the message... + if dst in ADDR_SPACE: # destination must be a valid address + write_msg(dst, msg) # write incoming message diff --git a/receiver.py b/receiver.py new file mode 100644 index 0000000..2cdf33e --- /dev/null +++ b/receiver.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +#receiver.py + +import os, sys, getopt, time +from netinterface import network_interface + +NET_PATH = './' +OWN_ADDR = 'B' + +# ------------ +# main program +# ------------ + +try: + opts, args = getopt.getopt(sys.argv[1:], shortopts='hp:a:', longopts=['help', 'path=', 'addr=']) +except getopt.GetoptError: + print('Usage: python receiver.py -p -a ') + sys.exit(1) + +for opt, arg in opts: + if opt == '-h' or opt == '--help': + print('Usage: python receiver.py -p -a ') + sys.exit(0) + elif opt == '-p' or opt == '--path': + NET_PATH = arg + elif opt == '-a' or opt == '--addr': + OWN_ADDR = arg + +if (NET_PATH[-1] != '/') and (NET_PATH[-1] != '\\'): NET_PATH += '/' + +if not os.access(NET_PATH, os.F_OK): + print('Error: Cannot access path ' + NET_PATH) + sys.exit(1) + +if len(OWN_ADDR) > 1: OWN_ADDR = OWN_ADDR[0] + +if OWN_ADDR not in network_interface.addr_space: + print('Error: Invalid address ' + OWN_ADDR) + sys.exit(1) + +# main loop +netif = network_interface(NET_PATH, OWN_ADDR) +print('Main loop started...') +while True: +# Calling receive_msg() in non-blocking mode ... +# status, msg = netif.receive_msg(blocking=False) +# if status: print(msg) # if status is True, then a message was returned in msg +# else: time.sleep(2) # otherwise msg is empty + +# Calling receive_msg() in blocking mode ... + status, msg = netif.receive_msg(blocking=True) # when returns, status is True and msg contains a message + print(msg.decode('utf-8')) + diff --git a/sender.py b/sender.py new file mode 100644 index 0000000..83bc75f --- /dev/null +++ b/sender.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +#sender.py + +import os, sys, getopt, time +from netinterface import network_interface + +NET_PATH = './' +OWN_ADDR = 'A' + +# ------------ +# main program +# ------------ + +try: + opts, args = getopt.getopt(sys.argv[1:], shortopts='hp:a:', longopts=['help', 'path=', 'addr=']) +except getopt.GetoptError: + print('Usage: python sender.py -p -a ') + sys.exit(1) + +for opt, arg in opts: + if opt == '-h' or opt == '--help': + print('Usage: python sender.py -p -a ') + sys.exit(0) + elif opt == '-p' or opt == '--path': + NET_PATH = arg + elif opt == '-a' or opt == '--addr': + OWN_ADDR = arg + +if (NET_PATH[-1] != '/') and (NET_PATH[-1] != '\\'): NET_PATH += '/' + +if not os.access(NET_PATH, os.F_OK): + print('Error: Cannot access path ' + NET_PATH) + sys.exit(1) + +if len(OWN_ADDR) > 1: OWN_ADDR = OWN_ADDR[0] + +if OWN_ADDR not in network_interface.addr_space: + print('Error: Invalid address ' + OWN_ADDR) + sys.exit(1) + +# main loop +netif = network_interface(NET_PATH, OWN_ADDR) +print('Main loop started...') +while True: + msg = input('Type a message: ') + dst = input('Type a destination address: ') + + netif.send_msg(dst, msg.encode('utf-8')) + + if input('Continue? (y/n): ') == 'n': break