import socket import threading import json import traceback import sys import re from collections import defaultdict class PortForwardServer: def __init__(self, InternalDataPort=5000, AllowedPortRange="5001-5500", MaxPortsPerClient=5, Key="07A36AEF1907843"): self.InternalDataPort = InternalDataPort self.AllowedPortRange = AllowedPortRange self.MaxPortsPerClient = MaxPortsPerClient self.Key = Key self.ParsePortRange() self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.Clients = {} self.ClientLocks = defaultdict(threading.Lock) self.ForwardMap = {} self.ForwardLocks = defaultdict(threading.Lock) self.Running = True self.MessageSeparator = b'|||' def ParsePortRange(self): match = re.match(r'^(\d+)-(\d+)$', self.AllowedPortRange) if not match: raise ValueError("Invalid port range format") self.MinPort = int(match.group(1)) self.MaxPort = int(match.group(2)) if self.MinPort >= self.MaxPort or self.MinPort < 1 or self.MaxPort > 65535: raise ValueError("Invalid port range values") def IsPortAllowed(self, port): return self.MinPort <= port <= self.MaxPort def Start(self): try: self.ServerSocket.bind(('0.0.0.0', self.InternalDataPort)) self.ServerSocket.listen(5) print(f"Server started on port {self.InternalDataPort}") acceptThread = threading.Thread(target=self.AcceptClients, daemon=True) acceptThread.start() while self.Running: cmd = input("Enter 'exit' to stop server: ") if cmd.lower() == 'exit': self.Running = False break self.Stop() except Exception as e: print(f"Server start error: {e}") traceback.print_exc() def Stop(self): self.Running = False self.ServerSocket.close() for clientId, clientData in self.Clients.items(): with self.ClientLocks[clientId]: if clientData['socket']: try: clientData['socket'].close() except: pass with self.ForwardLocks[clientId]: for forwardId, forwardData in clientData['forwards'].items(): try: forwardData['server'].close() except: pass print("Server stopped") def AcceptClients(self): while self.Running: try: clientSocket, addr = self.ServerSocket.accept() print(f"New client connection from {addr}") clientThread = threading.Thread(target=self.HandleClient, args=(clientSocket, addr), daemon=True) clientThread.start() except Exception as e: if self.Running: print(f"Accept error: {e}") traceback.print_exc() def HandleClient(self, clientSocket, addr): clientId = f"{addr[0]}:{addr[1]}" self.Clients[clientId] = {'socket': clientSocket, 'forwards': {}, 'addr': addr, 'buffer': b''} try: while self.Running: clientSocket.settimeout(30) try: data = clientSocket.recv(4096) if not data: print(f"Client {clientId} disconnected") break self.Clients[clientId]['buffer'] += data self.ProcessBuffer(clientId) except socket.timeout: continue except Exception as e: print(f"Client communication error: {e}") traceback.print_exc() break finally: with self.ClientLocks[clientId]: if clientId in self.Clients: del self.Clients[clientId] with self.ForwardLocks[clientId]: for forwardId in list(self.ForwardMap.keys()): if forwardId.startswith(clientId): del self.ForwardMap[forwardId] try: clientSocket.close() except: pass print(f"Client {clientId} handler cleaned up") def ProcessBuffer(self, clientId): clientData = self.Clients.get(clientId) if not clientData: return while self.MessageSeparator in clientData['buffer']: msgEnd = clientData['buffer'].index(self.MessageSeparator) messageData = clientData['buffer'][:msgEnd] clientData['buffer'] = clientData['buffer'][msgEnd + len(self.MessageSeparator):] try: message = json.loads(messageData.decode('utf-8')) self.ProcessClientMessage(clientId, message) except json.JSONDecodeError: print(f"Invalid JSON from client {clientId}") self.SendToClient(clientId, {'type': 'error', 'message': 'Invalid JSON'}) except Exception as e: print(f"Error processing message: {e}") traceback.print_exc() def ProcessClientMessage(self, clientId, message): if message.get('type') == 'auth': self.HandleAuth(clientId, message) elif message.get('type') == 'forward_request': self.HandleForwardRequest(clientId, message) elif message.get('type') == 'data': self.HandleData(clientId, message) elif message.get('type') == 'close_forward': self.HandleCloseForward(clientId, message) else: self.SendToClient(clientId, {'type': 'error', 'message': 'Unknown message type'}) def HandleAuth(self, clientId, message): clientData = self.Clients.get(clientId) if not clientData: return if message.get('key') == self.Key: clientData['authenticated'] = True self.SendToClient(clientId, {'type': 'auth_response', 'success': True}) print(f"Client {clientId} authenticated successfully") else: self.SendToClient(clientId, {'type': 'auth_response', 'success': False, 'message': 'Invalid key'}) clientData['socket'].close() print(f"Client {clientId} failed authentication") def HandleForwardRequest(self, clientId, message): clientData = self.Clients.get(clientId) if not clientData or not clientData.get('authenticated', False): self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Not authenticated'}) return with self.ClientLocks[clientId]: if len(clientData['forwards']) >= self.MaxPortsPerClient: self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Max ports per client reached'}) return targetPort = message.get('target_port') mode = message.get('mode', 'tcp').upper() if not self.IsPortAllowed(targetPort): self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Target port not allowed'}) return forwardId = f"{clientId}:{targetPort}" if forwardId in self.ForwardMap: self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Port already in use'}) return try: if mode == 'TCP': forwardServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) forwardServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) forwardServer.bind(('0.0.0.0', targetPort)) forwardServer.listen(5) with self.ClientLocks[clientId]: clientData['forwards'][forwardId] = {'server': forwardServer, 'mode': mode, 'connections': {}} with self.ForwardLocks[clientId]: self.ForwardMap[forwardId] = clientId threading.Thread(target=self.AcceptForwardConnections, args=(clientId, forwardId, forwardServer), daemon=True).start() self.SendToClient(clientId, {'type': 'forward_response', 'success': True, 'target_port': targetPort, 'forward_id': forwardId}) print(f"Forward created: {forwardId}") else: self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Unsupported mode'}) except Exception as e: print(f"Forward creation error: {e}") traceback.print_exc() self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': str(e)}) def AcceptForwardConnections(self, clientId, forwardId, forwardServer): try: while self.Running and forwardId in self.ForwardMap and self.ForwardMap[forwardId] == clientId: forwardServer.settimeout(1) try: conn, addr = forwardServer.accept() connId = f"{addr[0]}:{addr[1]}" print(f"New connection to forward {forwardId} from {connId}") with self.ClientLocks[clientId]: clientData = self.Clients.get(clientId) if not clientData or forwardId not in clientData['forwards']: conn.close() continue clientData['forwards'][forwardId]['connections'][connId] = conn self.SendToClient(clientId, { 'type': 'new_connection', 'forward_id': forwardId, 'conn_id': connId }) threading.Thread(target=self.ForwardToClient, args=(clientId, forwardId, connId, conn), daemon=True).start() except socket.timeout: continue except Exception as e: print(f"Forward accept error: {e}") traceback.print_exc() finally: try: forwardServer.close() except: pass print(f"Forward listener {forwardId} stopped") def ForwardToClient(self, clientId, forwardId, connId, conn): try: while self.Running: conn.settimeout(1) try: data = conn.recv(4096) if not data: break self.SendToClient(clientId, { 'type': 'data', 'forward_id': forwardId, 'conn_id': connId, 'data': data.hex() }) except socket.timeout: if not self.Running or clientId not in self.Clients: break continue except Exception as e: print(f"Forward to client error: {e}") traceback.print_exc() break finally: try: conn.close() except: pass with self.ClientLocks[clientId]: clientData = self.Clients.get(clientId) if clientData and forwardId in clientData['forwards']: if connId in clientData['forwards'][forwardId]['connections']: del clientData['forwards'][forwardId]['connections'][connId] self.SendToClient(clientId, { 'type': 'close_connection', 'forward_id': forwardId, 'conn_id': connId }) print(f"Connection {connId} to forward {forwardId} closed") def HandleData(self, clientId, message): forwardId = message.get('forward_id') connId = message.get('conn_id') dataHex = message.get('data') if not all([forwardId, connId, dataHex]): return try: data = bytes.fromhex(dataHex) with self.ClientLocks[clientId]: clientData = self.Clients.get(clientId) if not clientData or forwardId not in clientData['forwards']: return forwardData = clientData['forwards'][forwardId] if connId not in forwardData['connections']: return conn = forwardData['connections'][connId] conn.sendall(data) except Exception as e: print(f"Data handling error: {e}") traceback.print_exc() def HandleCloseForward(self, clientId, message): forwardId = message.get('forward_id') if not forwardId: return with self.ClientLocks[clientId]: clientData = self.Clients.get(clientId) if clientData and forwardId in clientData['forwards']: try: clientData['forwards'][forwardId]['server'].close() except: pass del clientData['forwards'][forwardId] with self.ForwardLocks[clientId]: if forwardId in self.ForwardMap: del self.ForwardMap[forwardId] print(f"Forward {forwardId} closed by client") def SendToClient(self, clientId, message): try: with self.ClientLocks[clientId]: clientData = self.Clients.get(clientId) if not clientData or not clientData['socket']: return clientSocket = clientData['socket'] data = json.dumps(message).encode('utf-8') + self.MessageSeparator clientSocket.sendall(data) except Exception as e: print(f"Error sending to client {clientId}: {e}") traceback.print_exc() def main(): config = { "InternalDataPort": 5000, "AllowedPortRange": "5001-5500", "MaxPortsPerClient": 5, "Key": "07A36AEF1907843" } if len(sys.argv) > 1: try: with open(sys.argv[1], 'r') as f: config.update(json.load(f)) except Exception as e: print(f"Error loading config file: {e}") traceback.print_exc() server = PortForwardServer( InternalDataPort=int(config["InternalDataPort"]), AllowedPortRange=config["AllowedPortRange"], MaxPortsPerClient=int(config["MaxPortsPerClient"]), Key=config["Key"] ) server.Start() if __name__ == "__main__": main()