fix(server): 补 #5 国内分流的 DNS 面 + 链路诊断端点(#12)

#5 只分了数据面(geoip/geosite-cn 路由 direct),DNS 仍 final:remote 全量经隧道
解析 → 国内域名解析到非 CN IP、漏过 geoip-cn 又走隧道(白盒实测国内 TLS
1000-1660ms;修后 ~50ms)。

- clientconfig.go: 开分流时 dns.rules 加 {rule_set:[geosite-cn]→local},国内域名
  用 local(223.5.5.5)直连解析 → 拿到真 CN IP → geoip-cn 命中直连
- main.go: PANGOLIN_PUBLIC_URL 缺失时启动告警(空则分流静默跳过,是隐蔽坑)
- nodes.go: connect 渲染加可观测日志(split_cn/rules_base/split_active/bytes)
- diag.go: 新增只读端点 GET /v1/diag/egress?host=X,节点侧量出海段耗时(白名单
  防 SSRF、只回耗时数字),供白盒拆「接入段 vs 出海段」

验证:go test(splitCN 开渲染 dns.rules→local、关无 dns.rules)+ go vet;cara
实测国内 TLS 1000ms+→~50ms、接入段占 TLS 握手 ~98%。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-22 14:12:31 +08:00
parent 43b25c8aa0
commit bc890974c0
5 changed files with 146 additions and 4 deletions
+17 -2
View File
@@ -126,8 +126,13 @@ func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string, opts Clien
"tolerance": 50,
}
// Route: LAN direct;(可选)国内 IP/域名直连;其余 via auto。
// Route: DNS 劫持 → LAN direct(可选)国内直连 → 其余 via auto。
routeRules := []any{
// DNS 劫持(sing-box 1.13 action=hijack-dns,按目的端口 53 匹配,不依赖 sniff):
// 把发往隧道 DNS(172.19.0.2:53)的查询交给 sing-box DNS 模块解析。必须排在 LAN
// 直连规则之前——否则 172.19.x 落在下面的 172.16.0.0/12 里,DNS 会被吞去直连、解析
// 失败导致打不开网站(TUN 模式 DNS 劫持是必需项,缺失则隧道连上也无法上网)。
map[string]any{"action": "hijack-dns", "port": []int{53}},
map[string]any{
"ip_cidr": []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"},
"outbound": "direct",
@@ -142,7 +147,8 @@ func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string, opts Clien
// 国内分流(#5):命中 geoip-cn / geosite-cn → 直连(不走隧道),省流量 + 国内快。
// rule_set 走控制面自托管(国内可达,客户端反正连控制面);download_detour:direct
// 让 sing-box 直连下载 .srs(不经隧道,启动期隧道还没起)。
if opts.SplitCN && opts.RulesBaseURL != "" {
splitActive := opts.SplitCN && opts.RulesBaseURL != ""
if splitActive {
base := strings.TrimRight(opts.RulesBaseURL, "/")
routeRules = append(routeRules, map[string]any{
"rule_set": []string{"geoip-cn", "geosite-cn"},
@@ -170,6 +176,15 @@ func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string, opts Clien
"final": "remote",
"strategy": "ipv4_only",
}
// 国内分流的 DNS 面(补 #5 数据面之外的 DNS 面):开分流时,命中 geosite-cn 的
// 国内域名用 local(223.5.5.5)直连解析,不走 remote(8.8.8.8 经隧道)。否则即便数据
// 直连,域名解析仍绕道出海(实测国内 DNS 段 200-600ms),首连凭空多一个出海 RTT。
// 复用 route.rule_set 里已定义的 geosite-cn 标签。
if splitActive {
dns["rules"] = []any{
map[string]any{"rule_set": []string{"geosite-cn"}, "server": "local"},
}
}
cfg := map[string]any{
// timestamp=false:客户端日志出口(logLine)已统一加时间戳,