Skip to content

feat: 出站支持 HTTP 代理,hub 连接与 ping 探测都走 CONNECT 隧道 - #8

Open
Yuuuuu0 wants to merge 1 commit into
monitor-probe:mainfrom
Yuuuuu0:feat/http-proxy
Open

Yuuuuu0 wants to merge 1 commit into
monitor-probe:mainfrom
Yuuuuu0:feat/http-proxy

Conversation

@Yuuuuu0

@Yuuuuu0 Yuuuuu0 commented Sep 21, 2026

Copy link
Copy Markdown

Closes #4

内网机器只能经代理访问外网时,agent 连不上 hub。这个 PR 让出站走 HTTP CONNECT 隧道。

做法

新增 --proxy / MONITOR_PROXY,agent 先连代理并发 CONNECT hub:443,其上的 TLS、WebSocket
升级和所有帧都在隧道里跑。TLS 握手的 SNI 与证书校验仍然是对 hub 的,代理是管道不是对端,只看得到
CONNECT 那一行。hub 的域名由代理解析,本机没有可用 DNS 也能连上。

monitor-agent --server https://your-hub --token <token> --proxy http://10.0.0.8:3128
monitor-agent --server https://your-hub --token <token> --proxy http://user:pass@10.0.0.8:3128
形式 说明
http://host:port、host:port 前缀可省
http://user:pass@host:port Basic 认证,密码含 @ 按最后一个 @ 切
http://[fd00::1]:3128 IPv6 要加方括号
https://、socks5:// 明确报错,不是这里说的协议

端口必须写全:http://10.0.0.8 直接报错退出,不会悄悄去连 80。

取舍

探测也走隧道。 配了代理之后 agent 唯一的直连对象就是代理本身,hub 下发的 ping 探测同样经
CONNECT。留成直连的话,这类机器上每个目标都读成 -1,什么也说明不了。代价是延迟量的是
「本机 → 代理 → 目标」整条路,比直连多一跳且含代理解析域名的时间,和直连节点的读数不可横向
比较——这点写进了 README。-1 的含义不变:目标不答、代理拒绝、代理不通,从这台机器看是同一件事。

隧道探测另给 3 秒上限。把 HANDSHAKE_DEADLINE 压在 1 秒内的理由是内核的 SYN 重传计时器,那条
理由在隧道上不成立——这里量的是代理解析目标并与之握手,再按 900ms 算的话,代理邻近范围之外的
目标全会读成 down。3 秒仍远在探测间隔 5 秒的下限之内。

两处安全考虑

  • 探测目标由 hub 下发,又要进 CONNECT 请求行。带 CR/LF 的目标在写出任何字节前就拒掉,否则
    hub 能往代理请求里塞自己的头,甚至跟一整个请求。既有注释已把「hub 可能被攻陷或只是有 bug」
    算在威胁模型内,这里同样按不可信处理。
  • 代理在 200 之后抢先发来的字节判错而不是丢弃。隧道里是客户端先说话(TLS ClientHello,或
    --insecure 下的升级请求),这些字节不可能是合法的,丢掉会让其后每个字节都错位。

兼容性

不配代理时每条路径都退回原状:session 走原来的 dial,NAT 时优先 v4 的逻辑不变,探测走原来的
解析加握手,Proxy::parse 根本不会被调用。MONITOR_PROXY 留空等同于不配,安装脚本可以无条件
写这个变量。未引入新依赖,Cargo.toml / Cargo.lock 没动,Basic 凭据的 base64 就地实现
(16 行,只为这一个头)。

版本号没动,按仓库惯例留给发版时的 chore 提交。

测试

新增 9 个单测(代理地址解析各形态、base64、隧道握手与请求行、IPv6 目标的方括号、407 原样带进
错误、200 后抢跑的字节、CRLF 注入、代理不答、探测经隧道且不做本机解析),cargo fmt --check
与 cargo clippy --all-targets -- -D warnings 干净。

在 Linux 容器里用假 hub(会说 WebSocket、下发 ping 任务)加需要认证的假 CONNECT 代理端到端跑过:

counting traffic on: eth0
reaching the hub and every probe target through proxy 127.0.0.1:45369
connected to 127.0.0.1:38541 via proxy 127.0.0.1:45369
PROXY CONNECT 127.0.0.1:38541  <- hub
HUB upgrade: GET /api/agent/ws HTTP/1.1 | token: True
PROXY CONNECT 127.0.0.1:42697  <- probe target
TARGET accepted from 127.0.0.1:48724

不配代理时同一场景的日志与改动前逐字相同(A/B 对比过改动前后的二进制)。musl 静态产物已在实际
服务器上部署验证通过。

只能经代理上网的机器此前无法上报。新增 --proxy / MONITOR_PROXY,agent 向代理发
CONNECT,其上的 TLS、WebSocket 升级与所有帧都在隧道里跑:TLS 握手的 SNI 与证书
校验仍然是对 hub 的,代理只看得到 CONNECT 那一行。hub 的域名由代理解析,本机没有
可用 DNS 也能连上。

配了代理之后唯一的直连对象就是代理本身——hub 下发的 ping 探测同样走隧道,否则这类
机器上每个目标都读成 -1,什么也说明不了。代价是延迟量的是「本机 → 代理 → 目标」
整条路,比直连多一跳且含代理解析域名的时间,与直连节点的读数不可横向比较,这点写进
了 README。探测的隧道另给 3 秒上限:把 HANDSHAKE_DEADLINE 压在 1 秒内的理由是内核
的 SYN 重传计时器,那条理由在隧道上不成立。

探测目标由 hub 下发又要进 CONNECT 请求行,带 CR/LF 的目标在写出任何字节前就拒掉,
否则 hub 能往代理请求里塞自己的头;代理在 200 之后抢先发来的字节判错而不是丢弃,
否则隧道里每个字节都会错位。

不配代理时每条路径都退回原状:session 走原来的 dial,NAT 时优先 v4 的逻辑不变,
探测走原来的解析加握手。MONITOR_PROXY 留空等同于不配,安装脚本可以无条件写这个变量。
未引入新依赖,Basic 凭据的 base64 就地实现。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[功能请求] 出站连接支持 HTTP 代理

1 participant