arc network 回答一个问题:这台机器能不能按 arc 需要的方式访问外网?它只有一个子命令 doctor,跑四项检查——代理发现、DNS、一次 HTTPS 请求、一次 WebSocket 握手——每一项单独成行报告。分开报告是刻意的:"DNS 能解析但代理拒绝 CONNECT" 和 "什么都解析不了" 是两种不同的故障,修法也不同,一个笼统的通过/失败会把你到底遇到哪一种给盖住。
对照的是 arc 2.0.0-beta.48(构建 5a5316bde,2026-09-10)。单看版本号定不了这个——2.0.0-beta.48 这一轮周期里已经指过不止一个二进制——所以在相信下面任何输出逐字节一致之前,先跑一遍 arc --version,把它的第三个字段(commit)跟上面这个对一下;检查项和选项不管怎样都可能在不同版本之间变化。
用法
arc network doctor [options]全局选项(见 总览):--json、--view、--instance / -i(作用在哪个本地 ARC 实例;省略即 default),以及 --home(实例根;要指定作用在哪个实例,用 --instance)。doctor 不跟 daemon 打交道,所以有没有 daemon 在跑它都能用。
--target <url>:要探测的 HTTPS URL(默认https://main.abtnetwork.io/)--ws-target <url>:要探测的 WSS URL(默认wss://main.abtnetwork.io/api/websocket?vsn=2.0.0)
arc network 单独用不是一条命令:它会打印 Please specify a subcommand 和子命令列表,退出码 5。
示例(一切可达,没有代理)
$ arc network doctor
Outbound network doctor
Proxy:
(none)
Checks:
✓ proxy-discovery no proxy environment variables
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✓ https-through-proxy HTTP 200 direct
✓ wss-through-proxy WSS handshake direct
OK四项全过,退出码 0。那两项网络检查的名字里始终带着 through-proxy,哪怕根本没有代理;真正告诉你走的是哪条路的是后面那一列——这里是 direct。
direct-dns 那一行的地址,是你自己的解析器返回的结果,所以你看到的多半和这里不同;在有劫持或过滤解析器的网络上,它甚至可能是这个域名其实并不拥有的地址。这不只是个脚注,因为失败被归到哪一层是跟着解析器走的:一个不存在的域名,在如实回答的解析器上报成 DNS 失败,在照样给出地址的解析器上则报成连接或 TLS 失败。doctor 的结果让你意外时,先用别的途径独立确认这个域名,再决定要不要相信是哪一项变红了。
示例(配了代理,但代理不应答)
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 arc network doctor
ERROR: Outbound network doctor
Proxy:
HTTP_PROXY http://127.0.0.1:9/
HTTPS_PROXY http://127.0.0.1:9/
Checks:
✓ proxy-discovery proxy environment variables present (credentials redacted)
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✗ https-through-proxy PROXY_CONNECT_FAILED connect ECONNREFUSED 127.0.0.1:9
✗ wss-through-proxy PROXY_CONNECT_FAILED connect ECONNREFUSED 127.0.0.1:9
FAILED只要有检查失败,退出码就是 5。第一行的 ERROR: 前缀是 CLI 唯一的"这次运行失败了"标记(arc#6301)——不管是哪条命令报的失败,消息都以同一种方式开头,调用方不需要知道是哪条命令产出的,也能找到错误的开头。注意看哪些还是绿的:代理发现过了,因为它只报告设了哪些变量,不报告代理能不能用;DNS 也过了,因为解析根本不走代理。失败的那两项写出了原因——PROXY_CONNECT_FAILED——而不是一个笼统的超时,这正是让你去查代理、而不是去查 DNS 或目标主机的依据。代理 URL 打印时里面的凭据会被抹掉。
机器可读的输出
--json 把同一次运行输出成结构化数据。下面用的是上面那次失败的运行,因为有意思的字段只在失败时才出现:每个失败的检查会多出一个 code,而 required / affectsOverallHealth 说明结论是由哪些结果算出来的。这里 direct-dns 翻成了 required: false——一旦走代理,能不能直连解析就不再是结论的依据了。
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 arc network doctor --json
ERROR: {
"proxy": {
"httpProxy": "http://127.0.0.1:9/",
"httpsProxy": "http://127.0.0.1:9/"
},
"checks": [
{
"name": "proxy-discovery",
"ok": true,
"status": "pass",
"required": true,
"affectsOverallHealth": true,
"detail": "proxy environment variables present (credentials redacted)"
},
{
"name": "direct-dns",
"ok": true,
"status": "pass",
"required": false,
"affectsOverallHealth": false,
"detail": "main.abtnetwork.io → 198.18.0.38"
},
{
"name": "https-through-proxy",
"ok": false,
"status": "fail",
"required": true,
"affectsOverallHealth": true,
"code": "PROXY_CONNECT_FAILED",
"detail": "connect ECONNREFUSED 127.0.0.1:9"
},
{
"name": "wss-through-proxy",
"ok": false,
"status": "fail",
"required": true,
"affectsOverallHealth": true,
"code": "PROXY_CONNECT_FAILED",
"detail": "connect ECONNREFUSED 127.0.0.1:9"
}
],
"ok": false
}开头那个 ERROR: 不是复制粘贴错了:失败的运行里,人类可读示例里同一个单一出口的前缀,也会原样出现在 JSON 前面,跟开头的 { 挤在同一行——因为失败命令的整条消息,不管是人类文本还是 JSON,这个 CLI 在那个环节并不区分,都会被统一加上这一个标记。非零退出时先把这一个 token 剥掉再解析,或者干脆按退出码分支,不要去把 stderr 当 JSON 硬解析。干净的一次运行是同样的形状,只是没有 code 字段,每个 status 都是 pass,proxy 是空对象,顶层 ok 为 true——而且没有 ERROR: 前缀,因为它只在失败时才出现。在你把这个接进管道之前还有一件事要知道:失败的运行里stdout 是 0 字节——整条消息,前缀也算在内,都去了 stderr——所以 arc network doctor --json | jq 在失败时什么都看不到,不是看到一个错误对象;先查退出码,不然你会去调试自己的 jq 表达式,而不是真正的失败。
两个 target 是各管各的。--target 会同时改掉 HTTPS 探测和跟着它走的 DNS 检查——探测 https://arc.arcblock.io/ 时 direct-dns 那行放的就是 arc.arcblock.io → …,不再是默认主机;而 --ws-target 只改 WebSocket 探测,DNS 仍然跟着 --target 走。所以把 --ws-target 指向一个关闭的端口,正好只有一项检查会失败:
$ arc network doctor --ws-target wss://127.0.0.1:9/
ERROR: Outbound network doctor
Proxy:
(none)
Checks:
✓ proxy-discovery no proxy environment variables
✓ direct-dns main.abtnetwork.io → 198.18.0.38
✓ https-through-proxy HTTP 200 direct
✗ wss-through-proxy UPSTREAM_REJECTED
FAILED这里特意用了字面地址加一个关闭的端口,让这个探测在任何机器上都以同样的方式失败——没有域名要解析,也就没有解析器可以有异议。真正点出是在哪一层失败的是 code,也就是 UPSTREAM_REJECTED,跟 PROXY_CONNECT_FAILED / DIRECT_DNS_FAILED / TLS_FAILED 分得开;它后面那段自由文本的详情并不保证一定有内容,这次抓到的就是空的——一次立即被拒绝的本机连接,没能让底层的 socket 层留下什么话可说。不要去匹配这段详情文本;要匹配 code,需要结构化就用 --json。当默认目标可达、但你真正关心的主机不可达时,就用这两个选项。