python.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. # -*- coding: utf-8 -*-
  2. # ==============================================================================
  3. # SCRIPT DE PROXY MULTIFILAMENTADO CON LÍMITE DE CONEXIONES Y RATE-LIMITING
  4. #
  5. # - Este script añade un límite al número máximo de conexiones activas que
  6. # el servidor puede manejar en un momento dado.
  7. # - Es la solución más efectiva para prevenir la saturación de la CPU en
  8. # entornos donde la arquitectura asíncrona no es viable.
  9. # - La limitación de velocidad se mantiene para proteger contra ataques de
  10. # denegación de servicio.
  11. #
  12. # Creado por Gemini
  13. # ==============================================================================
  14. import socket
  15. import threading
  16. import select
  17. import sys
  18. import time
  19. import os
  20. import logging
  21. import logging.handlers
  22. # ==============================================================================
  23. # CONFIGURACIÓN GLOBAL Y SETUP DE LOGGING
  24. # ==============================================================================
  25. # Direcciones de escucha para ambos protocolos.
  26. IPV4_ADDR = '0.0.0.0'
  27. IPV6_ADDR = '::'
  28. # Puerto de escucha. Se puede pasar como argumento.
  29. if sys.argv[1:]:
  30. LISTENING_PORT = int(sys.argv[1])
  31. else:
  32. LISTENING_PORT = 80
  33. # Contraseña opcional para el proxy.
  34. PASS = ''
  35. # Controla la prioridad de conexión
  36. PRIORITIZE_IPV4 = True
  37. # 💡 CONFIGURACIÓN DE SEGURIDAD
  38. # Tiempo mínimo de espera (en segundos) entre dos conexiones de la misma IP.
  39. CONNECTION_COOLDOWN_TIME = 100
  40. # 💡 NUEVA CONFIGURACIÓN: Límite de conexiones activas
  41. MAX_CONNECTIONS = 200 # Máximo de conexiones simultáneas
  42. # Constantes
  43. BUFLEN = 4096 * 4
  44. TIMEOUT = 60
  45. DEFAULT_HOST = '127.0.0.1:22'
  46. RESPONSE = b'HTTP/1.1 101 Switching Protocols <strong>nftables 1.1.6</strong>\r\n\r\n'
  47. # Configuración del log
  48. LOG_FILE = '/root/proxy.log'
  49. MAX_LOG_SIZE = 5 * 1024 * 1024 # 5 MB
  50. BACKUP_COUNT = 5 # Cantidad de archivos de log a rotar
  51. def setup_logging():
  52. """Configura el logger profesional con rotación de archivos."""
  53. log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  54. file_handler = logging.handlers.RotatingFileHandler(
  55. LOG_FILE,
  56. maxBytes=MAX_LOG_SIZE,
  57. backupCount=BACKUP_COUNT
  58. )
  59. file_handler.setFormatter(log_format)
  60. console_handler = logging.StreamHandler()
  61. console_handler.setFormatter(log_format)
  62. logger = logging.getLogger()
  63. logger.setLevel(logging.INFO)
  64. logger.addHandler(file_handler)
  65. logger.addHandler(console_handler)
  66. return logger
  67. # Inicializar el logger
  68. logger = setup_logging()
  69. # 💡 Variables compartidas para la limitación de velocidad y el semáforo
  70. last_connection_times = {}
  71. last_connection_lock = threading.Lock()
  72. connection_limit_semaphore = threading.Semaphore(MAX_CONNECTIONS)
  73. # ==============================================================================
  74. # CLASE DEL SERVIDOR
  75. # Gestiona la creación de sockets y la aceptación de conexiones
  76. # ==============================================================================
  77. class Server(threading.Thread):
  78. def __init__(self, port):
  79. super().__init__()
  80. self.running = False
  81. self.port = port
  82. self.threads = []
  83. self.threads_lock = threading.Lock()
  84. self.ipv4_socket = None
  85. self.ipv6_socket = None
  86. def run(self):
  87. logger.info("\n:-------PythonProxy-------:\n")
  88. logger.info(f"Listening addr: {IPV4_ADDR} and {IPV6_ADDR}")
  89. logger.info(f"Listening port: {self.port}\n")
  90. logger.info(f"Límite de conexiones: {MAX_CONNECTIONS}\n")
  91. logger.info(":-------------------------:\n")
  92. # Intentar enlazar a IPv4
  93. try:
  94. self.ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  95. self.ipv4_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  96. self.ipv4_socket.bind((IPV4_ADDR, self.port))
  97. self.ipv4_socket.listen(0)
  98. logger.info(f"Esperando conexiones IPv4 en {IPV4_ADDR}:{self.port}")
  99. except socket.error as e:
  100. logger.error(f"No se pudo enlazar a IPv4 ({e})")
  101. if self.ipv4_socket:
  102. self.ipv4_socket.close()
  103. self.ipv4_socket = None
  104. # Intentar enlazar a IPv6
  105. try:
  106. self.ipv6_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  107. self.ipv6_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  108. self.ipv6_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
  109. self.ipv6_socket.bind((IPV6_ADDR, self.port, 0, 0))
  110. self.ipv6_socket.listen(0)
  111. logger.info(f"Esperando conexiones IPv6 en {IPV6_ADDR}:{self.port}")
  112. except socket.error as e:
  113. logger.error(f"No se pudo enlazar a IPv6 ({e})")
  114. if self.ipv6_socket:
  115. self.ipv6_socket.close()
  116. self.ipv6_socket = None
  117. if not self.ipv4_socket and not self.ipv6_socket:
  118. logger.critical("No se pudo iniciar el servidor. Saliendo.")
  119. return
  120. self.running = True
  121. active_sockets = []
  122. if self.ipv4_socket:
  123. active_sockets.append(self.ipv4_socket)
  124. if self.ipv6_socket:
  125. active_sockets.append(self.ipv6_socket)
  126. try:
  127. while self.running:
  128. readable, _, _ = select.select(active_sockets, [], [], 2)
  129. for sock in readable:
  130. c, addr = sock.accept()
  131. client_ip = addr[0]
  132. current_time = time.time()
  133. # 💡 Lógica del cooldown (rate-limiting)
  134. with last_connection_lock:
  135. last_time = last_connection_times.get(client_ip, 0)
  136. if current_time - last_time < CONNECTION_COOLDOWN_TIME:
  137. logger.warning(f"[{client_ip}] Conexión rechazada por rate-limiting.")
  138. c.close()
  139. continue
  140. last_connection_times[client_ip] = current_time
  141. # 💡 Intenta adquirir un "slot" del semáforo
  142. if not connection_limit_semaphore.acquire(timeout=0):
  143. logger.warning(f"[{client_ip}] Conexión rechazada. Límite de conexiones alcanzado.")
  144. c.close()
  145. continue
  146. c.setblocking(1)
  147. conn = ConnectionHandler(c, self, addr)
  148. conn.start()
  149. self.add_conn(conn)
  150. except Exception as e:
  151. logger.error(f"Error en el bucle principal del servidor: {e}")
  152. finally:
  153. self.running = False
  154. if self.ipv4_socket:
  155. self.ipv4_socket.close()
  156. if self.ipv6_socket:
  157. self.ipv6_socket.close()
  158. def add_conn(self, conn):
  159. """Añade un hilo de conexión a la lista de hilos activos de forma segura."""
  160. with self.threads_lock:
  161. if self.running:
  162. self.threads.append(conn)
  163. def remove_conn(self, conn):
  164. """Remueve un hilo de conexión de la lista de hilos activos de forma segura."""
  165. with self.threads_lock:
  166. if conn in self.threads:
  167. self.threads.remove(conn)
  168. def close(self):
  169. """Cierra el servidor y todos los hilos de conexión."""
  170. self.running = False
  171. with self.threads_lock:
  172. threads = list(self.threads)
  173. for c in threads:
  174. c.close()
  175. # ==============================================================================
  176. # CLASE MANEJADORA DE CONEXIONES
  177. # Gestiona la lógica de cada conexión de cliente en un hilo separado
  178. # ==============================================================================
  179. class ConnectionHandler(threading.Thread):
  180. def __init__(self, client_socket, server, addr):
  181. super().__init__()
  182. self.client_closed = False
  183. self.target_closed = True
  184. self.client = client_socket
  185. self.client_buffer = b''
  186. self.server = server
  187. self.addr = addr
  188. self.log_prefix = f"{addr[0]}:{addr[1]}"
  189. logger.info(f"Nueva conexión de {self.log_prefix}")
  190. self.target = None
  191. def close(self):
  192. """Cierra los sockets del cliente y el destino."""
  193. logger.info(f"Cerrando conexión {self.log_prefix}")
  194. try:
  195. if not self.client_closed:
  196. self.client.shutdown(socket.SHUT_RDWR)
  197. self.client.close()
  198. except Exception:
  199. pass
  200. finally:
  201. self.client_closed = True
  202. try:
  203. if not self.target_closed:
  204. self.target.shutdown(socket.SHUT_RDWR)
  205. self.target.close()
  206. except Exception:
  207. pass
  208. finally:
  209. self.target_closed = True
  210. # 💡 Libera el semáforo al cerrar la conexión
  211. connection_limit_semaphore.release()
  212. def run(self):
  213. try:
  214. self.client_buffer = self.client.recv(BUFLEN)
  215. if not self.client_buffer:
  216. return
  217. headers = self.client_buffer.decode('latin-1')
  218. logger.debug(f"Headers received from {self.log_prefix}:\n{headers.strip()}")
  219. host_port = self.find_header(headers, 'X-Real-Host')
  220. if not host_port:
  221. host_port = DEFAULT_HOST
  222. if self.find_header(headers, 'X-Split'):
  223. self.client.recv(BUFLEN)
  224. if host_port:
  225. passwd = self.find_header(headers, 'X-Pass')
  226. if PASS and passwd == PASS:
  227. logger.info(f"Autenticación exitosa para {self.log_prefix} -> {host_port}")
  228. self.method_connect(host_port)
  229. elif PASS and passwd != PASS:
  230. logger.warning(f"Fallo de autenticación para {self.log_prefix} -> {host_port}")
  231. self.client.send(b'HTTP/1.1 400 WrongPass!\r\n\r\n')
  232. elif host_port.startswith('127.0.0.1') or host_port.startswith('localhost'):
  233. logger.info(f"Conexión local permitida para {self.log_prefix} -> {host_port}")
  234. self.method_connect(host_port)
  235. else:
  236. logger.warning(f"Acceso denegado (sin contraseña) para {self.log_prefix} -> {host_port}")
  237. self.client.send(b'HTTP/1.1 403 Forbidden!\r\n\r\n')
  238. else:
  239. logger.error(f"Encabezado 'X-Real-Host' no encontrado en la conexión de {self.log_prefix}")
  240. self.client.send(b'HTTP/1.1 400 NoXRealHost!\r\n\r\n')
  241. except Exception as e:
  242. logger.error(f"Error inesperado en el hilo de conexión {self.log_prefix}: {e}")
  243. finally:
  244. self.close()
  245. self.server.remove_conn(self)
  246. def find_header(self, head, header):
  247. """Busca un encabezado en la solicitud HTTP."""
  248. try:
  249. aux = head.find(header + ': ')
  250. if aux == -1: return ''
  251. head = head[aux + len(header) + 2:]
  252. aux = head.find('\r\n')
  253. if aux == -1: return ''
  254. return head[:aux]
  255. except Exception as e:
  256. logger.error(f"Error al analizar el encabezado '{header}': {e}")
  257. return ''
  258. def connect_target(self, host_port):
  259. """Intenta conectarse al host de destino con una lógica de reconexión."""
  260. i = host_port.find(':')
  261. if i != -1:
  262. try:
  263. port = int(host_port[i+1:])
  264. host = host_port[:i]
  265. except ValueError:
  266. raise RuntimeError(f"Puerto inválido: {host_port[i+1:]}")
  267. else:
  268. host = host_port
  269. port = 22 # Puerto por defecto
  270. try:
  271. addr_info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
  272. except socket.gaierror as e:
  273. raise RuntimeError(f"Error de resolución de DNS para {host}: {e}")
  274. if PRIORITIZE_IPV4:
  275. prioritized_addrs = sorted(addr_info, key=lambda x: x[0] != socket.AF_INET)
  276. else:
  277. prioritized_addrs = addr_info
  278. logger.info(f"Intentando conectar a {host}:{port}. Direcciones disponibles: {[res[4] for res in prioritized_addrs]}")
  279. for res in prioritized_addrs:
  280. soc_family, soc_type, proto, _, address = res
  281. address_str = address[0]
  282. try:
  283. self.target = socket.socket(soc_family, soc_type, proto)
  284. self.target.connect(address)
  285. self.target_closed = False
  286. logger.info(f"Conexión exitosa a {address_str} (Familia: {soc_family})")
  287. return
  288. except socket.error as e:
  289. logger.warning(f"Error al conectar a {address_str}: {e}. Intentando la siguiente dirección...")
  290. if self.target:
  291. self.target.close()
  292. self.target = None
  293. if self.target is None:
  294. raise RuntimeError(f"No se pudo establecer una conexión con el host de destino: {host}")
  295. def method_connect(self, path):
  296. """Maneja el túnel de datos una vez que la conexión se ha establecido."""
  297. try:
  298. self.connect_target(path)
  299. self.client.sendall(RESPONSE)
  300. self.do_connect()
  301. except RuntimeError as e:
  302. logger.error(f"Error en la conexión del método CONNECT para {self.log_prefix}: {e}")
  303. self.client.send(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
  304. except Exception as e:
  305. logger.error(f"Error inesperado en method_connect para {self.log_prefix}: {e}")
  306. self.client.send(b'HTTP/1.1 500 Internal Server Error\r\n\r\n')
  307. finally:
  308. self.client_buffer = b''
  309. def do_connect(self):
  310. """Bucle principal para el túnel de datos."""
  311. socs = [self.client, self.target]
  312. count = 0
  313. while True:
  314. try:
  315. readable, _, err = select.select(socs, [], socs, 3)
  316. if err:
  317. break
  318. if readable:
  319. for sock in readable:
  320. data = sock.recv(BUFLEN)
  321. if data:
  322. if sock is self.target:
  323. self.client.send(data)
  324. else:
  325. self.target.sendall(data)
  326. count = 0
  327. else:
  328. logger.info(f"Conexión con {self.log_prefix} terminada.")
  329. return
  330. if count == TIMEOUT:
  331. logger.info(f"Conexión con {self.log_prefix} excedió el tiempo de espera.")
  332. return
  333. count += 1
  334. except (socket.error, socket.timeout) as e:
  335. logger.error(f"Error en el túnel de datos para {self.log_prefix}: {e}")
  336. return
  337. except Exception as e:
  338. logger.error(f"Error inesperado en do_connect para {self.log_prefix}: {e}")
  339. return
  340. # ==============================================================================
  341. # LÓGICA PRINCIPAL
  342. # ==============================================================================
  343. def main():
  344. server = Server(LISTENING_PORT)
  345. server.start()
  346. try:
  347. while True:
  348. time.sleep(2)
  349. except KeyboardInterrupt:
  350. logger.info('Deteniendo el servidor...')
  351. server.close()
  352. server.join()
  353. sys.exit(0)
  354. if __name__ == '__main__':
  355. main()