HTTP 代理跨子网不通问题排查报告

HTTP 代理跨子网不通问题排查报告

IP 说明

文中 IP 地址最后一组已脱敏替换,对照关系如下:

  • 172.20.2.A1 — 远程机器(出问题的客户端)
  • 172.20.2.A2 — 远程子网网关
  • 172.20.6.B1 — Windows SS 代理服务器
  • 172.20.6.B2 — 排查用的对比本机
  • 172.20.6.B3 — OpenWrt 路由器(nginx stream 转发节点)

问题概述

远程机器 172.20.2.A1 配置 HTTP 代理指向 172.20.6.B1:1080,TCP 连接成功但代理不返回任何 HTTP 响应。

网络拓扑

1
2
3
4
5
6
7
8
9
10
11
172.20.2.A1 (远程机器)              172.20.6.B2 (本机)
│ │
│ 172.20.2.0/24 子网 │ 172.20.6.0/24 子网
│ │
└────────┬─────────┐ ┌─────────┴──────────┐
│ │
172.20.2.A2 172.20.6.B3
(网关) (OpenWrt nginx stream)
│ │
172.20.6.B1
(Windows SS 代理)
  • 172.20.6.B1: Windows 机器,运行 Shadowsocks(或类似代理软件),PID 9036,监听 0.0.0.0:1080
  • 172.20.6.B3: OpenWrt 路由器,运行 nginx stream 四层 TCP 转发 (172.20.6.B3:1080 → 172.20.6.B1:1080)
  • 172.20.2.A1: 远程 CentOS 机器,默认网关 172.20.2.A2
  • 172.20.6.B2: 排查过程使用的对比本机

排查过程

第一步:确认基础现象

远程机器代理配置:

1
2
3
export http_proxy="http://172.20.6.B1:1080"
export https_proxy="http://172.20.6.B1:1080"
export no_proxy="localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8"

curl 测试结果:TCP 握手成功,HTTP GET 请求发出后超时,0 字节收到。

1
2
3
4
5
6
7
8
9
* Connected to 172.20.6.B1 port 1080
> GET http://www.baidu.com/ HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
> Proxy-Connection: Keep-Alive
>
# ... 超时,无响应 ...
curl: (28) Operation timed out

第二步:检查 TCP 层连通性(远程机器 → 代理)

TCP 端口连通性:端口 1080 可达。

1
2
timeout 5 bash -c "echo > /dev/tcp/172.20.6.B1/1080" && echo "open"
# 结果:Port 1080 open

ICMP 连通性:ping 正常,无丢包。

1
2
PING 172.20.6.B1: 3 packets transmitted, 3 received, 0% packet loss
rtt min/avg/max = 0.704/0.864/0.990 ms

路由分析:路由表正常,默认网关 172.20.2.A2 可达。

traceroute:第一跳到达网关 172.20.2.A2(~4ms),之后全部无响应(中间节点不响应 ICMP,属正常现象)。

结论:IP 层路由正常,TCP 三次握手完成,ICMP 通。问题不在网络层。

第三步:tcpdump 抓包(远程机器侧)

在远程机器上对代理端口抓包,限制 3 个包以观察握手:

1
tcpdump -i any -n port 1080 -c 3

抓到的三个包:

  • 包 1: 172.20.2.A1:35464 → 172.20.6.B1:1080 — SYN(发起连接)
  • 包 2: 172.20.6.B1:1080 → 172.20.2.A1:35464 — SYN-ACK(代理应答)
  • 包 3: 172.20.2.A1:35464 → 172.20.6.B1:1080 — ACK(握手完成)

三次握手完全正常。HTTP 请求发出后,代理端没有任何数据返回,curl 最终超时。

第四步:本机对比测试

本机 (172.20.6.B2) 与代理 (172.20.6.B1) 同属 172.20.6.0/24 子网,用同样方式测试:

1
2
curl -x http://172.20.6.B1:1080 http://www.baidu.com/
# 结果:HTTP 200 OK,正常返回百度首页

本机 172.20.6.B2(同子网)

  • ping 代理: ✅ <1ms
  • TCP 握手: ✅
  • HTTP GET 到达代理: ✅
  • HTTP 响应: ✅ 200 OK

远程 172.20.2.A1(跨子网)

  • ping 代理: ✅ <1ms
  • TCP 握手: ✅
  • HTTP GET 到达代理: ✅(抓包证实)
  • HTTP 响应: ❌ 完全静默

