I have a custom TCP server on port 7777 and a client that connects to that server and performs some tasks. The server accepts multiple client connections.
./server./client
Since the Server and Client code are already built I can't modify it. This TCP server can only run on the local system but I want to expose this to the internet so that clients outside the network can also connect (I can't open an incoming port on the router).
My solution is to build a tunnel to aws server.
[local: ./server:7777 -> {proxy client 7777 to 8888}] -> (internet) -> [AWS {proxy server: 8888 --> new server: 7777}]
So ./server
is running locally on port 7777 port I created a simple Python script that connects to port 7777 and also connects to AWS server 8888 and transfers the data between them. I also do the same at AWS where a proxy server running at 8888 transfers data to a custom server running at 7777. Now I can use my ./client
and provide it with the address of the AWS machine it works.
Issue currently is that my solution only works for 1 connection, if two clients are connected there is confusion between proxy server and new server as I am using queue to share data
Server.py -> running on AWS
import socketimport threadingfrom queue import Queueclass TCPServer: def __init__(self, host, port, to_queue, from_queue): self.host = host self.port = port self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.bind((self.host, self.port)) self.server.listen(5) # Increased backlog for incoming connections self.to_queue = to_queue self.from_queue = from_queue self.stop_event = threading.Event() # Event for signaling thread termination def handle_client_read(self, client_socket, address): try: while not self.stop_event.is_set(): data = client_socket.recv(4096) if not data: print(f"handle_client_read Tunnel closed by {address}") break print(f"handle_client_read Tunnel from {address} {self.host} {self.port}: {data.decode()}") # Write received data to a queue self.to_queue.put((data, client_socket)) except Exception as e: print(f"Error handle_client_read: {e}") print(f"Closed connection from {address}") def handle_client_write(self): try: while not self.stop_event.is_set(): # Read data from a queue data, client_socket = self.from_queue.get() if data: # Send the read data to the client client_socket.sendall(data) except Exception as e: print(f"Error handle_client_write: {e}") def close(self): self.stop_event.set() def start(self): try: while not self.stop_event.is_set(): print(f"Server listening on {self.host}:{self.port}") client_socket, address = self.server.accept() print(f"Accepted connection from {address}") thread_read = threading.Thread(target=self.handle_client_read, args=(client_socket, address)) thread_write = threading.Thread(target=self.handle_client_write) thread_read.start() thread_write.start() except Exception as e: print(f"Error in start: {e}") finally: self.close()if __name__ == "__main__": server_host = '127.0.0.1' server_port1 = 8888 server_port2 = 7777 tunnel_server_queue = Queue() server_tunnel_queue = Queue() server1 = TCPServer(server_host, server_port1, tunnel_server_queue, server_tunnel_queue) server2 = TCPServer(server_host, server_port2, server_tunnel_queue, tunnel_server_queue) server_thread1 = threading.Thread(target=server1.start) server_thread2 = threading.Thread(target=server2.start) server_thread1.start() server_thread2.start() server_thread1.join() server_thread2.join()
Client.py -> running locally.
import socketimport threadingfrom queue import Queueclass TCPClient: def __init__(self, host, port, to_queue, from_queue): self.host = host self.port = port self.to_queue = to_queue self.from_queue = from_queue self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client.connect((self.host, self.port)) print(f"Connected to server at {self.host}:{self.port}") self.stop_event = threading.Event() # Event for signaling thread termination def handle_client_read(self): try: while not self.stop_event.is_set(): data = self.client.recv(4096) if self.stop_event.is_set(): break if not data: print(f"Received EOF from {self.host}:{self.port}") break print(f"Received: {data.decode()}") self.to_queue.put(data) except Exception as e: print(f"Error receiving data from {self.host}:{self.port}: {e}") self.close() def handle_client_write(self): try: while not self.stop_event.is_set(): data = self.from_queue.get() if self.stop_event.is_set(): break if data: print(f"Sending: {data.decode()}") self.client.sendall(data) except Exception as e: print(f"Error sending data to {self.host}:{self.port}: {e}") self.close() def close(self): self.stop_event.set() self.from_queue.put(-1)if __name__ == "__main__": # base server 7777 -> 8888 -> 7777 local_host = '127.0.0.1' aws_host = 'address' server_port1 = 8888 server_port2 = 7777 tunnel_local_queue = Queue() local_tunnel_queue = Queue() local_client = TCPClient(local_host, server_port2, local_tunnel_queue, tunnel_local_queue) tunnel_client = TCPClient(aws_host, server_port1, tunnel_local_queue, local_tunnel_queue) local_read_thread = threading.Thread(target=local_client.handle_client_read) local_write_thread = threading.Thread(target=local_client.handle_client_write) local_read_thread.start() local_write_thread.start() tunnel_read_thread = threading.Thread(target=tunnel_client.handle_client_read) tunnel_write_thread = threading.Thread(target=tunnel_client.handle_client_write) tunnel_read_thread.start() tunnel_write_thread.start() local_read_thread.join() local_write_thread.join() tunnel_read_thread.join() tunnel_write_thread.join()
I am using two queues shared between two servers and two clients to transfer data between them.
I am not sure if this is the right solution, so I am looking for a better solution.
Currently, the issue is here:
def handle_client_write(self): try: while not self.stop_event.is_set(): # Read data from a queue data, client_socket = self.from_queue.get() if data: # Send the read data to the client client_socket.sendall(data) except Exception as e: print(f"Error handle_client_write: {e}")
as this thread is called by all clients so when we get data in the queue it is hard to tell which client it belongs to as I can't modify the protocol.