Selaa lähdekoodia

Actualizar 'Pythonv1.py'

yosoyhendrix 1 viikko sitten
vanhempi
sitoutus
475debbea4
1 muutettua tiedostoa jossa 179 lisäystä ja 214 poistoa
  1. 179 214
      Pythonv1.py

+ 179 - 214
Pythonv1.py

@@ -1,268 +1,233 @@
 # -*- coding: utf-8 -*-
 
 # ==============================================================================
-# SCRIPT DE PROXY MULTIFILAMENTADO CON RESPUESTAS ROTATIVAS
+# PROXY MULTIFILAMENTADO DE ALTA DISPONIBILIDAD - VERSIÓN PYTHON 3
 #
-# - 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
+# CARACTERÍSTICAS PRINCIPALES:
+# 1. Rotación de Mensajes: Cada respuesta HTTP 101 varía cíclicamente.
+# 2. Soporte Dual Stack: Escucha en IPv4 (0.0.0.0) e IPv6 (::) simultáneamente.
+# 3. Thread-Safe: Uso de Locks para evitar colisiones entre hilos.
+# 4. Gestión de Logs: Rotación automática para evitar saturación de disco.
+# 5. Rate-Limiting: Protección básica contra ataques de inundación por IP.
 # ==============================================================================
+
 #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 /root/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
+import itertools
 
-# ==============================================================================
-# CONFIGURACIÓN GLOBAL Y SETUP DE LOGGING
-# ==============================================================================
+# --- CONFIGURACIÓN DE RED ---
 IPV4_ADDR = '0.0.0.0'
 IPV6_ADDR = '::'
+LISTENING_PORT = int(sys.argv[1]) if sys.argv[1:] else 8080
 
-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'
+# --- CONFIGURACIÓN DE SEGURIDAD ---
+MAX_CONNECTIONS = 1000
+CONNECTION_COOLDOWN = 5  # Segundos entre conexiones de la misma IP
+TIMEOUT = 60             # Tiempo de espera para sockets
+BUFLEN = 16384           # 16KB de buffer para mayor velocidad
 
