HTTP 代理入口(tp-*)
HTTP 代理入口(tp-*)
把 https://tp-<name>.<domain> 填进浏览器或操作系统的“HTTPS 代理”,就能从任意被允许的来源 IP
经指定 Agent 出网或访问该 Agent 的内网服务,用户机器上不需要安装 tunnelmesh-client。
出口 Agent、认证方式与来源 ACL 全部由管理员在后台集中配置,创建后 5 秒内生效。
反向代理路由(显式子域名与 tm-* 泛域名)是另一件事,见托管 HTTP 路由。
与 client 本地代理的区别
tunnelmesh-client forward socks5 / forward http-proxy |
托管入口 tp-*(本文) |
|
|---|---|---|
| 用户机器 | 必须安装并运行 client | 什么都不用装 |
| 监听位置 | 用户本机端口 | 管理员的公网 443(复用既有入口,不新增端口) |
| 支持协议 | SOCKS5 与 HTTP 代理都支持 | 只支持 HTTP/HTTPS 代理(CONNECT 与绝对形式请求) |
| 出口选择 | client 命令行指定 Agent | 管理员在路由上指定 Agent |
| 认证 | client 本地可选 | 管理员配置:无认证 / 用户名密码(Basic) |
| 来源控制 | 本机监听地址 | 源 IP/网段 ACL,默认拒绝所有来源 |
| 目标控制 | client 侧策略 | 管理员配置目标网段、端口、是否允许内网目标 |
| 审计与指标 | client 侧日志 | Server 侧审计事件 + Prometheus 指标 + Grafana Row |
两者可以并存:需要 SOCKS5、或需要用户自己掌控出口时用 client;要给一批人免安装、可集中管控的
出网通道时用 tp-*。
管理员:后台操作
路由管理 → 添加托管路由 → 协议选 HTTP 代理入口 (tp-),表单会切换成代理专属字段:
| 字段 | 说明 |
|---|---|
| 代理名称 | 只填 <name>,小写字母、数字与中划线,最长 32 字符;最终域名自动拼成 tp-<name>.<domain_suffix>,表单下方实时预览完整域名 |
| 代理节点 | 出口 Agent。一条路由只有一个出口,没有负载与故障转移 |
| 认证方式 | 无认证 或 用户名密码 |
| 代理凭据 | 认证方式为“用户名密码”时必填。需要先在密钥管理里新建一个 代理账号(proxy_basic)类型的凭据,填 username 与 password;密码用 AES-256-GCM 加密保存,列表与日志都不会回显 |
| 允许的来源 IP/网段 | 逗号分隔的 CIDR。默认拒绝所有来源;0.0.0.0/0 表示放开全部,也可以直接点“放开全部来源”。裸 IP 会自动补 /32(IPv6 补 /128) |
| 目标网段限制 | 可选。留空表示不额外限制(仍受危险地址过滤约束) |
| 目标端口限制 | 可选。留空表示任意端口;对域名目标和 IP 字面量都生效 |
| 允许访问内网目标 | 默认开。关闭后回环、私网、链路本地与 ULA 目标一律拒绝;云 metadata 地址(如 169.254.169.254)无论开关都恒拒 |
| 并发隧道上限 | 单路由上限,0 表示只受服务端全局上限约束 |
| 备注 | 256 字以内,只在后台展示 |
保存后列表会多出“代理地址”“认证方式”“ACL 条数”三列(只在存在 tp-* 路由时渲染,避免表格出现
横向滚动条),“目标”列显示“按请求动态决定”而不是哨兵 *:0。点“使用说明”打开抽屉,里面有完整
代理 URL 与各端配置示例,可直接复制发给使用者。活跃隧道数不在列表里,看 Grafana 统一 Dashboard
的 Row HTTP Proxy Entry → 面板 Proxy tunnels active。
生效时间 <= 5 秒,不需要重启 Server,也不需要改 nginx(前提是 OpenResty 侧已按
OpenResty tp-* 代理入口 部署好,且泛解析 DNS 与通配证书
覆盖 *.<domain_suffix>)。
管理员:API 方式
curl -sS -X POST https://tm.example.com/api/v1/routes \
-H "Authorization: Bearer <token>" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 3f2b8c1e-6d47-4a19-9c0e-2b7f0a5d9e11' \
--data-raw '{
"agentId": "agt_01HZX...",
"protocol": "http-proxy",
"domain": "tp-demo.tm.example.com",
"authMode": "basic",
"credentialId": "cred_01HZY...",
"sourceCIDRs": ["11.71.85.0/24"],
"targetPorts": [80, 443],
"allowPrivateTargets": true,
"maxConcurrentTunnels": 64,
"description": "研发出网通道"
}'
要点:
domain必须是完整域名tp-<name>.<suffix>,服务端不接受只传<name>;<name>小写、 最长 32 字符。- 不要传
targetHost/targetPort/config/hostHeader/targetScheme/tlsServerName。 真实目标来自每个代理请求,服务端会把目标固定成哨兵*/0。更新(PATCH)时同理,带上非哨兵值 会被拒绝。 authMode为basic时credentialId必填,且必须指向一个调用者有权读取、处于启用状态的proxy_basic凭据。- 一条
http-proxy路由独占整个域名:同域名下再建任何路由都会 409。 - 写操作支持
Idempotency-Key,重放同一个键返回已存的 201 响应而不是 409。
三类常见 400:
| 场景 | 服务端返回 |
|---|---|
传了 targetHost 或 targetPort |
targetHost must be omitted for http-proxy routes / targetPort must be omitted for http-proxy routes |
在 http-proxy 与 http/websocket 之间切换协议 |
protocol cannot be changed to or from http-proxy; create a new route |
| CIDR 或端口非法 | 对应的字段校验错误;非法 ACL 条目按“拒绝”处理,不会静默放开 |
字段级完整定义见 openapi.yaml。
使用者:各端配置
代理地址形如 https://tp-demo.tm.example.com(scheme 必须是 https://,端口 443 可省略)。
只能填
http://代理地址的旧客户端无法使用本入口。 路由身份来自 TLS SNI,凭据也必须加密 传输;填http://意味着明文,服务端不会接受。
macOS:系统设置 → 网络 → 当前网卡 → 详细信息 → 代理 → 打开“安全网页代理(HTTPS)”,
服务器填 tp-demo.tm.example.com,端口 443,勾选“代理服务器要求密码”并填用户名密码。
命令行临时使用:
networksetup -setsecurewebproxy Wi-Fi tp-demo.tm.example.com 443
# 带 Basic 认证时,用户名与密码是第 4、5 个位置参数
networksetup -setsecurewebproxy Wi-Fi tp-demo.tm.example.com 443 '<username>' '<password>'
# 用完关闭
networksetup -setsecurewebproxystate Wi-Fi off
Windows:Internet 选项 → 连接 → 局域网设置 → 勾选“为 LAN 使用代理服务器”→ 高级,
在 HTTPS 一栏填 tp-demo.tm.example.com:443,并勾选“对 HTTPS 使用相同代理”。
PAC(Chrome / Firefox / 系统自动配置):
function FindProxyForURL(url, host) {
if (host === "localhost" || host === "127.0.0.1" || isPlainHostName(host)) return "DIRECT";
return "HTTPS tp-demo.tm.example.com:443";
}
curl:
# HTTPS 目标(走 CONNECT)
curl -x https://tp-demo.tm.example.com --proxy-user 'u:p' https://ifconfig.me
# 自签证书验证阶段加 --proxy-insecure;正式通配证书不需要
curl -sv --proxy-insecure -x https://tp-demo.tm.example.com --proxy-user 'u:p' https://ifconfig.me
# 明文 HTTP 目标(绝对形式请求,同样支持)
curl -x https://tp-demo.tm.example.com --proxy-user 'u:p' http://example.com/
SwitchyOmega 之类浏览器插件:代理协议选 HTTPS(不是 HTTP,也不是 SOCKS5),服务器填
tp-demo.tm.example.com,端口 443,认证填 Basic 用户名密码。
Firefox 可以单独设置代理而不影响系统:设置 → 网络设置 → 设置 → 手动配置代理 →
“也将此代理用于 HTTPS”勾选,SSL 代理填 tp-demo.tm.example.com:443。
错误码自助排查
所有拒绝都带稳定错误码,curl -v 或服务端审计日志里都能看到。隧道建立成功之后的失败只会表现为
连接断开,不会再有状态码。
| 状态码 | 错误码 | 你会看到什么 | 为什么 | 你能做什么 |
|---|---|---|---|---|
| 400 | proxy_target_invalid |
Received HTTP code 400 from proxy |
目标主机名或端口非法(端口不在 1-65535、主机名为空或含非法字符) | 检查请求的 URL;如果配了目标端口限制,确认端口在允许列表里 |
| 403 | proxy_route_identity_invalid |
Received HTTP code 403 from proxy after CONNECT |
路由身份非法:SNI 缺失、不是 tp-<name>.<suffix> 形式,或后缀不匹配 |
确认代理地址填的是完整域名且 scheme 为 https://;确认 DNS 泛解析与证书覆盖该后缀 |
| 403 | proxy_route_unavailable |
同上 | 路由不存在或已停用 | 找管理员确认路由名与启用状态 |
| 403 | proxy_source_denied |
同上 | 你的来源 IP 不在该路由的 ACL 里 | 找管理员把来源网段加进 ACL;注意 NAT/多层代理下服务端看到的是出口地址 |
| 403 | proxy_target_denied |
同上 | 目标被策略拒绝:命中危险地址(云 metadata 恒拒)、目标网段限制,或关闭了“允许内网目标”却访问内网 | 换目标,或找管理员调整目标网段/端口/内网开关 |
| 407 | proxy_auth_required |
浏览器弹认证框,或 curl 报 407 | 该路由要求 Basic 认证,但请求没带 Proxy-Authorization 或格式非法 |
补上用户名密码(--proxy-user 'u:p') |
| 407 | proxy_auth_failed |
同上 | 用户名或密码不匹配,或凭据被停用/删除 | 核对凭据;找管理员确认凭据状态 |
| 407 | proxy_auth_backoff |
连续失败后即使密码正确也持续 407 | 同一路由连续认证失败达到阈值(默认 5 次)后进入退避:30 秒起每次翻倍,上限 15 分钟。退避期内不做密码比对直接 407 | 停止重试,等退避窗口过去;管理员可在指标里看到 reason="proxy_auth_backoff" |
| 502 | proxy_egress_unavailable |
Received HTTP code 502 from proxy |
出口不可用:Agent 离线、内部入口不可达,或集群跨节点 relay 失败 | 找管理员查 Agent 在线状态与 relay |
| 503 | proxy_capacity_exhausted |
503,响应带 Retry-After: 5 |
并发隧道超限(全局或单路由) | 按 Retry-After 退避重试;找管理员调高上限 |
| 503 | credential_secret_unavailable |
503 | 服务端凭据密文存储不可用(未配置 TUNNELMESH_TOKEN_ENCRYPTION_KEY 或解密失败) |
找管理员检查密钥注入,这不是客户端问题 |
| 504 | proxy_egress_timeout |
504 | connect_timeout(默认 10s)内没能通过 Agent 建立到目标的连接 |
确认目标可达、Agent 在线;必要时找管理员调整超时 |
三个 403 对外文案刻意一致,避免外部枚举哪些 tp-* 名字存在;只有服务端日志、审计事件
proxy_route_denied 的 reason 与指标 error_class 能区分具体原因。
限制
- 不支持 SOCKS5。需要 SOCKS5 请用
tunnelmesh-client forward socks5。 - tp-* 入口不承载 UDP。该入口只处理 HTTP/HTTPS/WebSocket 的 CONNECT 与绝对形式请求;UDP 只能通过
forward udp从用户侧发起到 Agent 内网,或改用内嵌 VPN 网关(ADR 0002,仅-tags vpn构建提供)。 - 一条路由只有一个出口 Agent,没有负载与故障转移;Agent 离线时该路由直接 502。
- Basic 认证是路由级共享账号,没有 per-user 配额,也无法区分是哪个人在用;需要区分请为不同 人群建不同路由。
- 隧道建立之后的失败只会表现为连接断开,不会再有状态码——排障要看服务端日志与指标。
- 目标侧仍会做二次校验:Agent 侧对目标地址再做一遍 SSRF、回环、私网、链路本地、CIDR 与端口策略 检查,管理端放开不等于 Agent 一定放行。