| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- # -*- coding: utf-8 -*-
- # ==============================================================================
- # PROXY MULTIFILAMENTADO DE ALTA DISPONIBILIDAD - VERSIÓN PYTHON 3
- #
- # 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 logging
- import logging.handlers
- import itertools
- # --- CONFIGURACIÓN DE RED ---
- IPV4_ADDR = '0.0.0.0'
- IPV6_ADDR = '::'
- LISTENING_PORT = int(sys.argv[1]) if sys.argv[1:] else 8080
- # --- 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 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"
- ]
- # Inicialización del iterador cíclico y lock de seguridad
- mensaje_cycle = itertools.cycle(MENSAJES)
- cycle_lock = threading.Lock()
- # --- CONFIGURACIÓN DE LOGS ---
- LOG_FILE = 'proxy_server.log'
- def setup_logger():
- logger = logging.getLogger("ProxyLogger")
- logger.setLevel(logging.INFO)
- 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
- log = setup_logger()
- conn_limit = threading.Semaphore(MAX_CONNECTIONS)
- ip_history = {}
- ip_lock = threading.Lock()
- class ConnectionHandler(threading.Thread):
- def __init__(self, client_socket, addr):
- super().__init__(daemon=True)
- self.client = client_socket
- self.addr = addr
- self.target = None
- 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
- # 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"
-
- # 3. Obtener mensaje rotativo de forma segura
- with cycle_lock:
- msg = next(mensaje_cycle)
- # 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)
-
- log.info(f"[{self.log_id}] Conectado a {target_info} | Mensaje: {msg}")
- # 6. Iniciar túnel bidireccional
- self.bridge()
- except Exception as e:
- log.error(f"[{self.log_id}] Error: {e}")
- finally:
- self.finish()
- 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_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, _, 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():
- # 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:
- 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:
- log.info("Cerrando servidor...")
- finally:
- for s in listeners:
- s.close()
- if __name__ == "__main__":
- main()
|