MCPcopy Create free account
hub / github.com/masterking32/MasterHttpRelayVPN / negotiate_socks5

Function negotiate_socks5

src/proxy/socks5.py:22–88  ·  view source on GitHub ↗

Perform a SOCKS5 handshake and return the requested (host, port). Sends protocol-level replies directly to *writer*. Returns ``None`` and leaves the connection in a closed state if negotiation fails at any step (unsupported version, method, command, or address type). Raises:

(
    reader: asyncio.StreamReader,
    writer: asyncio.StreamWriter,
)

Source from the content-addressed store, hash-verified

20
21
22async def negotiate_socks5(
23 reader: asyncio.StreamReader,
24 writer: asyncio.StreamWriter,
25) -> tuple[str, int] | None:
26 """Perform a SOCKS5 handshake and return the requested (host, port).
27
28 Sends protocol-level replies directly to *writer*. Returns ``None``
29 and leaves the connection in a closed state if negotiation fails at
30 any step (unsupported version, method, command, or address type).
31
32 Raises:
33 asyncio.IncompleteReadError: if the client closes the connection
34 mid-handshake.
35 asyncio.TimeoutError: propagated from the individual ``wait_for``
36 calls so the caller can log it separately.
37 """
38 # ── Auth negotiation ──────────────────────────────────────────
39 header = await asyncio.wait_for(reader.readexactly(2), timeout=15)
40 ver, nmethods = header[0], header[1]
41 if ver != 5:
42 return None
43
44 methods = await asyncio.wait_for(reader.readexactly(nmethods), timeout=10)
45 if 0x00 not in methods:
46 # No acceptable method — reject
47 writer.write(b"\x05\xff")
48 await writer.drain()
49 return None
50
51 # Accept: no authentication required
52 writer.write(b"\x05\x00")
53 await writer.drain()
54
55 # ── Request ───────────────────────────────────────────────────
56 req = await asyncio.wait_for(reader.readexactly(4), timeout=15)
57 ver, cmd, _rsv, atyp = req
58 if ver != 5 or cmd != 0x01:
59 # Only CONNECT (0x01) is supported
60 writer.write(b"\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00")
61 await writer.drain()
62 return None
63
64 # ── Address parsing ───────────────────────────────────────────
65 if atyp == 0x01: # IPv4
66 raw = await asyncio.wait_for(reader.readexactly(4), timeout=10)
67 host = socket.inet_ntoa(raw)
68 elif atyp == 0x03: # Domain name
69 ln = (await asyncio.wait_for(reader.readexactly(1), timeout=10))[0]
70 host = (
71 await asyncio.wait_for(reader.readexactly(ln), timeout=10)
72 ).decode(errors="replace")
73 elif atyp == 0x04: # IPv6
74 raw = await asyncio.wait_for(reader.readexactly(16), timeout=10)
75 host = socket.inet_ntop(socket.AF_INET6, raw)
76 else:
77 writer.write(b"\x05\x08\x00\x01\x00\x00\x00\x00\x00\x00")
78 await writer.drain()
79 return None

Callers 1

_on_socks_clientMethod · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected