# Cloudflare 命名隧道（固定公网域名）

内置的 cloudflared 通道有两种模式，在面板 **移动访问 → 远程 → cloudflared → 隧道类型** 中切换：

| | 快速隧道（默认） | 命名隧道 |
| --- | --- | --- |
| 账号 | 不需要 | 需要 Cloudflare 账号 |
| 地址 | 每次启动随机分配 `*.trycloudflare.com` | 你自己域名下的固定主机名 |
| 重启后 | 地址变化，需要重新扫码 | 地址不变，已配对设备继续可用 |
| 可用性 | 官方定位为测试用途：有限流、无可用性保证 | 由你的 Cloudflare 账号承载 |
| 配置项 | 无 | 连接器令牌、公网域名、本机转发端口 |

命名隧道的公网域名由 Cloudflare 负责解析与 TLS，插件只负责在本机运行 `cloudflared` 并把流量交给已认证的私有网关；**手机端仍然要走 App 的远程访问扫码流程**，隧道不改变配对方式。

## 前置条件

1. 一个已接入 Cloudflare 的域名（该域名的 NS 必须指向 Cloudflare 分配的名称服务器，在域名注册商处修改；改完通常几分钟到 24 小时生效）。
2. Cloudflare Zero Trust（团队域名）已启用——隧道控制台位于其中。
3. 面板中 cloudflared 组件已安装（命名隧道和快速隧道使用同一个官方客户端）。

## 在 Cloudflare 控制台创建隧道

1. 打开 **Zero Trust → Networks → Tunnels**，选择 **Create a tunnel**，类型选 **Cloudflared**。
2. 命名（例如 `dsh-mobile`），保存后会显示 connector 安装命令。
3. 在 **Public Hostname** 页签添加一条：
   - Subdomain：`dsh`，Domain：选你的域名（得到 `dsh.example.com`）
   - Service：**HTTP** → `127.0.0.1:3444`
4. 回到 **Overview** 页签复制连接器令牌（一长串以 `eyJ` 开头的字符串）。令牌里已经包含账号、隧道 ID 和隧道密钥，**等同于密码**。

> `127.0.0.1:3444` 就是面板里的「本机转发端口」。Cloudflare 把公网主机名固定转发到这个端口，所以它必须与面板中填写的端口一致，并且长期不变。

## 在 DSH Mobile 面板中填写

1. **移动访问 → 远程 → cloudflared**。
2. 隧道类型选 **命名隧道**。
3. 依次填写：
   - **公网域名**：`dsh.example.com`
   - **本机转发端口**：`3444`（与第 3 步的 Service 端口一致）
   - **连接器令牌**：粘贴上一步复制的令牌
4. 点击 **保存并连接**。

已保存后令牌输入框留空表示不更改；输入框里不会再回显已保存的令牌。「移除已保存的令牌」会**同时停止 cloudflared 通道**并把配置改回快速隧道。不会自动重连，之后需要手动重新打开开关。

## 用手机连上命名隧道

切到固定域名后，**已经配对过的手机必须重新配对**：DSH Mobile 的设备凭据只发给它当初保存的那个精确 Origin（这是防止换域名骗走凭据的安全设计），所以旧地址上的凭据在新域名上一律无效。

1. 电脑面板 → **远程** → 点 **生成远程配对二维码**。这一步才会打开配对窗口，窗口有时限；没打开时任何设备都进不来。
2. 手机 App → **先进入「远程」入口**（连接中心的远程访问设置页）→ 再扫描二维码。

> **命名隧道必须在「远程」入口里扫码。** App 只把 `.ts.net`、cpolar、`.trycloudflare.com` 这类平台后缀当作「无需询问即为远程」；你自己的域名同样会被识别为可远程的候选地址，但「局域网」流程只接受非候选地址，所以在局域网界面扫码会被判为无效并提示**无效二维码 / Invalid QR code**，看起来就像「连不上」。先进入「远程」流程再扫即可。
>
> 不想扫码时，完整链接（`https://你的域名/mobile-access/pair#instance=…&token=…`）可以整条复制到手机上粘贴，App 的输入框接受完整链接。

