Просмотр исходного кода

Actualizar 'Proxy_VPN.py'

:

Auto-Baneo en Memoria (Mini Fail2Ban interno): Si alguien escanea tu IP pública o intenta conectarse a lo loco al puerto 8080 (flood), el script consume un poco de CPU al rechazarlo. Le he añadido un contador: si una IP viola el cooldown (CONNECTION_COOLDOWN) 3 veces seguidas, el script la mete en una "Lista Negra" en la memoria RAM y bloquea sus paquetes silenciosamente sin procesar nada más. ¡Protege tu CPU!

Estadísticas de Tráfico (Consumo de Datos): ¿No te gustaría saber cuántos megabytes o gigabytes consumió una conexión cuando se cierra? Le he añadido un contador de bytes. Cuando Netmod se desconecte, el log te dirá exactamente cuánto tráfico pasó por ese túnel (Ej: Tráfico total: 15.4 MB).

Modo Sigilo (Fake Web Server): He dejado preparada una opción que responde con un falso "Error 400 Bad Request" si un navegador web normal intenta entrar por curiosidad a tu puerto 8080, despistando a los escáneres de puertos.
yosoyhendrix 3 дней назад
Родитель
Сommit
e9d0b563ac
1 измененных файлов с 93 добавлено и 75 удалено
  1. 93 75
      Proxy_VPN.py

+ 93 - 75
Proxy_VPN.py

@@ -11,19 +11,22 @@ 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 (22 o 223) y OpenSSH
 LOG_FILE = "/root/proxy.log"
 MAX_LOG_SIZE = 10 * 1024 * 1024  # 10MB
 
 # --- CONFIGURACIÓN DE SEGURIDAD AVANZADA ---
 MAX_CONNECTIONS = 1000
-CONNECTION_COOLDOWN = 0.7
+CONNECTION_COOLDOWN = 0.5
 TIMEOUT = 60
 BUFLEN = 16384
 
-# --- FILTRADO DE ACCESO ---
+# --- FILTRADO DE ACCESO Y AUTO-BANEO ---
 ALLOWED_IPS = [] 
 BLOCKED_HOSTS = ['ads.doubleclick.net', 'telemetry.microsoft.com']
+AUTO_BAN_STRIKES = 3  # Intentos de flood antes de banear la IP en memoria
+banned_ips_memory = set() # Lista negra dinámica en RAM
+ip_strikes = {}
 
 # --- SECCIÓN DE CUSTOM HEADERS (Inyectados en la respuesta 101) ---
 CUSTOM_HEADERS = {
@@ -31,50 +34,35 @@ CUSTOM_HEADERS = {
     "X-Forwarded-For": "127.0.0.1",
     "Content-Type": "text/html; charset=UTF-8",
     "Proxy-Connection": "keep-alive",
-    "Cache-Control": "no-cache",
-    "X-Real-IP": "127.0.0.1"
+    "Cache-Control": "no-cache"
 }
 
 # --- MENSAJES ROTATIVOS (Status 101) ---
 MENSAJES = [
     "🚀 CONEXION ESTABLECIDA",
     "🛡️ SEGURIDAD ACTIVA",
-  	"Pfsense",
-    "OPNsense",
-    "VyOS",
-    "Claro",
-    "Windows Server",
-    "BSD Free",
-    "VyOS",
-    "Altice",
-    "Viva",
-    "Google",
-    "VyOS",
-    "TNSR",
-    "🔋 OPTIMIZACION",
-    "🌐 ACCESO OK"
+    "🔋 OPTIMIZACION SISTEMA",
+    "🌐 ACCESO NETMOD OK"
 ]
 mensaje_cycle = itertools.cycle(MENSAJES)
 cycle_lock = threading.Lock()
 
 # --- 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 ""
-    log_entry = f"[{timestamp}]{client_info} {msg}\n"
-    
-    # Escribir en archivo y consola
     try:
+        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 ""
+        log_entry = f"[{timestamp}]{client_info} {msg}\n"
+        
         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}")
+    except:
+        pass
 
 # --- GESTIÓN DE ESTADO ---
 active_connections = 0
@@ -87,6 +75,8 @@ class ConnectionHandler(threading.Thread):
         self.client = client_socket
         self.addr = addr
         self.target = None
+        self.tx_bytes = 0 # Bytes transmitidos (Subida)
+        self.rx_bytes = 0 # Bytes recibidos (Bajada)
 
     def build_http_response(self, status_msg):
         headers_str = "".join([f"{k}: {v}\r\n" for k, v in CUSTOM_HEADERS.items()])
@@ -100,47 +90,60 @@ class ConnectionHandler(threading.Thread):
 
     def run(self):
         global active_connections
+        client_ip = self.addr[0]
+        
         try:
-            log("--- Nueva conexión recibida ---", self.addr)
-
-            # 1. Validación de IP
-            if ALLOWED_IPS and self.addr[0] not in ALLOWED_IPS:
+            # 1. Filtro de Auto-Baneo (Evita gasto de CPU en atacantes)
+            if client_ip in banned_ips_memory:
+                return # Cierra silenciosamente
+                
+            # 2. Validación de Lista Blanca
+            if ALLOWED_IPS and client_ip not in ALLOWED_IPS:
                 log("🚫 Conexión rechazada: IP no autorizada", self.addr)
                 return
 
-            # 2. Control de Rate-Limiting / Cooldown
+            # 3. Control de Rate-Limiting e Inteligencia de Auto-Ban
             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)
