213 lines
7.3 KiB
Python
213 lines
7.3 KiB
Python
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
|
|
FRAME_SIZE = MTU + 42 # MTU is the size of payload, and the frame itself takes 42 bytes
|
|
|
|
########################## 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 FRAME_SIZE # 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(FRAME_SIZE) # 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
|
|
|
|
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 FRAME_SIZE # Read what it used for in "Defining global variables" section above
|
|
|
|
|
|
while True:
|
|
|
|
buffer_ = NIC.read(FRAME_SIZE) # 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 FRAME_SIZE
|
|
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(FRAME_SIZE) # 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()
|