|
|
@@ -6,41 +6,42 @@ import select
|
|
|
import sys
|
|
|
import time
|
|
|
import itertools
|
|
|
+import os
|
|
|
|
|
|
# --- CONFIGURACIÓN BASE ---
|
|
|
LISTENING_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
|
|
SSH_HOST = '127.0.0.1'
|
|
|
-SSH_PORT = 223 # Compatible con Dropbear o OpenSSH local
|
|
|
+SSH_PORT = 22 # Compatible con Dropbear o OpenSSH local
|
|
|
+LOG_FILE = "/root/proxy.log"
|
|
|
+MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB
|
|
|
|
|
|
# --- CONFIGURACIÓN DE SEGURIDAD AVANZADA ---
|
|
|
MAX_CONNECTIONS = 1000
|
|
|
-CONNECTION_COOLDOWN = 0.5 # Segundos entre conexiones por IP
|
|
|
+CONNECTION_COOLDOWN = 0.5
|
|
|
TIMEOUT = 60
|
|
|
-BUFLEN = 16384 # Tamaño óptimo para sistemas embebidos
|
|
|
+BUFLEN = 16384
|
|
|
|
|
|
# --- FILTRADO DE ACCESO ---
|
|
|
-# 🛡️ LISTA BLANCA DE IPs (Vacia = Permitir todas)
|
|
|
ALLOWED_IPS = []
|
|
|
-# 🚫 LISTA NEGRA DE DOMINIOS (Hosts bloqueados)
|
|
|
BLOCKED_HOSTS = ['ads.doubleclick.net', 'telemetry.microsoft.com']
|
|
|
|
|
|
-# --- SECCIÓN DE CUSTOM HEADERS ---
|
|
|
-# Estos se añaden a la respuesta HTTP 101 enviada a NetMod
|
|
|
+# --- SECCIÓN DE CUSTOM HEADERS (Inyectados en la respuesta 101) ---
|
|
|
CUSTOM_HEADERS = {
|
|
|
"Server": "nginx/1.21.0",
|
|
|
"X-Forwarded-For": "127.0.0.1",
|
|
|
"Content-Type": "text/html; charset=UTF-8",
|
|
|
"Proxy-Connection": "keep-alive",
|
|
|
- "Cache-Control": "no-cache"
|
|
|
+ "Cache-Control": "no-cache",
|
|
|
+ "X-Real-IP": "127.0.0.1"
|
|
|
}
|
|
|
|
|
|
# --- MENSAJES ROTATIVOS (Status 101) ---
|
|
|
MENSAJES = [
|
|
|
"🚀 CONEXION ESTABLECIDA",
|
|
|
"🛡️ SEGURIDAD ACTIVA",
|
|
|
- "🔋 OPTIMIZACION",
|
|
|
- "🌐 ACCESO OK",
|
|
|
- "Pfsense",
|
|
|
+ "🔋 OPTIMIZACION DROPBEAR",
|
|
|
+ "🌐 ACCESO NETMOD OK",
|
|
|
+ "Pfsense",
|
|
|
"OPNsense",
|
|
|
"VyOS",
|
|
|
"Claro",
|
|
|
@@ -56,13 +57,26 @@ MENSAJES = [
|
|
|
mensaje_cycle = itertools.cycle(MENSAJES)
|
|
|
cycle_lock = threading.Lock()
|
|
|
|
|
|
-# --- SISTEMA DE LOGS ---
|
|
|
+# --- SISTEMA DE LOGS CON AUTO-LIMPIEZA ---
|
|
|
def log(msg, addr=None):
|
|
|
+ # Verificar tamaño del log y limpiar si excede el límite
|
|
|
+ if os.path.exists(LOG_FILE) and os.path.getsize(LOG_FILE) > MAX_LOG_SIZE:
|
|
|
+ with open(LOG_FILE, 'w') as f:
|
|
|
+ f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] LOG REINICIADO (Límite 10MB alcanzado)\n")
|
|
|
+
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
client_info = f" [{addr[0]}]" if addr else ""
|
|
|
- print(f"[{timestamp}]{client_info} {msg}")
|
|
|
+ log_entry = f"[{timestamp}]{client_info} {msg}\n"
|
|
|
+
|
|
|
+ # Escribir en archivo y consola
|
|
|
+ try:
|
|
|
+ with open(LOG_FILE, 'a') as f:
|
|
|
+ f.write(log_entry)
|
|
|
+ print(log_entry.strip())
|
|
|
+ except Exception as e:
|
|
|
+ print(f"Error escribiendo log: {e}")
|
|
|
|
|
|
-# --- GESTIÓN DE ESTADO GLOBAL ---
|
|
|
+# --- GESTIÓN DE ESTADO ---
|
|
|
active_connections = 0
|
|
|
conn_lock = threading.Lock()
|
|
|
ip_cooldowns = {}
|
|
|
@@ -75,7 +89,6 @@ class ConnectionHandler(threading.Thread):
|
|
|
self.target = None
|
|
|
|
|
|
def build_http_response(self, status_msg):
|
|
|
- """Construye la respuesta HTTP compatible con NetMod"""
|
|
|
headers_str = "".join([f"{k}: {v}\r\n" for k, v in CUSTOM_HEADERS.items()])
|
|
|
response = (
|
|
|
f"HTTP/1.1 101 {status_msg}\r\n"
|
|
|
@@ -88,56 +101,59 @@ class ConnectionHandler(threading.Thread):
|
|
|
def run(self):
|
|
|
global active_connections
|
|
|
try:
|
|
|
- # 1. Validación de IP (Lista Blanca)
|
|
|
+ log("--- Nueva conexión recibida ---", self.addr)
|
|
|
+
|
|
|
+ # 1. Validación de IP
|
|
|
if ALLOWED_IPS and self.addr[0] not in ALLOWED_IPS:
|
|
|
- log("🚫 IP No autorizada bloqueada.", self.addr)
|
|
|
+ log("🚫 Conexión rechazada: IP no autorizada", self.addr)
|
|
|
return
|
|
|
|
|
|
- # 2. Control de Cooldown (Anti-Flood)
|
|
|
+ # 2. Control de Rate-Limiting / Cooldown
|
|
|
now = time.time()
|
|
|
if self.addr[0] in ip_cooldowns and (now - ip_cooldowns[self.addr[0]]) < CONNECTION_COOLDOWN:
|
|
|
+ log("⚠️ Conexión rechazada por rate-limiting", self.addr)
|
|
|
return
|
|
|
ip_cooldowns[self.addr[0]] = now
|
|
|
|
|
|
- # 3. Recibir Payload inicial de NetMod
|
|
|
- self.client.settimeout(5)
|
|
|
+ # 3. Payload inicial
|
|
|
+ self.client.settimeout(10)
|
|
|
payload = self.client.recv(BUFLEN)
|
|
|
if not payload:
|
|
|
return
|
|
|
|
|
|
- # 4. Rotar mensaje de status
|
|
|
+ # 4. Conexión al destino (Dropbear/SSH)
|
|
|
with cycle_lock:
|
|
|
current_status = next(mensaje_cycle)
|
|
|
|
|
|
- # 5. Conectar al servicio local (Dropbear)
|
|
|
- self.target = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
- self.target.settimeout(TIMEOUT)
|
|
|
- self.target.connect((SSH_HOST, SSH_PORT))
|
|
|
+ log(f"Intentando conectar a {SSH_HOST}:{SSH_PORT}...", self.addr)
|
|
|
+
|
|
|
+ # Soporte IPv4/IPv6 para la conexión interna
|
|
|
+ self.target = socket.create_connection((SSH_HOST, SSH_PORT), timeout=TIMEOUT)
|
|
|
+
|
|
|
+ log("Conexión exitosa al destino SSH", self.addr)
|
|
|
|
|
|
- # 6. Enviar respuesta HTTP con Headers a NetMod
|
|
|
+ # 5. Respuesta HTTP 101
|
|
|
self.client.sendall(self.build_http_response(current_status))
|
|
|
-
|
|
|
- log(f"✅ Inyectado: {current_status}", self.addr)
|
|
|
+ log(f"✅ Protocolo Upgrade: {current_status}", self.addr)
|
|
|
|
|
|
- # 7. Iniciar Túnel Bidireccional
|
|
|
+ # 6. Túnel bidireccional
|
|
|
self.tunnel()
|
|
|
|
|
|
except Exception as e:
|
|
|
- log(f"⚠️ Error: {str(e)[:50]}", self.addr)
|
|
|
+ log(f"❌ Error: {str(e)}", self.addr)
|
|
|
finally:
|
|
|
with conn_lock:
|
|
|
active_connections -= 1
|
|
|
self.cleanup()
|
|
|
|
|
|
def tunnel(self):
|
|
|
- """Maneja el tráfico binario SSH optimizado para CPU baja"""
|
|
|
self.client.settimeout(None)
|
|
|
self.target.settimeout(None)
|
|
|
- inputs = [self.client, self.target]
|
|
|
+ sockets = [self.client, self.target]
|
|
|
|
|
|
while True:
|
|
|
- readable, _, exceptional = select.select(inputs, [], inputs, TIMEOUT)
|
|
|
- if exceptional or not readable:
|
|
|
+ readable, _, error = select.select(sockets, [], sockets, TIMEOUT)
|
|
|
+ if error or not readable:
|
|
|
break
|
|
|
|
|
|
for s in readable:
|
|
|
@@ -146,9 +162,8 @@ class ConnectionHandler(threading.Thread):
|
|
|
if not data:
|
|
|
return
|
|
|
|
|
|
- # Enviar al extremo opuesto
|
|
|
- out = self.target if s is self.client else self.client
|
|
|
- out.sendall(data)
|
|
|
+ dest = self.target if s is self.client else self.client
|
|
|
+ dest.sendall(data)
|
|
|
except:
|
|
|
return
|
|
|
|
|
|
@@ -162,14 +177,17 @@ class ConnectionHandler(threading.Thread):
|
|
|
|
|
|
def main():
|
|
|
global active_connections
|
|
|
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
- server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
|
|
+ # Configuración de Socket Dual (IPv4 e IPv6)
|
|
|
try:
|
|
|
- server.bind(('0.0.0.0', LISTENING_PORT))
|
|
|
+ if socket.has_dualstack_ipv6():
|
|
|
+ server = socket.create_server(('::', LISTENING_PORT), family=socket.AF_INET6, dualstack_ipv6=True)
|
|
|
+ else:
|
|
|
+ server = socket.create_server(('0.0.0.0', LISTENING_PORT), family=socket.AF_INET)
|
|
|
+
|
|
|
server.listen(200)
|
|
|
- log(f"🔥 PROXY INICIADO EN PUERTO {LISTENING_PORT}")
|
|
|
- log(f"🎯 DESTINO: {SSH_HOST}:{SSH_PORT} (Dropbear)")
|
|
|
+ log(f"🔥 Servidor Robusto Iniciado en Puerto {LISTENING_PORT} (Dual IPv4/IPv6)")
|
|
|
+ log(f"🛡️ Logs: {LOG_FILE} (Autolimitado a 10MB)")
|
|
|
|
|
|
while True:
|
|
|
client, addr = server.accept()
|