-# 💡 LISTA DE MENSAJES PARA ROTAR
-# Puedes añadir todos los que quieras aquí.
-MENSAJES_ROTATIVOS = [
+# --- LISTA DE MENSAJES ROTATIVOS ---
+# Se enviarán en la línea de estado de la respuesta HTTP 101
+MENSAJES = [
     "Pfsense",
     "OPNsense",
     "VyOS",
+    "Claro",
+    "Windows Server",
+    "BSD Free",
+    "VyOS",
+    "Altice",
+    "Viva",
+    "Google",
+    "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 
+# Inicialización del iterador cíclico y lock de seguridad
+mensaje_cycle = itertools.cycle(MENSAJES)
+cycle_lock = threading.Lock()
 
-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()
+# --- CONFIGURACIÓN DE LOGS ---
+LOG_FILE = 'proxy_server.log'
+def setup_logger():
+    logger = logging.getLogger("ProxyLogger")
     logger.setLevel(logging.INFO)
-    logger.addHandler(file_handler)
-    logger.addHandler(console_handler)
+    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
+    
+    # Rotación de logs: 5MB por archivo, máximo 3 backups
+    handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=5*1024*1024, backupCount=3)
+    handler.setFormatter(formatter)
+    
+    # Salida a consola
+    console = logging.StreamHandler()
+    console.setFormatter(formatter)
+    
+    logger.addHandler(handler)
+    logger.addHandler(console)
     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
+log = setup_logger()
+conn_limit = threading.Semaphore(MAX_CONNECTIONS)
+ip_history = {}
+ip_lock = threading.Lock()
 
-                    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__()
+    def __init__(self, client_socket, addr):
+        super().__init__(daemon=True)
         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()
+        self.log_id = f"{addr[0]}:{addr[1]}"
+
+    def finish(self):
+        """Cierra todos los recursos de la conexión de forma segura."""
+        for s in [self.client, self.target]:
+            if s:
+                try:
+                    s.close()
+                except:
+                    pass
+        conn_limit.release()
 
     def run(self):
         try:
+            # 1. Leer petición inicial
             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
+            if not data:
+                return
 
-            # 💡 Lógica de rotación: obtenemos el siguiente mensaje de la lista
-            with iterator_lock:
-                mensaje_actual = next(mensaje_iterator)
+            # 2. Determinar destino (Header X-Real-Host o Default)
+            headers = data.decode('latin-1', errors='ignore')
+            target_info = self.extract_header(headers, 'X-Real-Host') or "127.0.0.1:22"
             
-            # 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()
+            # 3. Obtener mensaje rotativo de forma segura
+            with cycle_lock:
+                msg = next(mensaje_cycle)
 
-            logger.info(f"[{self.log_prefix}] Usando mensaje: '{mensaje_actual}'")
+            # 4. Intentar conectar al destino
+            if not self.connect_to_target(target_info):
+                return
+
+            # 5. Enviar respuesta con el mensaje rotado
+            resp = (f"HTTP/1.1 101 {msg}\r\n"
+                    f"Connection: Upgrade\r\n"
+                    f"Upgrade: websocket\r\n\r\n").encode('utf-8')
+            self.client.sendall(resp)
             
-            self.connect_target(host_port)
-            self.client.sendall(response_dinamica)
-            self.do_tunnel()
+            log.info(f"[{self.log_id}] Conectado a {target_info} | Mensaje: {msg}")
+
+            # 6. Iniciar túnel bidireccional
+            self.bridge()
 
         except Exception as e:
-            logger.error(f"Error en {self.log_prefix}: {e}")
+            log.error(f"[{self.log_id}] Error: {e}")
         finally:
-            self.close()
-            self.server.remove_conn(self)
+            self.finish()
 
-    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 extract_header(self, text, header_name):
+        for line in text.split('\r\n'):
+            if line.lower().startswith(header_name.lower() + ":"):
+                return line.split(':', 1)[1].strip()
+        return None
 
-    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]
+    def connect_to_target(self, target_str):
+        try:
+            parts = target_str.split(':')
+            host = parts[0]
+            port = int(parts[1]) if len(parts) > 1 else 22
+            
+            # Soporte IPv4 e IPv6 automático
+            infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+            for res in infos:
+                af, socktype, proto, canonname, sa = res
+                try:
+                    self.target = socket.socket(af, socktype, proto)
+                    self.target.settimeout(10)
+                    self.target.connect(sa)
+                    return True
+                except:
+                    continue
+            return False
+        except:
+            return False
+
+    def bridge(self):
+        """Mueve datos entre cliente y destino usando select."""
+        sockets = [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)
+            readable, _, error = select.select(sockets, [], sockets, TIMEOUT)
+            if error or not readable:
+                break
+            
+            for s in readable:
+                other = self.target if s is self.client else self.client
+                try:
+                    chunk = s.recv(BUFLEN)
+                    if not chunk:
+                        return
+                    other.sendall(chunk)
+                except:
+                    return
 
 def main():
-    server = Server(LISTENING_PORT)
-    server.start()
+    # Crear sockets de escucha
+    listeners = []
+    for addr_info in [(socket.AF_INET, IPV4_ADDR), (socket.AF_INET6, IPV6_ADDR)]:
+        try:
+            s = socket.socket(addr_info[0], socket.SOCK_STREAM)
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            if addr_info[0] == socket.AF_INET6:
+                s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
+            s.bind((addr_info[1], LISTENING_PORT))
+            s.listen(128)
+            listeners.append(s)
+            log.info(f"Escuchando en {addr_info[1]}:{LISTENING_PORT}")
+        except Exception as e:
+            log.warning(f"No se pudo abrir socket en {addr_info[1]}: {e}")
+
+    if not listeners:
+        log.critical("No hay sockets disponibles. Saliendo.")
+        return
+
+    log.info("Servidor Proxy iniciado. Presiona Ctrl+C para salir.")
+    
     try:
-        while True: time.sleep(2)
+        while True:
+            r, _, _ = select.select(listeners, [], [])
+            for s in r:
+                client, addr = s.accept()
+                
+                # Control de frecuencia por IP
+                ip = addr[0]
+                with ip_lock:
+                    now = time.time()
+                    last = ip_history.get(ip, 0)
+                    if now - last < CONNECTION_COOLDOWN:
+                        client.close()
+                        continue
+                    ip_history[ip] = now
+
+                # Control de límite total
+                if not conn_limit.acquire(blocking=False):
+                    log.warning(f"Límite de conexiones alcanzado ({MAX_CONNECTIONS})")
+                    client.close()
+                    continue
+
+                # Iniciar manejador
+                ConnectionHandler(client, addr).start()
     except KeyboardInterrupt:
-        server.close()
+        log.info("Cerrando servidor...")
+    finally:
+        for s in listeners:
+            s.close()
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()