|
|
@@ -1,16 +1,8 @@
|
|
|
# -*- 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.
|
|
|
+# PROXY MULTIFILAMENTADO PROFESIONAL - VERSIÓN ULTRA-ROBUSTA (PYTHON 3)
|
|
|
# ==============================================================================
|
|
|
-
|
|
|
#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
|
|
|
|
|
|
@@ -27,15 +19,28 @@ import itertools
|
|
|
IPV4_ADDR = '0.0.0.0'
|
|
|
IPV6_ADDR = '::'
|
|
|
LISTENING_PORT = int(sys.argv[1]) if sys.argv[1:] else 8080
|
|
|
+DEFAULT_HOST = '127.0.0.1:223'
|
|
|
|
|
|
-# --- CONFIGURACIÓN DE SEGURIDAD ---
|
|
|
+# --- CONFIGURACIÓN DE SEGURIDAD AVANZADA ---
|
|
|
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
|
|
|
+CONNECTION_COOLDOWN = 0.5
|
|
|
+TIMEOUT = 60
|
|
|
+BUFLEN = 16384
|
|
|
+
|
|
|
+# 🛡️ LISTA BLANCA DE IPs (Si está vacía, permite todas)
|
|
|
+# Ejemplo: ALLOWED_IPS = ['127.0.0.1', '192.168.1.50']
|
|
|
+ALLOWED_IPS = []
|
|
|
+
|
|
|
+# 🚫 LISTA NEGRA DE DOMINIOS (Bloquea conexiones a estos hosts)
|
|
|
+BLOCKED_HOSTS = ['sitio-prohibido.com', 'anuncios.malware.net']
|
|
|
|
|
|
-# --- LISTA DE MENSAJES ROTATIVOS ---
|
|
|
-# Se enviarán en la línea de estado de la respuesta HTTP 101
|
|
|
+# 📑 ENCABEZADOS A INYECTAR (Se añaden a la comunicación con el destino)
|
|
|
+CUSTOM_HEADERS = {
|
|
|
+ "X-Proxy-Agent": "Gemini-Ultra-Robust-v3",
|
|
|
+ "X-Forwarded-For-Proxy": "True"
|
|
|
+}
|
|
|
+
|
|
|
+# --- MENSAJES ROTATIVOS ---
|
|
|
MENSAJES = [
|
|
|
"Pfsense",
|
|
|
"OPNsense",
|
|
|
@@ -51,25 +56,19 @@ MENSAJES = [
|
|
|
"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'
|
|
|
+# --- SISTEMA DE LOGS ---
|
|
|
+LOG_FILE = 'proxy_avanzado.log'
|
|
|
def setup_logger():
|
|
|
- logger = logging.getLogger("ProxyLogger")
|
|
|
+ logger = logging.getLogger("ProxyAvanzado")
|
|
|
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 = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=10*1024*1024, backupCount=5)
|
|
|
handler.setFormatter(formatter)
|
|
|
-
|
|
|
- # Salida a consola
|
|
|
console = logging.StreamHandler()
|
|
|
console.setFormatter(formatter)
|
|
|
-
|
|
|
logger.addHandler(handler)
|
|
|
logger.addHandler(console)
|
|
|
return logger
|
|
|
@@ -85,50 +84,59 @@ class ConnectionHandler(threading.Thread):
|
|
|
self.client = client_socket
|
|
|
self.addr = addr
|
|
|
self.target = None
|
|
|
- self.log_id = f"{addr[0]}:{addr[1]}"
|
|
|
+ self.log_id = "{}:{}".format(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
|
|
|
+ try: s.close()
|
|
|
+ except: pass
|
|
|
conn_limit.release()
|
|
|
|
|
|
+ def is_ip_allowed(self, ip):
|
|
|
+ if not ALLOWED_IPS: return True
|
|
|
+ return ip in ALLOWED_IPS
|
|
|
+
|
|
|
+ def is_host_blocked(self, target_str):
|
|
|
+ host = target_str.split(':')[0].lower()
|
|
|
+ return host in BLOCKED_HOSTS
|
|
|
+
|
|
|
def run(self):
|
|
|
try:
|
|
|
- # 1. Leer petición inicial
|
|
|
+ # Validar IP en lista blanca
|
|
|
+ if not self.is_ip_allowed(self.addr[0]):
|
|
|
+ log.warning("[{}] IP no autorizada. Cerrando.".format(self.log_id))
|
|
|
+ return
|
|
|
+
|
|
|
data = self.client.recv(BUFLEN)
|
|
|
- if not data:
|
|
|
+ if not data: return
|
|
|
+
|
|
|
+ headers_text = data.decode('latin-1', errors='ignore')
|
|
|
+ target_info = self.extract_header(headers_text, 'X-Real-Host') or DEFAULT_HOST
|
|
|
+
|
|
|
+ # Validar dominio bloqueado
|
|
|
+ if self.is_host_blocked(target_info):
|
|
|
+ log.warning("[{}] Intento de acceso a host bloqueado: {}".format(self.log_id, target_info))
|
|
|
+ self.client.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n")
|
|
|
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):
|
|
|
+ log.error("[{}] Error conectando a {}".format(self.log_id, 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')
|
|
|
+ # Respuesta al cliente
|
|
|
+ resp = "HTTP/1.1 101 {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n".format(msg).encode('utf-8')
|
|
|
self.client.sendall(resp)
|
|
|
|
|
|
- log.info(f"[{self.log_id}] Conectado a {target_info} | Mensaje: {msg}")
|
|
|
+ log.info("[{}] OK -> {} | Msg: {}".format(self.log_id, target_info, msg))
|
|
|
|
|
|
- # 6. Iniciar túnel bidireccional
|
|
|
self.bridge()
|
|
|
|
|
|
except Exception as e:
|
|
|
- log.error(f"[{self.log_id}] Error: {e}")
|
|
|
+ log.error("[{}] Error: {}".format(self.log_id, e))
|
|
|
finally:
|
|
|
self.finish()
|
|
|
|
|
|
@@ -140,94 +148,78 @@ class ConnectionHandler(threading.Thread):
|
|
|
|
|
|
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
|
|
|
+ host, port = (target_str.split(':') + [22])[:2]
|
|
|
+ port = int(port)
|
|
|
|
|
|
- # 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
|
|
|
+ af, socktype, proto, _, sa = res
|
|
|
try:
|
|
|
self.target = socket.socket(af, socktype, proto)
|
|
|
self.target.settimeout(10)
|
|
|
self.target.connect(sa)
|
|
|
return True
|
|
|
except:
|
|
|
+ if self.target: self.target.close()
|
|
|
continue
|
|
|
return False
|
|
|
- except:
|
|
|
- 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:
|
|
|
+ try:
|
|
|
+ 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
|
|
|
chunk = s.recv(BUFLEN)
|
|
|
- if not chunk:
|
|
|
- return
|
|
|
+ if not chunk: return
|
|
|
other.sendall(chunk)
|
|
|
- except:
|
|
|
- return
|
|
|
+ except: break
|
|
|
|
|
|
def main():
|
|
|
- # Crear sockets de escucha
|
|
|
listeners = []
|
|
|
- for addr_info in [(socket.AF_INET, IPV4_ADDR), (socket.AF_INET6, IPV6_ADDR)]:
|
|
|
+ for af, addr in [(socket.AF_INET, IPV4_ADDR), (socket.AF_INET6, IPV6_ADDR)]:
|
|
|
try:
|
|
|
- s = socket.socket(addr_info[0], socket.SOCK_STREAM)
|
|
|
+ s = socket.socket(af, 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))
|
|
|
+ if af == socket.AF_INET6:
|
|
|
+ try: s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
|
|
+ except: pass
|
|
|
+ s.bind((addr, LISTENING_PORT))
|
|
|
s.listen(128)
|
|
|
listeners.append(s)
|
|
|
- log.info(f"Escuchando en {addr_info[1]}:{LISTENING_PORT}")
|
|
|
+ log.info("Escuchando en {}:{}".format(addr, LISTENING_PORT))
|
|
|
except Exception as e:
|
|
|
- log.warning(f"No se pudo abrir socket en {addr_info[1]}: {e}")
|
|
|
+ log.debug("Interfaz {} ocupada: {}".format(addr, e))
|
|
|
|
|
|
if not listeners:
|
|
|
- log.critical("No hay sockets disponibles. Saliendo.")
|
|
|
+ log.critical("No se pudo iniciar ningun listener.")
|
|
|
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:
|
|
|
+ if now - ip_history.get(ip, 0) < 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...")
|
|
|
+ log.info("Servidor detenido.")
|
|
|
finally:
|
|
|
- for s in listeners:
|
|
|
- s.close()
|
|
|
+ for s in listeners: s.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main()
|