From a05c003fbcbc17a8add7f6a4ad74af3933657883 Mon Sep 17 00:00:00 2001 From: user-1 Date: Sun, 12 Mar 2023 05:28:27 +0000 Subject: [PATCH] Project files added --- client.py | 151 ++++++++++++++++++++++++++++++++++++++ server.py | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 client.py create mode 100644 server.py diff --git a/client.py b/client.py new file mode 100644 index 0000000..60c17ec --- /dev/null +++ b/client.py @@ -0,0 +1,151 @@ +import socket +import struct +import logging +import threading + +from pytun import TunTapDevice, IFF_TAP + + +########################## Defining global variables + + +ADDRESS = "127.0.0.1"; # Address to connect to +PORT = 9999; # Port to connect to + +NIC_NAME = "client"; # Name for the TAP interface that gonna be created + +CONFIGURE_NIC = True; # If you prefer to set up the interface manualy set it to False + +NIC_IP = "10.0.0.2"; # IP to be set for the interface +NIC_NETMASK = "255.255.255.0"; # Netmask to be used with the interface + +MTU = 1500; # MTU that gonna be set for the interface, please don't change it if you don't know what you are doing + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s %(message)s\n') # Sets logging level and format. Possible levels: DEBUG, INFO, WARNING, ERROR, CRITICAL Read python logging module documentation for more detailes + + +# Don't change if you don't know what are you doing +connected = False # Stores the connection status + +########################## Defining global variables (END) + + + + + +########################## Defining functions + + +def recive(socket, NIC): # A function that reads socket for new data and writes this data to the NIC + + global MTU # Read what they are used for in "Defining global variables" section above + global connected + + buffer_ = " " # Setting buffer to " " so it's not empty and the loop can start + + while buffer_ != b'': # If buffer_ is b'' that meas the server closed the socket + + buffer_ = socket.recv(MTU) # reciving data from the server + + if buffer_ != b'': # If buffer_ is still not empty, write it to the NIC + + NIC.write(buffer_) + + logging.debug(f'Recived: {buffer_}') + + logging.info("Connection with the server lost. Reconnecting...") + connection = False + + + + +def send(socket, NIC): # The function reads the NIC for new frames and send them to the server + + global MTU # Read what they are used for in "Defining global variables" section above + global connected + + while connected == True: + + buffer_ = NIC.read(MTU) # Reads outgoing frames + socket.send(buffer_) # Sends the frames + + logging.debug(f'Sent: {buffer_}') + + + + +def main(): + + global NIC_NAME # Read what they are used for in "Defining global variables" section above + global MTU + global CONFIGURE_NIC + global NIC_IP + global NIC_NETMASK + global ADDRESS + global PORT + global connected + + + + + # Setting up a TAP interface + NIC = TunTapDevice(flags=IFF_TAP, name=NIC_NAME) + NIC.mtu = MTU + + if CONFIGURE_NIC: + + NIC.addr = NIC_IP + NIC.netmask = NIC_NETMASK + + NIC.up() + + + + + # Setting up a socket + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + + logging.info(f'Connecting to {ADDRESS}:{PORT}') + + while True: + + try: + + client_socket.connect((ADDRESS, PORT)) + + connected = True + + logging.info(f'Connected {ADDRESS}:{PORT}') + + sending_thread = threading.Thread(target=send, args=(client_socket, NIC)) + sending_thread.start() + + recive(client_socket, NIC) + + except Exception: # If there is an error execute the code below + + logging.debug("An error occurred during connection. Reconnecting") + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Recreating socket + + except KeyboardInterrupt: # if there is a keyboard interrupt (CTRL + C) it gonna close the socket and the NIC and break the loop + + NIC.close() + client_socket.close() + break + + + + + + + +########################## Defining functions (END) + + + + + +if __name__ == '__main__': # Starting the main function + main() + diff --git a/server.py b/server.py new file mode 100644 index 0000000..e0e860c --- /dev/null +++ b/server.py @@ -0,0 +1,214 @@ +import socket +import struct +import logging +import threading + +from pytun import TunTapDevice, IFF_TAP + + + + + +########################## Setting global variables + + +ADDRESS = "127.0.0.1" # Address to listen on +PORT = 9999 # Port to listen on + +NIC_NAME = "server" # Name for the TAP interface that gonna be created + +CONFIGURE_NIC = True # If you prefer to set up the interface manualy set it to False + +NIC_IP = "10.0.0.1" # IP to be set for the interface +NIC_NETMASK = "255.255.255.0" # Netmask to be used with the interface + +MTU = 1500 # MTU that gonna be set for the interface, please don't change it if you don't know what you are doing + +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s %(message)s\n') # Sets logging level and format. Possible levels: DEBUG, INFO, WARNING, ERROR, CRITICAL Read python logging module documentation for more detailes + + +## Don't change this if you not sure what you are doing +client = {} # Dictionary of clients, Key is the MAC address of a client, Value is an object of class Client +client_thread = {} # Client thread contains a Key that is MAC of a client, and object of type Thread, that runs "recive" function + + +########################## Setting global variables (END) + + + + + +########################## Defining functions + +class Client: # Class Client contains a client info and some methods to work with the client + + def __init__(self, socket, address, NIC, MAC): # Constructor for class Client + + self.socket = socket + self.address = address + self.NIC = NIC + self.MAC = MAC + + + + + def recive(self): # Function that listens the client socket for a frames and writes the frames into the NIC. It runs in a loop + + # Importing global variables + global MTU # Read what they are used for in "Defining global variables" section above + global client + global client_thread + + buffer_ = " " # Setting buffer to " " so it's not empty and the loop can start + + while buffer_ != b'': # If buffer_ is b'' that meas that client closed the socket, and needs to be deleted + + buffer_ = self.socket.recv(MTU) # reciving data from the client + + if buffer_ != b'': # If buffer_ is still not empty, write it to the server's NIC + self.NIC.write(buffer_) + logging.debug(f'Recived: From {self.address} {buffer_}') + + logging.info(f'Connection closed: {self.address}') + + del client[self.MAC] # Deleting the client from the dictionary, and removing it's "recive" thread + del client_thread[self.MAC] + + + + + def send(self, frame): # Just sends the data to the client's socket + + global MTU # Read what it used for in "Defining global variables" section above + + + self.socket.send(frame) + + logging.debug(f'Sent: To {self.address} {frame}') + + + + + +def send(client, NIC): # Not to be confused with "send" from the Client class, this one gets all outgoing frames and decides what client it needs to be sent to + + global MTU # Read what it used for in "Defining global variables" section above + + + while True: + + buffer_ = NIC.read(MTU) # Reads NIC for outgoing frames + dst = struct.unpack("! 4x 6s", buffer_[:10]) # Extracts destination MAC address from the frame + + if (client.get(dst)): # If client with the destination MAC address exists, send the frame to the client + + client[dst].send(buffer_) + logging.debug(f'Sending: To {dst} {buffer_}') + + else: # If client with the destination MAC address doesn't exist, then send the frame to all of the clients (Send to broadcast) + logging.warning(f'Client with MAC {dst} not found, sending to broadcast') + + for mac in client.keys(): # Going throught all of the clients, and send the frame to each of them + + try: # The "try" needs to be here, in case a client closes connection, so instead of crashing the thread, it will print the error message + + logging.debug(f'Sending a broadcast message to {mac} {buffer_}') + client[mac].send(buffer_) + + except: + + logging.error('Cannot send the frame to {mac}') + + + + + + +def main(): + + # Importing global variables + + global NIC_NAME # Read what they are used for in "Defining global variables" section above + global MTU + global CONFIGURE_NIC + global NIC_IP + global NIC_NETMASK + global ADDRESS + global PORT + global client + global client_thread + + + + # Setting up a TAP interface + NIC = TunTapDevice(flags=IFF_TAP, name=NIC_NAME) + NIC.mtu = MTU + + if CONFIGURE_NIC: + + NIC.addr = NIC_IP + NIC.netmask = NIC_NETMASK + + NIC.up() + + + + + # Setting up a socket + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.bind((ADDRESS, PORT)) + + + + + + # Creating a "routing" thread, it will read the outgoing frames, and decide to who it will be sent + routing = threading.Thread(target=send, args=(client, NIC)); + FirstRun = False # Used to only start thread at the end of first cicle of the main loop, if you know more elegant way to do this, please let me know + + + # Main loop + + try: + + while True: + + logging.info("Listening for a new client") + + server_socket.listen(1) # Listening + new_client, new_client_address = server_socket.accept() # When there is a client accepting the connection request and creating socket for the new client (new_client), new_client_addrss contains... new client's address + buffer_ = new_client.recv(MTU) # Reciving a frame and putting it to the buffer + new_client_mac = struct.unpack("! 10x 6s", buffer_[:16]) # Extracting a source MAC address, to use it later as a Key for the new client + + logging.info(f'New client connected: {new_client_address} MAC: {new_client_mac}') + + client[new_client_mac] = Client(new_client, new_client_address, NIC, new_client_mac) # Creating an object of type Client with all the information for the new client, and putting it to "client" dictionary, using the client MAC address as a Key to access the client + new_client_thread = threading.Thread(target=client[new_client_mac].recive) # Creating a new thread that runs "recive" method, it will read client's socket for new data and write the data to the server's NIC. It runs in a loop + client_thread[new_client_mac] = new_client_thread # Putting the thread to "client_thread" and useing client's MAC as a Key, and the "new_client_thread" object as a value + client_thread[new_client_mac].start() # Starting the thread + + logging.debug(f'Client list: {client.keys()}') + + if FirstRun is False: # If loop runs the first time, start the routing function, it is required as the function will fail without a client's data (Might be fixed soon) + routing.start() + FirstRun = True + + + + + except KeyboardInterrupt: # When CTRL + C pressed run the code below + + NIC.close() # Closing everything that needs to be closed + server_socket.close() + + # Main loop (END) + + +########################## Defining functions (END) + + + + + +if __name__ == '__main__': # Starting the Main function + main()