结论:代理服务本身工作正常,但只服务于同子网客户端。

第五步:代理侧抓包确认(关键证据)

在代理机器 172.20.6.B1 上用抓包工具过滤 ip.addr==172.20.2.A1 && tcp.port==1080

捕获到的 TCP payload(hex):

1
47455420687474703a2f2f7777772e62616964752e636f6d2f20485454502f312e310d0a...

解码后的 HTTP 请求:

1
2
3
4
5
GET http://www.baidu.com/ HTTP/1.1
User-Agent: curl/7.29.0
Host: www.baidu.com
Accept: */*
Proxy-Connection: Keep-Alive

stream 中仅有此一条请求数据,代理侧无任何回复包。 HTTP 请求完整到达了代理进程,但代理选择静默丢弃,不做任何响应。这是应用层访问控制行为,排除了网络层问题。

第六步:发现 OpenWrt 中转方案

发现网络中有一台 OpenWrt 设备 (172.20.6.B3),配置了 nginx stream 四层 TCP 转发:

1
2
3
4
5
6
7
stream {
server {
listen 1080;
proxy_pass 172.20.6.B1:1080;
}
# ... 其他转发规则 ...
}

远程机器改用 OpenWrt 作为代理入口:

1
2
export http_proxy="http://172.20.6.B3:1080"
export https_proxy="http://172.20.6.B3:1080"

结果:HTTP 200 OK,通信正常。

根因分析

数据流对比

直连(失败)

1
2
3
172.20.2.A1 ────→ 172.20.6.B1:1080 (Windows SS)
来源IP=172.20.2.A1(跨子网)
→ Windows SS ACL 拒绝,静默丢弃

经 OpenWrt 中转(成功)

1
2
3
4
5
172.20.2.A1 ────→ 172.20.6.B3:1080 (nginx stream)

└──→ 172.20.6.B1:1080 (Windows SS)
来源IP=172.20.6.B3(同子网)
→ Windows SS 正常处理

根本原因

Windows 上的 Shadowsocks 客户端(或其内置的 HTTP 代理组件)配置了 ACL(访问控制),只允许同子网或特定来源 IP 的客户端使用代理。来自 172.20.2.0/24 的请求虽然 TCP 可达,但被应用层 ACL 静默丢弃。

nginx stream 四层 TCP 转发之所以能解决,是因为 SS 看到的连接来源变成了 172.20.6.B3(同子网),不会触发 ACL 拒绝。

解决方案

方案一:使用 OpenWrt nginx stream 中转(已验证可用)

在远程机器上:

1
2
3
export http_proxy="http://172.20.6.B3:1080"
export https_proxy="http://172.20.6.B3:1080"
export no_proxy="localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8"
  • 优点:无需修改 Windows SS 配置,立即可用
  • 缺点:增加一跳转发,依赖 OpenWrt 设备可用性

方案二:修改 Windows SS 的 ACL 配置

  1. 确认 PID 9036 对应的确切进程名:

    1
    tasklist /fi "PID eq 9036"
  2. 根据具体软件修改 ACL:

    • Shadowsocks-libev / Shadowsocks-windows: 检查 ACL 配置文件(JSON 中的 aclallowed_clients 字段)
    • Clash / Clash for Windows: 检查 allow-lanipv4 配置
    • v2ray / Xray: 检查 inbounds[].streamSettings.tcpSettings.acceptProxyProtocol 或路由规则
    • SSTap: 检查是否启用了 IP 过滤
  3. 172.20.2.0/240.0.0.0/0 加入允许列表即可

方案三:在远程机器上运行本地代理客户端

172.20.2.A1 上直接安装并运行 SS/v2ray 客户端,不走远程 HTTP 代理,改为本地 SOCKS5/HTTP 代理。

关键排查经验

  1. TCP 握手成功 ≠ 应用层可达:这次问题中 ping 通、端口通、三次握手正常,但应用层 ACL 拦截了请求
  2. 代理侧抓包是定位关键:在代理机器上抓包证实了 HTTP 请求已到达但无回复,排除了所有网络层问题
  3. 对比测试快速缩小范围:本机(同子网)vs 远程(跨子网)的对比直接锁定了子网差异
  4. 四层转发可以绕过应用层 ACL:nginx stream 这类纯 TCP 转发不改变 payload,但改变了源 IP,从而绕过了基于来源 IP 的访问控制