# -*- coding: utf-8 -*- # ============================================================================== # SCRIPT DE PROXY MULTIFILAMENTADO CON RESPUESTAS ROTATIVAS # # - Basado en la versión robusta y estable. # - Añade rotación dinámica del mensaje de estado en la respuesta HTTP 101. # - Utiliza un iterador cíclico para cambiar el mensaje en cada nueva conexión. # # Creado por Gemini # ============================================================================== #screen -dmS badvpn2 /bin/badvpn-udpgw --listen-addr 127.0.0.1:7300 --max-clients 1000 --max-connections-for-client 100 #screen -dmS pydic-80 python3 /etc/VPS-MX/protocolo/Pythonv1.py 8080 import socket import threading import select import sys import time import os import logging import logging.handlers import itertools # 💡 Importado para la rotación de mensajes # ============================================================================== # CONFIGURACIÓN GLOBAL Y SETUP DE LOGGING # ============================================================================== IPV4_ADDR = '0.0.0.0' IPV6_ADDR = '::' if sys.argv[1:]: LISTENING_PORT = int(sys.argv[1]) else: LISTENING_PORT = 8080 PASS = '' PRIORITIZE_IPV4 = True CONNECTION_COOLDOWN_TIME = 10 MAX_CONNECTIONS = 1000 # Constantes de red BUFLEN = 4096 * 4 TIMEOUT = 60 DEFAULT_HOST = '127.0.0.1:223' # 💡 LISTA DE MENSAJES PARA ROTAR # Puedes añadir todos los que quieras aquí. MENSAJES_ROTATIVOS = [ "Pfsense", "OPNsense", "VyOS", "TNSR" ] # Creamos un iterador infinito que rota sobre la lista anterior mensaje_iterator = itertools.cycle(MENSAJES_ROTATIVOS) # Lock para asegurar que la extracción del siguiente mensaje sea segura entre hilos iterator_lock = threading.Lock() # Configuración del log LOG_FILE = '/root/proxy.log' MAX_LOG_SIZE = 5 * 1024 * 1024 BACKUP_COUNT = 5 def setup_logging(): log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') file_handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=MAX_LOG_SIZE, backupCount=BACKUP_COUNT) file_handler.setFormatter(log_format) console_handler = logging.StreamHandler() console_handler.setFormatter(log_format) logger = logging.getLogger() logger.setLevel(logging.INFO) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger logger = setup_logging() last_connection_times = {} last_connection_lock = threading.Lock() connection_limit_semaphore = threading.Semaphore(MAX_CONNECTIONS) # ============================================================================== # CLASE DEL SERVIDOR # ============================================================================== class Server(threading.Thread): def __init__(self, port): super().__init__() self.running = False self.port = port self.threads = [] self.threads_lock = threading.Lock() self.ipv4_socket = None self.ipv6_socket = None def run(self): logger.info("\n:-------PythonProxy-------:\n") logger.info(f"Listening port: {self.port}") logger.info(f"Mensajes configurados: {len(MENSAJES_ROTATIVOS)}\n") logger.info(":-------------------------:\n") try: self.ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ipv4_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ipv4_socket.bind((IPV4_ADDR, self.port)) self.ipv4_socket.listen(0) except socket.error as e: logger.error(f"Error IPv4: {e}") self.ipv4_socket = None try: self.ipv6_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.ipv6_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ipv6_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) self.ipv6_socket.bind((IPV6_ADDR, self.port, 0, 0)) self.ipv6_socket.listen(0) except socket.error as e: logger.error(f"Error IPv6: {e}") self.ipv6_socket = None if not self.ipv4_socket and not self.ipv6_socket: return self.running = True active_sockets = [s for s in [self.ipv4_socket, self.ipv6_socket] if s] try: while self.running: readable, _, _ = select.select(active_sockets, [], [], 2) for sock in readable: c, addr = sock.accept() client_ip = addr[0] current_time = time.time() with last_connection_lock: last_time = last_connection_times.get(client_ip, 0) if current_time - last_time < CONNECTION_COOLDOWN_TIME: c.close() continue last_connection_times[client_ip] = current_time if not connection_limit_semaphore.acquire(timeout=0): c.close() continue c.setblocking(1) conn = ConnectionHandler(c, self, addr) conn.start() self.add_conn(conn) except Exception as e: logger.error(f"Error servidor: {e}") finally: self.running = False def add_conn(self, conn): with self.threads_lock: if self.running: self.threads.append(conn) def remove_conn(self, conn): with self.threads_lock: if conn in self.threads: self.threads.remove(conn) def close(self): self.running = False with self.threads_lock: for c in list(self.threads): c.close() # ============================================================================== # CLASE MANEJADORA DE CONEXIONES # ============================================================================== class ConnectionHandler(threading.Thread): def __init__(self, client_socket, server, addr): super().__init__() self.client = client_socket self.server = server self.addr = addr self.log_prefix = f"{addr[0]}:{addr[1]}" self.target = None self.client_closed = False self.target_closed = True def close(self): try: if not self.client_closed: self.client.close() except: pass finally: self.client_closed = True try: if not self.target_closed: self.target.close() except: pass finally: self.target_closed = True connection_limit_semaphore.release() def run(self): try: data = self.client.recv(BUFLEN) if not data: return headers = data.decode('latin-1') host_port = self.find_header(headers, 'X-Real-Host') or DEFAULT_HOST # 💡 Lógica de rotación: obtenemos el siguiente mensaje de la lista with iterator_lock: mensaje_actual = next(mensaje_iterator) # Construimos la respuesta HTTP dinámica response_dinamica = f"HTTP/1.1 101 {mensaje_actual}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n".encode() logger.info(f"[{self.log_prefix}] Usando mensaje: '{mensaje_actual}'") self.connect_target(host_port) self.client.sendall(response_dinamica) self.do_tunnel() except Exception as e: logger.error(f"Error en {self.log_prefix}: {e}") finally: self.close() self.server.remove_conn(self) def find_header(self, head, header): aux = head.find(header + ': ') if aux == -1: return '' head = head[aux + len(header) + 2:] aux = head.find('\r\n') return head[:aux] def connect_target(self, host_port): i = host_port.find(':') host = host_port[:i] if i != -1 else host_port port = int(host_port[i+1:]) if i != -1 else 22 addr_info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM) for res in addr_info: af, socktype, proto, _, sa = res try: self.target = socket.socket(af, socktype, proto) self.target.connect(sa) self.target_closed = False return except: if self.target: self.target.close() raise RuntimeError("No se pudo conectar al destino") def do_tunnel(self): socs = [self.client, self.target] while True: readable, _, _ = select.select(socs, [], [], TIMEOUT) if not readable: break for sock in readable: data = sock.recv(BUFLEN) if not data: return if sock is self.target: self.client.send(data) else: self.target.sendall(data) def main(): server = Server(LISTENING_PORT) server.start() try: while True: time.sleep(2) except KeyboardInterrupt: server.close() if __name__ == '__main__': main()