+            if client_ip in ip_cooldowns and (now - ip_cooldowns[client_ip]) < CONNECTION_COOLDOWN:
+                # Si viola el cooldown, sumamos un strike
+                ip_strikes[client_ip] = ip_strikes.get(client_ip, 0) + 1
+                if ip_strikes[client_ip] >= AUTO_BAN_STRIKES:
+                    banned_ips_memory.add(client_ip)
+                    log(f"⛔ IP Baneada en memoria RAM por Flood/Spam", self.addr)
                 return
-            ip_cooldowns[self.addr[0]] = now
-
-            # 3. Payload inicial
-            self.client.settimeout(10)
-            payload = self.client.recv(BUFLEN)
-            if not payload:
-                return
-
-            # 4. Conexión al destino (Dropbear/SSH)
-            with cycle_lock:
-                current_status = next(mensaje_cycle)
-
+                
+            ip_cooldowns[client_ip] = now
+            ip_strikes[client_ip] = 0 # Resetea strikes si conecta legalmente
+
+            # 4. Leer payload inicial de Netmod (Absorción)
+            self.client.settimeout(5)
+            try:
+                payload = self.client.recv(BUFLEN)
+                if not payload: return
+                
+                # 💡 Modo Sigilo: Si un navegador intenta acceder (ej. GET / HTTP/1.1) y NO es Netmod.
+                # Puedes quitar el comentario de las 3 líneas de abajo para activarlo si quieres que Netmod sea obligatorio.
+                # if b"Upgrade: websocket" not in payload and b"HTTP/" in payload:
+                #     self.client.sendall(b"HTTP/1.1 400 Bad Request\r\nServer: nginx\r\n\r\n")
+                #     return
+            except: return
+
+            # 5. Conectar al destino (SSH/Dropbear)
             log(f"Intentando conectar a {SSH_HOST}:{SSH_PORT}...", self.addr)
+            self.target = socket.create_connection((SSH_HOST, SSH_PORT), timeout=10)
             
-            # Soporte IPv4/IPv6 para la conexión interna
-            self.target = socket.create_connection((SSH_HOST, SSH_PORT), timeout=TIMEOUT)
+            # 6. Elegir mensaje rotativo y responder al cliente
+            with cycle_lock:
+                current_status = next(mensaje_cycle)
             
-            log("Conexión exitosa al destino SSH", self.addr)
-
-            # 5. Respuesta HTTP 101
             self.client.sendall(self.build_http_response(current_status))
-            log(f"✅ Protocolo Upgrade: {current_status}", self.addr)
+            log(f"✅ Túnel activo: {current_status}", self.addr)
 
-            # 6. Túnel bidireccional
+            # 7. Túnel bidireccional puro
             self.tunnel()
 
         except Exception as e:
-            log(f"❌ Error: {str(e)}", self.addr)
+            log(f"❌ Error: {e}", self.addr)
         finally:
             with conn_lock:
                 active_connections -= 1
@@ -152,7 +155,7 @@ class ConnectionHandler(threading.Thread):
         sockets = [self.client, self.target]
         
         while True:
-            readable, _, error = select.select(sockets, [], sockets, TIMEOUT)
+            readable, _, error = select.select(sockets, [], sockets, 300)
             if error or not readable:
                 break
             
@@ -162,43 +165,58 @@ class ConnectionHandler(threading.Thread):
                     if not data:
                         return
                     
-                    dest = self.target if s is self.client else self.client
-                    dest.sendall(data)
+                    if s is self.client:
+                        self.target.sendall(data)
+                        self.tx_bytes += len(data) # Sumar subida
+                    else:
+                        self.client.sendall(data)
+                        self.rx_bytes += len(data) # Sumar bajada
                 except:
                     return
 
     def cleanup(self):
+        # Calcula el tráfico total al cerrar la conexión
+        total_mb = (self.tx_bytes + self.rx_bytes) / (1024 * 1024)
+        if total_mb > 0.01: # Solo registrar si hubo tráfico real (> 10KB)
+            log(f"[*] Conexión finalizada. Tráfico consumido: {total_mb:.2f} MB", self.addr)
+            
         for s in [self.client, self.target]:
             if s:
-                try:
-                    s.close()
-                except:
-                    pass
+                try: s.close()
+                except: pass
 
 def main():
     global active_connections
     
-    # Configuración de Socket Dual (IPv4 e IPv6)
+    # Intentamos socket dual (IPv6/IPv4)
     try:
-        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)
+        addr_info = socket.getaddrinfo(None, LISTENING_PORT, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+        addr_info.sort(key=lambda x: x[0] == socket.AF_INET6, reverse=True)
         
+        af, socktype, proto, canonname, sa = addr_info[0]
+        server = socket.socket(af, socktype, proto)
+        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        
+        if af == socket.AF_INET6:
+            try: server.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+            except: pass
+
+        server.bind(sa)
         server.listen(200)
-        log(f"🔥 Servidor Robusto Iniciado en Puerto {LISTENING_PORT} (Dual IPv4/IPv6)")
-        log(f"🛡️ Logs: {LOG_FILE} (Autolimitado a 10MB)")
+        
+        log(f"=====================================================")
+        log(f"🔥 Servidor Robusto Iniciado en Puerto {LISTENING_PORT}")
+        log(f"🎯 Destino Interno: {SSH_HOST}:{SSH_PORT}")
+        log(f"🛡️ Logs limitados a 10MB en {LOG_FILE}")
+        log(f"=====================================================")
 
         while True:
             client, addr = server.accept()
-            
             with conn_lock:
                 if active_connections >= MAX_CONNECTIONS:
-                    log("⛔ Máximo de conexiones alcanzado.", addr)
                     client.close()
                     continue
                 active_connections += 1
-            
             ConnectionHandler(client, addr).start()
             
     except Exception as e: