少写一个长度字节,域名在服务端眼里是 101 字节长

举报
yd_48584989 发表于 2026/11/05 08:33:12 2026/11/05
【摘要】 我给一个本地中转写了 SOCKS5 握手,客户端拨号全部超时。上游的账密是通的,坏的是我自己拼的请求:ATYP=3 的地址段是「1 字节长度 + 域名」,我把长度省了,服务端就把域名首字母的 ASCII 码当成了长度。

先说现象。我给一个本地中转加了 SOCKS5 支持,代码跑起来以后,所有走它的请求都是超时, 没有一条成功,也没有一条报错。日志里干干净净,没有任何异常。

我第一反应是上游坏了。可这个中转的上游是一个带账密的 SOCKS5 服务, 它自带一个 --check:只验证上游的用户名密码能不能通过握手。 跑一次,通过。这就把范围缩掉一大半——上游是活的,问题在我自己拼出来的那段字节里。

SOCKS5 的 CONNECT 请求长什么样

握手分两步。第一步是协商方法:客户端发 VER NMETHODS METHODS, 服务端回 VER METHOD。第二步才是请求:

VER(1)  CMD(1)  RSV(1)  ATYP(1)  DST.ADDR      DST.PORT(2)
           1 = CONNECT          1 = IPv4  → 4 字节
                                3 = 域名  → [len][domain]   ← 这里
                                4 = IPv6  → 16 字节

关键就在 ATYP=3 这一支。地址段的第一个字节是域名长度, 不是域名本身。我当时想当然地按「域名 + 端口」拼了过去:

# 错的
head = bytes([5, 1, 0, 3])
sock.sendall(head + domain.encode() + struct.pack("!H", port))

# 对的
sock.sendall(head + bytes([len(domain)]) + domain.encode() + struct.pack("!H", port))

少一个字节,肉眼几乎看不出来;对端的解析器看到的是完全另一件事。

量一次,比猜十次快

我写了个十来行的最小服务端,严格按 RFC 1928 解析,然后把两种请求各发一次。 下面是真实输出,第一个是补上长度字节的,第二个是漏掉的:

== 正确写法:带 1 字节长度前缀 ==
   payload(18 bytes): 05 01 00 03 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb
   服务端解析: {'atyp': 3, 'declared_len': 11, 'stage': 'parsed',
              'domain': 'example.com', 'port': 443, 'elapsed': 0.0}

== 漏掉长度字节 ==
   payload(17 bytes): 05 01 00 03 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb
   服务端解析: {'atyp': 3, 'declared_len': 101,
              'stage': 'timeout_waiting_domain', 'waited': 12, 'need': 103,
              'elapsed': 5.0}

两个数字就够了:0x65 是 e 的 ASCII 码, 0x65 = 101。服务端认为这个域名有 101 个字节,再加上 2 字节端口, 它要等满 103 字节才会往下走。而一条 example.com 的请求总共只有 12 个字节可读。 它就在那儿等,等满我给的 5 秒超时,一个字节都没回。

这也解释了为什么客户端侧看起来是「超时」而不是「失败」: SOCKS5 的服务端在地址段没读齐之前不会发 REP 应答, 客户端拿不到任何回应,只能挂到自己的超时。我第一次排查时看的是 netstat,一片 CLOSE_WAIT——那其实是「连上了、但对面一直不说话」的典型形态, 跟「连不上」完全不是一回事。

可复用的三条

  1. 长度前缀是协议字段,不是可省的优化。凡是「长度 + 内容」的编码(SOCKS5 域名、TLS 扩展、 各种 TLV),少一个字节不会报错,只会让对面读到另一个数——而且那个数往往很大,表现为「卡住」。
  2. 先把「上游」和「本地转发层」切开再修。我这次能用一条 --check 把上游排除掉, 省掉了至少半小时的瞎猜。写自己的转发层时,顺手留一个只验上游的开关,是值得的。
  3. 字节级 dump 比日志有用。我原来的日志只在「发出去了」这一层打点,看不出内容。 把 payload.hex(" ") 打出来以后,18 字节和 17 字节的差别一眼就看见了。

最后补一句我自己的教训:这类 bug 的可怕之处是「静默」。没有异常、没有错误码, 只有一条条挂到超时的连接。遇到这种形态,先怀疑自己拼的字节,再怀疑网络。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。