NNetpath
原理预计 35 分钟

受限网络下的技术访问:症状与选型

拉不下依赖、打不开文档时,先分清是 DNS、SNI、IP 还是限速,再决定用什么方案。

学完这节你能做到

  • 用命令区分 DNS 污染、连接重置、IP 不可达与单纯超时
  • 按团队条件在自建与订阅服务之间做出选择
  • 看懂线路标签并用 mtr 自己验证回程质量
  • 列出自建方案的必要组件与常见配置项

先做正确的诊断

"打不开"是症状,不是原因。工程师能做的第一件有价值的事,是把它归到某一类:

症状表现归类
域名解析到了明显不对的地址dig 结果和权威 DNS 不一致DNS 层面
TCP 连上了,TLS 握手时断curl -v 卡在握手后 Connection reset握手阶段被打断
SYN 发出去没人应nc -zv 超时,traceroute 在某跳后消失地址不可达
能连、能传,但极慢吞吐只有几十 KB/s,重传率高限速或丢包

四类的处置方式完全不同,混在一起猜会浪费大量时间。

一套诊断命令

# 1. DNS:换不同解析器对比,结果不一致就是解析层面的问题
dig +short github.com @1.1.1.1
dig +short github.com @8.8.8.8
dig +short github.com                    # 本地/运营商 DNS
# 用 DoH 拿一个可信参照
curl -s 'https://cloudflare-dns.com/dns-query?name=github.com&type=A' \
  -H 'accept: application/dns-json' | jq -r '.Answer[].data'

# 2. TCP 可达性:先确认三次握手本身成不成
nc -zv github.com 443
# 在哪一跳消失
traceroute -T -p 443 github.com

# 3. TLS 握手:分辨是连不上还是握手被打断
curl -vI --connect-timeout 5 https://github.com 2>&1 | tail -20
openssl s_client -connect github.com:443 -servername github.com </dev/null 2>&1 | head -15

# 4. 绕过 DNS 直接测某个 IP,用来验证"是 DNS 的问题还是 IP 的问题"
curl -vI --resolve github.com:443:140.82.121.4 https://github.com

# 5. 速度与稳定性
curl -o /dev/null -w '%{speed_download}\n' https://speed.cloudflare.com/__down?bytes=10000000
nstat -az | grep -iE 'retrans|timeout'
第 4 条是最有价值的一条

curl --resolve 让你跳过 DNS 直接指定 IP。它把两个变量分开了:

  • --resolve 指定正确 IP 后能通 → 问题在 DNS 解析
  • 指定正确 IP 仍不通 → 问题在 连接层(地址不可达或握手被打断)

一条命令把诊断范围砍掉一半。同样的思路适用于任何"域名相关"的疑难问题, 不只是受限网络场景。

判断树

拉不下依赖 / 打不开文档
   │
   ├─ dig 各解析器结果不一致,或返回明显不对的地址
   │     → DNS 层面:换加密 DNS(DoH/DoT),或让代理侧解析(socks5h)
   │
   ├─ nc -zv 超时、traceroute 在某跳后消失
   │     → 地址层面:这个出口到该目标不可达,需要换出口
   │
   ├─ TCP 通但 TLS 握手阶段断开
   │     → 握手阶段被打断:需要隧道把握手包裹在另一层里
   │
   └─ 都正常但极慢、重传高
         → 限速或链路质量:换线路,或先确认不是自己这端的问题(L0 那套体检)
i很多情况根本不需要隧道

在下结论之前先试镜像源——对开发者场景,这往往是更快、更稳、也更省事的答案:

生态镜像方案
GoGOPROXY=https://goproxy.cn,direct
Pythonpip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Nodenpm config set registry https://registry.npmmirror.com
Rust~/.cargo/config.toml 里配 crates.io 镜像
容器镜像公司内部 Harbor / 镜像加速,或自建 pull-through cache
系统包换 apt/yum 的镜像源

镜像的好处是没有额外链路、没有隧道可断、CI 里也能用。 只有镜像覆盖不到的场景(文档站点、issue 讨论、私有仓库、某些 SDK 下载)才需要往下走。

自建还是用现成服务

维度自建(VPS + 自己部署)订阅服务
成本一台小 VPS,按月固定按月,通常更便宜
可控性完全可控,出口 IP 固定出口共享,IP 可能被目标站点风控
稳定性取决于你选的线路和维护取决于服务方
隐私流量只经过自己的机器流量经过第三方
维护成本要自己管:证书续期、升级、监控
适合长期、要固定出口、要接 CI临时、个人、不想维护

