diff --git a/engine/server.py b/engine/server.py index e4ae816..9f6df53 100644 --- a/engine/server.py +++ b/engine/server.py @@ -143,35 +143,52 @@ class ConnectionHandler: if self.config.auth_method != "userpass": return True - # 读认证请求头 - header = await asyncio.wait_for(self.reader.read(256), - timeout=self.config.timeout) - if len(header) < 2: - return False - ver, ulen = struct.unpack("!BB", header[:2]) + # RFC 1929 认证请求格式: + # +----+------+----------+------+----------+ + # |VER | ULEN | UNAME | PLEN | PASSWD | + # +----+------+----------+------+----------+ + # | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + # +----+------+----------+------+----------+ + # 注意:整个请求是一个连续 TCP 流,可能一次性全部到达 + + data = b"" + # 1. 先读版本和用户名长度 (2 字节) + while len(data) < 2: + chunk = await asyncio.wait_for(self.reader.read(256), + timeout=self.config.timeout) + if not chunk: + return False + data += chunk + + ver, ulen = struct.unpack("!BB", data[:2]) if ver != 1: return False - # 读用户名 - username = await asyncio.wait_for(self.reader.read(ulen), - timeout=self.config.timeout) - if len(username) != ulen: - return False - username = username.decode("utf-8", errors="replace") + offset = 2 - # 读密码长度 - plen_byte = await asyncio.wait_for(self.reader.read(1), - timeout=self.config.timeout) - if not plen_byte: - return False - plen = plen_byte[0] + # 2. 读取剩余需要的数据 + needed = ulen + 1 # 用户名 + 密码长度字节 + while len(data) < offset + needed: + chunk = await asyncio.wait_for(self.reader.read(256), + timeout=self.config.timeout) + if not chunk: + return False + data += chunk - # 读密码 - passwd = await asyncio.wait_for(self.reader.read(plen), - timeout=self.config.timeout) - if len(passwd) != plen: - return False - passwd = passwd.decode("utf-8", errors="replace") + username = data[offset:offset + ulen].decode("utf-8", errors="replace") + offset += ulen + plen = data[offset] + offset += 1 + + # 3. 读取密码 + while len(data) < offset + plen: + chunk = await asyncio.wait_for(self.reader.read(256), + timeout=self.config.timeout) + if not chunk: + return False + data += chunk + + passwd = data[offset:offset + plen].decode("utf-8", errors="replace") # 验证(使用 bcrypt 兼容验证) user = self.user_service.get_user(username) @@ -221,20 +238,32 @@ class ConnectionHandler: return None, None, None, None async def _reply(self, rep, bnd_addr="0.0.0.0", bnd_port=0): - """发送 SOCKS5 响应。""" - addr_bytes = b"\x00\x00\x00\x00" - if bnd_addr and "." not in str(bnd_addr): - # IPv6 + """发送 SOCKS5 响应 (RFC 1928 §6). + + 格式: VER(1) REP(1) RSV(1) ATYP(1) BND.ADDR(4/16字节) BND.PORT(2字节) + IPv4 是 4 字节 packed, IPv6 是 16 字节, 域名不允许出现在 reply. + """ + atyp = ATYP_IPV4 + if bnd_addr: try: - addr_bytes = ipaddress.IPv6Address(str(bnd_addr)).packed - except Exception: - pass + ip = ipaddress.ip_address(str(bnd_addr)) + if ip.version == 6: + atyp = ATYP_IPV6 + addr_bytes = ip.packed + else: + atyp = ATYP_IPV4 + addr_bytes = ip.packed + except ValueError: + atyp = ATYP_IPV4 + addr_bytes = b"\x00\x00\x00\x00" + else: + atyp = ATYP_IPV4 + addr_bytes = b"\x00\x00\x00\x00" self.writer.write(struct.pack( - "!BBBB5sH", - SOCKS_VERSION, rep, 0x00, ATYP_IPV4, - addr_bytes, bnd_port - )) + "!BBBB", + SOCKS_VERSION, rep, 0x00, atyp, + ) + addr_bytes + struct.pack("!H", bnd_port)) await self.writer.drain() async def _handle_connect(self, addr, port):