Proxy 1 import asyncio import logging import socket import sys import binascii import re from typing import Tuple # ==================== КОНФИГУРАЦИЯ ==================== CONVERT_MODE = 1 # 1 - HEX, 0 - прозрачный REPLACE_ON = 0 # 0 - без замены текста LISTEN_IP = "10.10.0.1" LISTEN_PORT = 8443 SOURCE_IP = "20.20.0.1" TARGET_HOST = "20.20.0.2" TARGET_PORT = 80 MAX_CLIENTS = 500 IDLE_TIMEOUT = 60 CONNECT_TIMEOUT = 10 TOTAL_TIMEOUT = 300 BUFFER_LIMIT = 65536 LOG_LEVEL = "INFO" # ===================================================== logging.basicConfig( level=getattr(logging, LOG_LEVEL), format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) active_connections = 0 connections_lock = asyncio.Lock() def CONVERTER(data: bytes) -> bytes: """Преобразует двоичные данные в HEX-строку с разделителем \\n""" if not data: return b"\n" hex_data = binascii.hexlify(data) return hex_data + b"\n" def DECONVERTER(text_line: bytes) -> bytes: """Преобразует HEX-строку обратно в двоичные данные""" if not text_line: return b'' try: hex_str = text_line.strip() if not hex_str: return b'' if len(hex_str) % 2 != 0: logger.error(f"HEX-строка нечётной длины: {hex_str[:100]}") return b'' # Проверяем, что строка содержит только HEX символы if not all(c in b'0123456789abcdefABCDEF' for c in hex_str): logger.error(f"HEX-строка содержит недопустимые символы: {hex_str[:100]}") return b'' return binascii.unhexlify(hex_str) except binascii.Error as e: logger.error(f"Ошибка DECONVERTER (неверный HEX): {e}") return b'' except Exception as e: logger.error(f"Ошибка DECONVERTER: {e}") return b'' def PASS_THROUGH(data: bytes) -> bytes: return data def extract_host_from_request(data: bytes) -> str: try: text = data.decode('ascii', errors='ignore') match = re.search(r'CONNECT\s+([^\s:]+)', text, re.IGNORECASE) if match: return match.group(1) match = re.search(r'^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)\s+https?://([^/:]+)', text, re.IGNORECASE) if match: return match.group(2) match = re.search(r'Host:\s*([^\r\n]+)', text, re.IGNORECASE) if match: return match.group(1).strip() return "unknown" except: return "unknown" if CONVERT_MODE == 1: logger.info("Режим: HEX-конвертация") TO_SERVER = CONVERTER TO_CLIENT = DECONVERTER else: logger.info("Режим: прозрачный туннель") TO_SERVER = PASS_THROUGH TO_CLIENT = PASS_THROUGH if REPLACE_ON == 1: logger.info("Замена текста: ВКЛЮЧЕНА") else: logger.info("Замена текста: ВЫКЛЮЧЕНА") async def connect_to_target() -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.bind((SOURCE_IP, 0)) loop = asyncio.get_event_loop() try: await asyncio.wait_for( loop.sock_connect(sock, (TARGET_HOST, TARGET_PORT)), timeout=CONNECT_TIMEOUT ) reader, writer = await asyncio.open_connection(sock=sock) return reader, writer except Exception as e: sock.close() raise async def handle_client(client_reader, client_writer): global active_connections client_addr = client_writer.get_extra_info('peername') client_id = f"{client_addr[0]}:{client_addr[1]}" if client_addr else "unknown" client_socket = client_writer.get_extra_info('socket') if client_socket: client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) server_reader = None server_writer = None try: server_reader, server_writer = await connect_to_target() logger.info(f"[{client_id}] Соединение с Proxy2 {TARGET_HOST}:{TARGET_PORT}") buffer = b"" async def client_to_server(): nonlocal server_writer try: while True: try: data = await asyncio.wait_for( client_reader.read(8192), timeout=IDLE_TIMEOUT ) except asyncio.TimeoutError: continue if not data: logger.info(f"[{client_id}] Нет данных от клиента") break target_site = extract_host_from_request(data) if target_site != "unknown": logger.info(f"[{client_id}] ЗАПРОС > {target_site}") # Конвертируем в HEX и отправляем hex_data = TO_SERVER(data) server_writer.write(hex_data) await server_writer.drain() except Exception as e: logger.error(f"[{client_id}] Ошибка client_to_server: {e}") async def server_to_client(): nonlocal server_reader, client_writer, buffer try: while True: try: chunk = await asyncio.wait_for( server_reader.read(8192), timeout=IDLE_TIMEOUT ) except asyncio.TimeoutError: continue if not chunk: logger.info(f"[{client_id}] Нет данных от Proxy2") break buffer += chunk # Обрабатываем буфер по разделителю '\n' while b'\n' in buffer: line, buffer = buffer.split(b'\n', 1) line = line.strip() if line: binary_data = TO_CLIENT(line) if binary_data: client_writer.write(binary_data) await client_writer.drain() logger.debug(f"[{client_id}] Отправлено {len(binary_data)} байт клиенту") else: logger.warning(f"[{client_id}] DECONVERTER вернул пустые данные") if len(buffer) > BUFFER_LIMIT: logger.warning(f"[{client_id}] Буфер превысил лимит, сбрасываем") buffer = b"" except Exception as e: logger.error(f"[{client_id}] Ошибка server_to_client: {e}") tasks = [ asyncio.create_task(client_to_server()), asyncio.create_task(server_to_client()) ] async with connections_lock: active_connections += 1 logger.info(f"[{client_id}] Новое соединение. Активных: {active_connections}") done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) for task in pending: task.cancel() try: await task except asyncio.CancelledError: pass except Exception as e: logger.error(f"[{client_id}] Ошибка: {e}") finally: if client_writer and not client_writer.is_closing(): client_writer.close() if server_writer and not server_writer.is_closing(): server_writer.close() async with connections_lock: if active_connections > 0: active_connections -= 1 logger.info(f"[{client_id}] Соединение закрыто. Активных: {active_connections}") async def connection_handler(client_reader, client_writer): try: await handle_client(client_reader, client_writer) except Exception as e: logger.error(f"Критическая ошибка: {e}") async def monitor_connections(): while True: await asyncio.sleep(30) async with connections_lock: logger.info(f"Статус: активных {active_connections}") async def main(): logger.info(f"Целевой адрес (Proxy2): {TARGET_HOST}:{TARGET_PORT}") logger.info(f"Исходящий интерфейс: {SOURCE_IP}") server = await asyncio.start_server( connection_handler, host=LISTEN_IP, port=LISTEN_PORT, backlog=100 ) logger.info(f"Сервер запущен на {LISTEN_IP}:{LISTEN_PORT}") logger.info(f"Режим: {'HEX-конвертация' if CONVERT_MODE == 1 else 'Прозрачный'}") logger.info("Нажмите Ctrl+C для остановки") asyncio.create_task(monitor_connections()) try: async with server: await server.serve_forever() except KeyboardInterrupt: logger.info("Остановка...") server.close() await server.wait_closed() logger.info("Сервер остановлен") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: pass except Exception as e: logger.critical(f"Ошибка: {e}") sys.exit(1) Proxy 2 import socket import threading import binascii import signal import sys import time import re # ==================== НАСТРОЙКИ ==================== CONVERT_MODE = 1 # 1 - HEX, 0 - прозрачный REPLACE_ON = 0 # 0 - без замены текста # Внутренний интерфейс (для связи с первым прокси) LISTEN_IP = "20.20.0.2" LISTEN_PORT = 80 # Исходящий интерфейс (для подключения к первому прокси) OUTGOING_IP = "20.20.0.2" # Внешний интерфейс (в Интернет) - укажите реальный IP вашей VM EXTERNAL_IP = "192.168.0.1" EXTERNAL_PORT = 0 # Адрес первого прокси (для отправки ответов) TARGET_HOST = "20.20.0.1" TARGET_PORT = 8443 # Настройки сервера MAX_CLIENTS = 500 BUFFER_SIZE = 8192 BUFFER_LIMIT = 65536 CONNECT_TIMEOUT = 10 IDLE_TIMEOUT = 60 # ======================================================================== server_running = True active_connections = 0 connections_lock = threading.Lock() def safe_print(msg): try: print(msg, flush=True) except: pass safe_print("=" * 60) safe_print("ЗАПУСК ВТОРОГО ПРОКСИ-СЕРВЕРА") safe_print("=" * 60) safe_print(f"[*] Режим: {'HEX-конвертация' if CONVERT_MODE == 1 else 'Прозрачный'}") safe_print(f"[*] Замена текста: {'ВКЛЮЧЕНА' if REPLACE_ON == 1 else 'ВЫКЛЮЧЕНA'}") def HEX_TO_BINARY(data: bytes) -> bytes: """Преобразует HEX-строку в двоичные данные""" if not data: return b'' try: hex_str = data.strip() if not hex_str: return b'' if len(hex_str) % 2 != 0: safe_print(f"[!] HEX-строка нечётной длины: {hex_str[:100]}") return b'' return binascii.unhexlify(hex_str) except Exception as e: safe_print(f"[!] Ошибка HEX_TO_BINARY: {e}") return b'' def BINARY_TO_HEX(data: bytes) -> bytes: """Преобразует двоичные данные в HEX-строку с \\n""" if not data: return b"\n" return binascii.hexlify(data) + b"\n" def PASS_THROUGH(data: bytes) -> bytes: return data def extract_host_from_request(data: bytes) -> str: try: text = data.decode('ascii', errors='ignore') match = re.search(r'CONNECT\s+([^\s:]+)', text, re.IGNORECASE) if match: return match.group(1) match = re.search(r'^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)\s+https?://([^/:]+)', text, re.IGNORECASE) if match: return match.group(2) match = re.search(r'Host:\s*([^\r\n]+)', text, re.IGNORECASE) if match: return match.group(1).strip() return "unknown" except: return "unknown" if CONVERT_MODE == 1: FROM_FIRST_PROXY = HEX_TO_BINARY TO_FIRST_PROXY = BINARY_TO_HEX else: FROM_FIRST_PROXY = PASS_THROUGH TO_FIRST_PROXY = PASS_THROUGH def parse_connect_request(data: bytes) -> tuple: try: text = data.decode('ascii', errors='ignore') match = re.search(r'CONNECT\s+([^\s:]+)(?::(\d+))?\s+HTTP/\d\.\d', text, re.IGNORECASE) if match: host = match.group(1) port = int(match.group(2)) if match.group(2) else 443 return (host, port) match = re.search(r'^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)\s+https?://([^/:]+)', text, re.IGNORECASE) if match: host = match.group(2) port = 80 if 'http://' in text else 443 return (host, port) return (None, None) except: return (None, None) def send_connect_response(client_sock, client_addr, success: bool): """Отправляет ответ на CONNECT запрос""" response = b"HTTP/1.1 200 Connection established\r\n\r\n" if success else b"HTTP/1.1 502 Bad Gateway\r\n\r\n" try: # В HEX режиме ответ нужно отправить в HEX формате if CONVERT_MODE == 1: hex_response = TO_FIRST_PROXY(response) client_sock.sendall(hex_response) safe_print(f"[*] {client_addr}: Ответ 200 Connection established (HEX)") else: client_sock.sendall(response) safe_print(f"[*] {client_addr}: Ответ 200 Connection established") except Exception as e: safe_print(f"[!] {client_addr}: Ошибка отправки ответа: {e}") def connect_to_internet(host: str, port: int, client_addr: str): try: safe_print(f"[*] {client_addr}: Подключение к {host}:{port}") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if EXTERNAL_IP: sock.bind((EXTERNAL_IP, EXTERNAL_PORT)) safe_print(f"[*] {client_addr}: Привязан к {EXTERNAL_IP}") sock.settimeout(CONNECT_TIMEOUT) sock.connect((host, port)) sock.settimeout(None) safe_print(f"[*] {client_addr}: Подключен к {host}:{port}") return sock except Exception as e: safe_print(f"[!] {client_addr}: Ошибка подключения: {e}") raise def tunnel_to_internet(client_sock, internet_sock, client_addr, stop_event, early_data_buffer): """Поток: Proxy1 > Интернет""" if early_data_buffer: try: if CONVERT_MODE == 1: binary_data = FROM_FIRST_PROXY(early_data_buffer) if binary_data: internet_sock.sendall(binary_data) else: internet_sock.sendall(early_data_buffer) except Exception as e: safe_print(f"[!] {client_addr}: Ошибка отправки буфера: {e}") buffer = b"" while not stop_event.is_set(): try: data = client_sock.recv(BUFFER_SIZE) if not data: break if CONVERT_MODE == 1: buffer += data while b'\n' in buffer: line, buffer = buffer.split(b'\n', 1) line = line.strip() if line: binary_data = FROM_FIRST_PROXY(line) if binary_data: internet_sock.sendall(binary_data) if len(buffer) > BUFFER_LIMIT: safe_print(f"[!] {client_addr}: Буфер переполнен") buffer = b"" else: internet_sock.sendall(data) except (BlockingIOError, socket.error): continue except Exception as e: if not stop_event.is_set(): safe_print(f"[!] {client_addr}: Ошибка tunnel_to_internet: {e}") break stop_event.set() def tunnel_to_first_proxy(internet_sock, client_sock, client_addr, stop_event): """Поток: Интернет > Proxy1""" while not stop_event.is_set(): try: data = internet_sock.recv(BUFFER_SIZE) if not data: break if CONVERT_MODE == 1: hex_data = TO_FIRST_PROXY(data) client_sock.sendall(hex_data) else: client_sock.sendall(data) except (BlockingIOError, socket.error): continue except Exception as e: if not stop_event.is_set(): safe_print(f"[!] {client_addr}: Ошибка tunnel_to_first_proxy: {e}") break stop_event.set() def handle_client(client_sock, client_addr): global active_connections internet_sock = None stop_event = threading.Event() early_data_buffer = None try: initial_data = client_sock.recv(BUFFER_SIZE) if not initial_data: safe_print(f"[!] {client_addr}: Нет данных") return safe_print(f"[*] {client_addr}: Получено {len(initial_data)} байт данных") # Декодируем HEX в бинарные данные if CONVERT_MODE == 1: if b'\n' in initial_data: first_line = initial_data.split(b'\n')[0].strip() binary_data = FROM_FIRST_PROXY(first_line) else: binary_data = FROM_FIRST_PROXY(initial_data) else: binary_data = initial_data if not binary_data: safe_print(f"[!] {client_addr}: Не удалось декодировать данные") send_connect_response(client_sock, client_addr, False) return target_host, target_port = parse_connect_request(binary_data) if not target_host: safe_print(f"[!] {client_addr}: Не удалось распознать запрос") send_connect_response(client_sock, client_addr, False) return safe_print(f"[*] {client_addr}: ЗАПРОС > {target_host}:{target_port}") internet_sock = connect_to_internet(target_host, target_port, client_addr) # Проверяем, CONNECT ли это is_connect = binary_data.decode('ascii', errors='ignore').upper().startswith('CONNECT') if is_connect: send_connect_response(client_sock, client_addr, True) parts = binary_data.split(b'\r\n\r\n', 1) if len(parts) == 2 and parts[1]: early_data_buffer = parts[1] if CONVERT_MODE == 1: early_data_buffer = TO_FIRST_PROXY(early_data_buffer) else: # Для HTTP запросов отправляем сразу if CONVERT_MODE == 1: client_sock.sendall(TO_FIRST_PROXY(binary_data)) else: client_sock.sendall(binary_data) safe_print(f"[+] {client_addr}: Соединение > {target_host}:{target_port}") with connections_lock: active_connections += 1 safe_print(f"[*] Активных: {active_connections}") t1 = threading.Thread( target=tunnel_to_internet, args=(client_sock, internet_sock, client_addr, stop_event, early_data_buffer), daemon=True ) t2 = threading.Thread( target=tunnel_to_first_proxy, args=(internet_sock, client_sock, client_addr, stop_event), daemon=True ) t1.start() t2.start() t1.join() t2.join() except Exception as e: safe_print(f"[!] {client_addr}: Ошибка: {e}") try: send_connect_response(client_sock, client_addr, False) except: pass finally: stop_event.set() try: client_sock.close() except: pass try: if internet_sock: internet_sock.close() except: pass with connections_lock: if active_connections > 0: active_connections -= 1 safe_print(f"[-] {client_addr}: Закрыто. Активных: {active_connections}") def signal_handler(signum, frame): global server_running safe_print("\n[!] Получен сигнал остановки. Завершаем работу...") server_running = False wait_time = 0 while active_connections > 0 and wait_time < 5: safe_print(f"[*] Ожидаем завершения {active_connections} соединений...") time.sleep(1) wait_time += 1 safe_print("[*] Прокси сервер остановлен") sys.exit(0) def start_proxy(): global server_running signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server.bind((LISTEN_IP, LISTEN_PORT)) safe_print(f"[+] Привязан к {LISTEN_IP}:{LISTEN_PORT}") except Exception as e: safe_print(f"[!] Ошибка привязки: {e}") sys.exit(1) server.listen(MAX_CLIENTS) server.settimeout(1.0) safe_print("=" * 60) safe_print("ВТОРОЙ ПРОКСИ-СЕРВЕР") safe_print("=" * 60) safe_print(f"[*] СЛУШАЕТ первый прокси на: {LISTEN_IP}:{LISTEN_PORT}") safe_print(f"[*] ПОДКЛЮЧАЕТСЯ к первому прокси на: {TARGET_HOST}:{TARGET_PORT}") safe_print(f"[*] Выход в Интернет через: {EXTERNAL_IP if EXTERNAL_IP else 'авто'}") safe_print(f"[*] Режим: {'HEX-конвертация' if CONVERT_MODE == 1 else 'Прозрачный'}") safe_print(f"[*] Замена текста: {'ВКЛЮЧЕНА' if REPLACE_ON == 1 else 'ВЫКЛЮЧЕНА'}") safe_print(f"[*] Максимум соединений: {MAX_CLIENTS}") safe_print("[*] Нажмите Ctrl+C для остановки") safe_print("-" * 60) safe_print("[*] Сервер запущен и ожидает подключений...") try: while server_running: try: client_sock, client_addr = server.accept() if not server_running: break safe_print(f"[*] Принято соединение от {client_addr}") client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) threading.Thread( target=handle_client, args=(client_sock, client_addr), daemon=True ).start() except socket.timeout: continue except OSError as e: if server_running: safe_print(f"[!] Ошибка accept: {e}") break except Exception as e: safe_print(f"[!] Критическая ошибка: {e}") finally: safe_print("[*] Закрываем серверный сокет...") server.close() safe_print("[*] Сервер остановлен") if __name__ == "__main__": try: start_proxy() except KeyboardInterrupt: safe_print("\n[*] Программа прервана пользователем") except Exception as e: safe_print(f"[!] Необработанная ошибка: {e}") sys.exit(1)