fix socks

This commit is contained in:
asus
2026-04-22 19:28:25 +03:30
parent 3c20028212
commit 873d72747b
2 changed files with 160 additions and 17 deletions
+140 -15
View File
@@ -7,11 +7,14 @@ a domain-fronted connection to a CDN worker or Apps Script relay.
Supports:
- CONNECT method → WebSocket tunnel (modes 1-3) or MITM relay (apps_script)
- GET / POST etc. → HTTP forwarding (modes 1-3) or JSON relay (apps_script)
- SOCKS5 CONNECT → Same tunnel/MITM routing as the HTTP proxy
"""
import asyncio
import contextlib
import logging
import re
import socket
import ssl
import time
@@ -104,6 +107,8 @@ class ProxyServer:
def __init__(self, config: dict):
self.host = config.get("listen_host", "127.0.0.1")
self.port = config.get("listen_port", 8080)
self.socks5_enabled = bool(config.get("socks5_enabled"))
self.socks5_port = int(config.get("socks5_port", 1080))
self.mode = config.get("mode", "domain_fronting")
self.fronter = DomainFronter(config)
self.mitm = None
@@ -128,13 +133,33 @@ class ProxyServer:
raise SystemExit(1)
async def start(self):
srv = await asyncio.start_server(self._on_client, self.host, self.port)
log.info(
"Listening on %s:%d — configure your browser HTTP proxy to this address",
self.host, self.port,
)
async with srv:
await srv.serve_forever()
servers = []
try:
http_srv = await asyncio.start_server(self._on_client, self.host, self.port)
servers.append(http_srv)
log.info(
"Listening on %s:%d — configure your browser HTTP proxy to this address",
self.host, self.port,
)
if self.socks5_enabled:
socks_srv = await asyncio.start_server(
self._on_socks5_client, self.host, self.socks5_port
)
servers.append(socks_srv)
log.info(
"Listening on %s:%d — SOCKS5 CONNECT (no-auth, TCP only)",
self.host, self.socks5_port,
)
async with contextlib.AsyncExitStack() as stack:
for srv in servers:
await stack.enter_async_context(srv)
await asyncio.gather(*(srv.serve_forever() for srv in servers))
finally:
for srv in servers:
srv.close()
await asyncio.gather(*(srv.wait_closed() for srv in servers), return_exceptions=True)
# ── client handler ────────────────────────────────────────────
@@ -176,19 +201,79 @@ class ProxyServer:
except Exception:
pass
async def _on_socks5_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
addr = writer.get_extra_info("peername")
try:
version, nmethods = await asyncio.wait_for(reader.readexactly(2), timeout=15)
if version != 5:
return
methods = await asyncio.wait_for(reader.readexactly(nmethods), timeout=15)
if 0x00 not in methods:
writer.write(b"\x05\xff")
await writer.drain()
return
writer.write(b"\x05\x00")
await writer.drain()
version, cmd, _rsv, atyp = await asyncio.wait_for(reader.readexactly(4), timeout=30)
if version != 5:
return
host = await self._read_socks5_address(reader, atyp)
if host is None:
await self._send_socks5_reply(writer, rep=0x08)
return
port = int.from_bytes(
await asyncio.wait_for(reader.readexactly(2), timeout=15), "big"
)
if cmd != 0x01:
log.debug("SOCKS5 unsupported command %d from %s", cmd, addr)
await self._send_socks5_reply(writer, rep=0x07)
return
await self._handle_connect_target(
host,
port,
reader,
writer,
protocol="SOCKS5",
ready_cb=lambda: self._send_socks5_reply(writer, rep=0x00),
)
except asyncio.IncompleteReadError:
pass
except asyncio.TimeoutError:
log.debug("SOCKS5 timeout: %s", addr)
except Exception as e:
log.error("SOCKS5 error (%s): %s", addr, e)
finally:
try:
writer.close()
await writer.wait_closed()
except Exception:
pass
# ── CONNECT (HTTPS tunnelling) ────────────────────────────────
async def _do_connect(self, target: str, reader, writer):
host, _, port = target.rpartition(":")
port = int(port) if port else 443
if not host:
host, port = target, 443
host, port = self._split_host_port(target, default_port=443)
await self._handle_connect_target(
host,
port,
reader,
writer,
protocol="CONNECT",
ready_cb=lambda: self._send_http_connect_ok(writer),
)
log.info("CONNECT → %s:%d", host, port)
writer.write(b"HTTP/1.1 200 Connection Established\r\n\r\n")
await writer.drain()
async def _handle_connect_target(self, host: str, port: int, reader, writer,
*, protocol: str, ready_cb):
log.info("%s%s:%d", protocol, host, port)
await ready_cb()
if self.mode == "apps_script":
override_ip = self._sni_rewrite_ip(host)
if override_ip:
@@ -207,6 +292,46 @@ class ProxyServer:
else:
await self.fronter.tunnel(host, port, reader, writer)
@staticmethod
def _split_host_port(target: str, default_port: int) -> tuple[str, int]:
target = target.strip()
if target.startswith("["):
host, _, port_str = target[1:].partition("]:")
return host, int(port_str) if port_str else default_port
host, _, port_str = target.rpartition(":")
if host:
return host, int(port_str)
return target, default_port
@staticmethod
async def _send_http_connect_ok(writer):
writer.write(b"HTTP/1.1 200 Connection Established\r\n\r\n")
await writer.drain()
@staticmethod
async def _read_socks5_address(reader: asyncio.StreamReader, atyp: int) -> str | None:
if atyp == 0x01:
return socket.inet_ntoa(await reader.readexactly(4))
if atyp == 0x03:
length = (await reader.readexactly(1))[0]
return (await reader.readexactly(length)).decode("ascii", errors="replace")
if atyp == 0x04:
return socket.inet_ntop(socket.AF_INET6, await reader.readexactly(16))
return None
@staticmethod
async def _send_socks5_reply(writer, *, rep: int,
bind_host: str = "0.0.0.0", bind_port: int = 0):
try:
packed_host = socket.inet_aton(bind_host)
atyp = 0x01
except OSError:
packed_host = b"\x00\x00\x00\x00"
atyp = 0x01
reply = b"\x05" + bytes([rep, 0x00, atyp]) + packed_host + bind_port.to_bytes(2, "big")
writer.write(reply)
await writer.drain()
# ── Hosts override (fake DNS) ─────────────────────────────────
# Built-in list of domains that must be reached via Google's frontend IP