设备列表里原来那条远程记录会显示「地址可能已变化 / Address may have changed」或不可达，重新配对成功后删掉即可。

## 安全边界

- 令牌只写入 DSH Mobile 私有目录（`~/.dsh/mobile-access/remote/cloudflared/tunnel.json`，权限 0600），并且**只**通过子进程环境变量 `TUNNEL_TOKEN` 传给 `cloudflared`，不出现在命令行里（本机任何进程都能读到命令行）。
- 令牌从不回传给浏览器或手机端；面板状态里只有「是否已配置」、域名和端口。
- 隧道背后是 DSH Mobile 自己的认证网关：公网主机名只暴露该网关，DSH 本体仍然需要配对设备凭据。
- 插件不会创建系统服务、开机启动项、注册表项或 PATH 项；关闭通道即结束进程。

## 错误码

| 面板提示 | 实际错误码 | 含义与处理 |
| --- | --- | --- |
| 本机转发端口不可用 | `cloudflared_tunnel_port_unavailable` | 配置的端口已被占用。命名隧道不能改用其他端口（Cloudflare 固定转发到该端口），请释放端口或换一个端口并同步修改 Cloudflare 的 Service。 |
| 公网域名无效 | `cloudflared_tunnel_hostname_invalid` | 必须是本账号下域名的真实主机名；不接受 IP、通配符、`.trycloudflare.com` 与 `.cfargotunnel.com`。 |
| 转发端口无效 | `cloudflared_tunnel_port_invalid` | 端口需在 1024–65535 之间。 |
| 转发端口被保留 | `cloudflared_tunnel_port_reserved` | 不能填 **3443**：那是 DSH Mobile 局域网网关的 HTTPS 端口，只要 DSH 在运行就一直被占用，不是"临时被别的程序占了"。请换 3444 或 3445。 |
| 令牌无效 | `cloudflared_tunnel_token_invalid` | 令牌需从控制台完整复制，不能有空格或换行。 |
| 设置未通过校验 | `cloudflared_tunnel_settings_invalid` | 请求里带了不该有的字段（例如快速隧道模式下携带端口）。 |
| 需要同时填写令牌、域名和端口 | `cloudflared_tunnel_config_missing` | 首次配置命名隧道时三项都必填。 |
| 已保存配置无法读取 | `cloudflared_tunnel_config_invalid` | 配置文件损坏或不符合格式；插件会退回快速隧道，重新保存即可。 |
| 已保存配置不是普通文件 | `cloudflared_tunnel_target_invalid` | `tunnel.json` 变成了符号链接或非常规文件。移除它并重新保存设置。 |
| 无法预留本机端口 | `cloudflared_port_reservation_failed` | 本机端口预留本身失败（不是端口被占）。重试；若持续出现请检查系统资源。 |
| 组件下载校验失败 | `cloudflared_download_hash_mismatch` / `cloudflared_download_size_mismatch` | 下载到的二进制与固定版本的大小或 SHA-256 不符。重新安装；若反复失败说明中间链路在改包。 |
| 已安装组件校验失败 | `cloudflared_executable_hash_mismatch` | 本机那份 cloudflared 与校验过的版本不一致。彻底移除后重新安装。 |
| 当前构建不支持该组件 | `cloudflared_component_unsupported` | 当前平台不在支持范围内（目前支持 Windows x64 与 Linux x64/arm64）。 |
| 等待隧道可用超时 | `cloudflared_start_timeout` | connector 在超时预算内没有打印 `Registered tunnel connection`。命名隧道下进程活着会继续等（最多约 5 分钟）；常见原因是网络到 Cloudflare 边缘不通。 |
| 组件未安装 | `cloudflared_component_missing` | 还没安装官方组件。按上面的准备步骤安装。 |
| 组件校验失败 | `cloudflared_component_invalid` | 本机组件与校验过的版本不符。彻底移除后重新安装。 |
| 无法分配端口 | `cloudflared_port_unavailable` | 快速隧道模式下无法分配本机网关端口。重试。 |
| 客户端启动失败 | `cloudflared_launch_failed` | cloudflared 进程没能启动。检查本地日志。 |
| 连接已停止 / 意外退出 | `cloudflared_stopped` / `cloudflared_exited` | 通道已断开，点重新连接。 |
| 返回内容无法识别 | `cloudflared_invalid_output` / `cloudflared_invalid_origin` | cloudflared 输出或公网地址未通过校验。重新连接并复制诊断报告。 |
| 网关启动失败 | `gateway_start_failed` | 隧道背后的认证网关没能启动（不是端口冲突）。检查本地日志。 |
| 下载跳转异常 | `cloudflared_download_redirect_missing` / `_invalid` / `_rejected` | 官方下载页没有跳到发布资源，或跳转目标不是 GitHub 发布资源。稍后重试，或按官方页面手动安装。 |

