feat: 国内流量直连分流(geoip-cn / geosite-cn)— #5
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled

国内 IP/域名直连(不走隧道)→ 省流量 + 国内访问快;非国内走代理。

- clientconfig.go: BuildClientConfig 加 ClientConfigOpts{SplitCN,RulesBaseURL};
  开启时 route 加 {rule_set:[geoip-cn,geosite-cn]→direct} + 定义 rule_set
  (remote .srs,download_detour:direct 直连下载)
- rules.go: 控制面静态服务 /v1/rules/{name}.srs(白名单防穿越)——自托管避免
  GitHub 在国内被墙的鸡生蛋;客户端反正连控制面,可达性有保证
- nodes.go ConnectNode: 读 ?split_cn → opts;NodeAPI 加 rulesBaseURL
  (PANGOLIN_PUBLIC_URL);main.go 挂 /v1/rules 路由 + RulesHandler
- 客户端: connect_api splitCN→?split_cn=1;connection_provider 传 smartRoute 偏好
- deploy/single-node: 拉 geoip-cn/geosite-cn.srs 到 $DATA_DIR/rules +
  设 PANGOLIN_PUBLIC_URL/PANGOLIN_RULES_DIR

验证:go test(splitCN 开/关渲染 + RulesHandler 白名单/404)+ flutter analyze +
shellcheck;不需要节点。订阅链接暂用默认 opts、DNS 分流为后续增强。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-19 20:34:00 +08:00
parent cf2bd93c93
commit 43b25c8aa0
9 changed files with 230 additions and 16 deletions
+35 -10
View File
@@ -8,6 +8,15 @@ import (
"github.com/wangjia/pangolin/server/internal/nodes"
)
// ClientConfigOpts carries per-request rendering options for BuildClientConfig.
type ClientConfigOpts struct {
// SplitCN 开启国内分流:命中 geoip-cn/geosite-cn 的 IP/域名直连(不走隧道)。
SplitCN bool
// RulesBaseURL 是控制面对外公网基址(如 "http://node:8080"),rule_set 的 .srs
// 从 <base>/v1/rules/*.srs 下载。SplitCN 生效需此项非空(否则分流静默跳过)。
RulesBaseURL string
}
// BuildClientConfig renders a complete sing-box CLIENT configuration JSON that
// the client app passes verbatim to the local tunnel kernel.
//
@@ -19,9 +28,8 @@ import (
// - dpUUID: the authenticated user's data-plane UUID (used as VLESS uuid)
// - deriveKey: shared HMAC key used by both server and agent to derive the
// Hysteria2 password from dp_uuid (must equal PANGOLIN_AGENT_DERIVE_KEY)
// - ttlSeconds: credential lifetime hint; not embedded in the config but can
// be used by callers to set a session timer
func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string) ([]byte, error) {
// - opts: 渲染选项(国内分流等),见 ClientConfigOpts
func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string, opts ClientConfigOpts) ([]byte, error) {
// Parse host:port from endpoint; endpoint format is "host:port".
host, _ := splitHostPort(node.Endpoint)
if host == "" {
@@ -118,19 +126,36 @@ func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string) ([]byte, e
"tolerance": 50,
}
// Route: LAN direct, everything else via auto.
route := map[string]any{
"rules": []any{
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",
},
// Route: LAN direct;(可选)国内 IP/域名直连;其余 via auto
routeRules := []any{
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",
},
}
route := map[string]any{
"final": "auto",
"auto_detect_interface": true,
// sing-box 1.12+ 要求显式声明出站域名用哪个 DNS 解析,缺失即 FATAL。
"default_domain_resolver": map[string]any{"server": "local"},
}
// 国内分流(#5):命中 geoip-cn / geosite-cn → 直连(不走隧道),省流量 + 国内快。
// rule_set 走控制面自托管(国内可达,客户端反正连控制面);download_detour:direct
// 让 sing-box 直连下载 .srs(不经隧道,启动期隧道还没起)。
if opts.SplitCN && opts.RulesBaseURL != "" {
base := strings.TrimRight(opts.RulesBaseURL, "/")
routeRules = append(routeRules, map[string]any{
"rule_set": []string{"geoip-cn", "geosite-cn"},
"outbound": "direct",
})
route["rule_set"] = []any{
map[string]any{"tag": "geoip-cn", "type": "remote", "format": "binary",
"url": base + "/v1/rules/geoip-cn.srs", "download_detour": "direct"},
map[string]any{"tag": "geosite-cn", "type": "remote", "format": "binary",
"url": base + "/v1/rules/geosite-cn.srs", "download_detour": "direct"},
}
}
route["rules"] = routeRules
// DNS: remote over tunnel, local for domestic.
// sing-box 1.12+ DNS server format(type+server);旧的 address 串格式在