给工程师的实际建议:如果需要在 CI 或服务器上稳定使用,自建—— 因为你需要固定出口 IP 和可预期的可用性。个人临时用途没必要。

VPS 选择的几个要点

  • 地域:香港延迟最低(大陆访问通常几十毫秒),美西便宜、机房和线路选择多,但延迟通常 在一百多到两百毫秒。做交互式操作(SSH、Web 控制台)地域的影响比带宽大得多
  • 流量与带宽:看清是"月流量配额"还是"不限量但限速"。前者用超会直接停机, 拉几个大镜像就可能触顶
  • 回程线路同一机房不同线路的体验能差好几倍,这是最容易被价格掩盖的一项
  • 快照与重建:选支持快照的,配置搞坏了能回滚;同时看清换 IP 的政策(IP 被封时能不能换)

线路名词对照

报价页上那几个缩写是真正决定体验的东西:

标签是什么体验
CN2 GIA电信 CN2 的高优先级产品(AS4809)最好:低丢包、晚高峰不塌,也最贵
CN2 GT电信 CN2 的普通档中等:比普通骨干好,晚高峰仍会拥塞
163 / AS4134电信普通骨干最便宜,晚高峰丢包和抖动明显
AS4837 / CU4837联通骨干性价比常见选择,质量介于两者之间
Eyeball面向末端消费者网络优化的线路(eyeball network 是行业术语,指承载大量家庭宽带用户的 ISP)到家宽用户的路径更短

判断线路真假不靠商家宣传,看 traceroute 的骨干跳:途经 59.43.x.x 说明走了 CN2, 只见 202.97.x.x 则是普通骨干(这个判据来自 haoel 的科学上网笔记)。

具体推荐

下面这几家按价格从高到低、稳定性也从高到低排列 —— 这两件事在这个领域基本是正相关的:

服务商机房与线路定位
搬瓦工 bwh8.netUS · E-Commerce VPS最贵也最稳。要长期挂服务、不想操心的选它
DMIT dmit.ioUS · Premium / Eyeball次之。线路分档细,按用途挑
CubeCloud cubecloud.netHK · CN2 GIA / US · CN2 GIA性价比档。要低延迟就选香港那条
CloudCone cloudcone.comUS最便宜,稳定性最弱。适合临时验证、随时可弃

按用途对应:

  • CI 或服务器上长期用(拉依赖、跑构建)→ 取前两档。这类场景断一次就是流水线红一片, 稳定性的钱值得花
  • 个人日常看文档、查 issue → 第三档足够,香港线路的交互体验明显更好
  • 临时验证一个想法、或者只是想练一遍部署 → 最后一档,用完就删
下单前先自己测一遍回程

关键是回程(VPS → 大陆),不是去程。 从自己电脑 traceroute 出去测的是去程, 而中国大陆的国际链路去回程经常走完全不同的路径 —— 去程漂亮、回程拥塞是很常见的组合。

所以要在 VPS 上反向测

# 在 VPS 上装 mtr,往大陆三网各测一个地址
mtr -r -c 20 202.96.209.133      # 电信(上海)
mtr -r -c 20 210.22.84.3         # 联通(上海)
mtr -r -c 20 211.136.112.50      # 移动(上海)

# 看骨干跳里有没有 59.43(CN2)
mtr -r -c 20 202.96.209.133 | grep -E '59\.43|202\.97'

看两件事:丢包出现在哪一跳,以及晚高峰(20:00–23:00)的延迟涨多少。 白天测都好看,晚高峰才见真章 —— 至少跨一个晚高峰再决定要不要长租。

多数商家有几天的退款窗口,先按月付测一轮再转年付,比一上来省 20% 划算。

!价格、库存与线路都会变

这个市场变动很快:机房线路会被降级、库存补货靠抢、同一款产品不同批次的实际线路可能不同。 上面的排序是长期口碑而不是当下承诺,下单前一定用上面那套 mtr 自己验一遍。

另外注意两件事:不要用同一台机器承载重要业务和这类用途(IP 被封会一起受影响), 以及账号别复用密码 —— 这类商家的安全水位参差不齐。

服务端的基础配置

# 1. BBR:长肥链路上收益最直接(L0 讲过原因)
cat >> /etc/sysctl.conf <<'EOF'
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
EOF
sysctl -p
sysctl net.ipv4.tcp_congestion_control      # 确认生效

# 2. 域名 + 真实证书(自签会立刻暴露)
certbot certonly --standalone -d proxy.example.com
# 续期交给 cron / systemd timer