## 排错

- **连接一直停在「正在连接」**：命名隧道没有横幅，只有 connector 真正注册后才算就绪。查看 DSH 日志里 cloudflared 的输出；预检表（`CONNECTIVITY PRE-CHECKS`）会指出是 DNS、UDP/QUIC 还是 TCP 不通。
- **公网访问返回 1033**：Cloudflare 认为该主机名没有健康的 connector。确认隧道在 Zero Trust 里显示 Healthy，且 ingress 指向的端口与面板一致。
- **`cloudflared` 报 `Unauthorized` 或隧道 ID 不存在**：令牌与控制台里的隧道不匹配（例如隧道被删除后重建）。重新复制令牌。
- **本机开着 TUN/透明代理（Clash、Mihomo、Clash Verge 等）：手机端会卡在「正在加载插件」并反复重试。** 这是实测到过的一类真实故障，症状很容易被误判成配对或插件问题：
  - `cloudflared` 到 `region*.v2.argotunnel.com` 的连接没有任何专属规则，于是落到兜底的 `Match`/`MATCH` 规则，被送进代理节点。用内核 API 看得很清楚：
    ```
    cloudflared.exe -> region2.v2.argotunnel.com
      rule=Match   chains=["<某个机场节点>","漏网之鱼"]   up/down = 103 MB / 2.1 MB
    ```
  - 结果下行被压到 **8–100 KB/s**。而手机端 DSH 启动要一次拉 **4.5 MB**（压缩后）的 boot bundle，加载器等不到就报 `bundle script failed to load`，App 表现为「加载 → 失败 → 重试」。
  - 同一份文件在局域网网关上是 **0.2 秒**，加上直连规则后走隧道是 **9.9 秒 / 454 KB/s** —— 差了两个数量级。
  - 修法：让 cloudflared 与其边缘域名走直连。Clash Verge 的**全局扩展配置**（`profiles/Merge.yaml`，订阅更新不会覆盖）：
    ```yaml
    prepend-rules:
      - PROCESS-NAME,cloudflared.exe,DIRECT
      - DOMAIN-SUFFIX,argotunnel.com,DIRECT
      - DOMAIN-SUFFIX,trycloudflare.com,DIRECT
    ```
    改完要让 cloudflared **重连**才会换路由：内核热重载不会迁移已建立的长连接（面板里点重连，或开关一次提供方）。
  - 判断方法：如果手机端一直卡在加载插件，先在**本机**用同一份会话拉一次 boot bundle 看速率。若本机也很慢，就不是手机或配对的问题。
- **域名解析还是旧地址**：改完 NS 后 Cloudflare 需要把 zone 从 Pending 变为 Active；A/CNAME 在 zone 激活前不会对外生效。