# 3. 服务本体:用 gost 这类工具跑在 TLS/HTTP2 之上(见 gost 那一节)

三条纪律:用真实域名和证书、加认证、只开必要端口。 上一节说过,不加认证的代理挂公网上几小时就会被扫到滥用。

开发者场景:让各个工具走代理

假设本地代理是 127.0.0.1:1080(SOCKS5)和 127.0.0.1:8080(HTTP)。

# 通用环境变量(记得大小写都设,NO_PROXY 写全)
export http_proxy=http://127.0.0.1:8080  HTTP_PROXY=$http_proxy
export https_proxy=$http_proxy           HTTPS_PROXY=$http_proxy
export all_proxy=socks5h://127.0.0.1:1080
export no_proxy="localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.internal,.svc,.cluster.local"

一个方便的开关(思路同样出自上面那份笔记):

proxy()   { export http_proxy=http://127.0.0.1:8080 https_proxy=$http_proxy; echo "proxy on"; }
unproxy() { unset http_proxy https_proxy; echo "proxy off"; }
with_proxy() { http_proxy=http://127.0.0.1:8080 https_proxy=http://127.0.0.1:8080 "$@"; }
# 用法:with_proxy curl -I https://example.com

各工具的独立配置:

# Git(HTTPS 协议)
git config --global http.proxy socks5h://127.0.0.1:1080
# 只给某个域名走代理,避免影响内网仓库
git config --global http.https://github.com/.proxy socks5h://127.0.0.1:1080
# Git(SSH 协议)—— ssh 不认环境变量,写进 ~/.ssh/config
Host github.com
  User git
  ProxyCommand nc -x 127.0.0.1:1080 %h %p
  # 或者 ProxyCommand socat - SOCKS4A:127.0.0.1:%h:%p,socksport=1080
# Docker daemon 是系统服务,环境变量对它无效
# /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:8080"
Environment="HTTPS_PROXY=http://127.0.0.1:8080"
Environment="NO_PROXY=localhost,127.0.0.1,.internal,10.0.0.0/8"
systemctl daemon-reload && systemctl restart docker
# apt
# /etc/apt/apt.conf.d/95proxy
Acquire::http::Proxy "http://127.0.0.1:8080";
Acquire::https::Proxy "http://127.0.0.1:8080";
×容器里的 127.0.0.1 不是宿主机的 127.0.0.1

在容器或 Pod 里把代理配成 127.0.0.1:8080 一定不通——那是容器自己的 loopback。 要用宿主机地址:Docker Desktop 用 host.docker.internal, Linux 上用 docker0 的网关地址(通常 172.17.0.1)或者 --network host

同理,docker build 时用的是 daemon 的网络与代理配置,不是你 shell 里的环境变量, 要用 --build-arg HTTP_PROXY=... 传进去。这两件事每个人都会踩一次。

用途与边界

!限定用途

这一阶段的内容面向技术资料访问与问题排查:拉依赖、看官方文档、读 issue 讨论、 访问上游代码仓库。

三条实际的纪律:

  • 遵守所在地法律法规与所在公司的网络与合规政策,公司资产上的任何网络改动先走内部流程
  • 不要把它变成绕过公司安全管控的通道:审计、DLP、零信任策略存在是有原因的, 绕过它们通常违反雇佣协议
  • 技术手段本身中立,用途不中立。本站只讨论原理、配置与排障,不涉及其它用途

检查点

检查点单选

pip install 一直超时。用 curl --resolve 指定正确 IP 后能正常访问。问题在哪一层?

检查点单选

在容器里把代理配成 http://127.0.0.1:8080,结果完全不通。为什么?

检查点单选

团队 CI 需要长期稳定拉取境外依赖。最优先该做什么?

这节课的落点

  • 先诊断再动手:分清 DNS 层面 / 地址不可达 / 握手被打断 / 限速 四类
  • curl --resolve 是性价比最高的一条命令,一步把 DNS 与连接问题分开
  • 开发者场景优先用镜像源:Go/pip/npm/cargo/容器镜像都有成熟方案,比隧道更稳
  • 需要在 CI 或服务器上长期用才值得自建;个人临时用途不必
  • VPS 关注地域、流量口径与回程线路(看 traceroute 的骨干跳)
  • 服务端三件事:开 BBR、真实域名与证书、务必加认证
  • 环境变量只对认它的程序有效;ssh/git over ssh/Docker daemon/apt 各有各的配法
  • 容器里的 127.0.0.1 不是宿主机;docker build 用的是 daemon 的代理
  • 用途限定在技术资料访问,遵守当地法规与公司合规政策

延伸资料