43b25c8aa0
国内 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>
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// RulesHandler 静态服务国内分流(#5)用的 sing-box rule-set(.srs)文件。
|
|
// 客户端配置的 rule_set 指向 /v1/rules/{name},sing-box 直连下载
|
|
// (download_detour:direct);自托管避免 GitHub 在国内被墙的鸡生蛋问题。
|
|
// 只放行白名单文件名,防目录穿越。无需鉴权(sing-box 下载不带 token)。
|
|
type RulesHandler struct {
|
|
dir string
|
|
}
|
|
|
|
// NewRulesHandler 指定 .srs 所在目录(PANGOLIN_RULES_DIR,默认 /var/lib/pangolin/rules)。
|
|
func NewRulesHandler(dir string) *RulesHandler {
|
|
if dir == "" {
|
|
dir = "/var/lib/pangolin/rules"
|
|
}
|
|
return &RulesHandler{dir: dir}
|
|
}
|
|
|
|
var allowedRuleSets = map[string]bool{
|
|
"geoip-cn.srs": true,
|
|
"geosite-cn.srs": true,
|
|
}
|
|
|
|
// Serve 处理 GET /v1/rules/{name}。
|
|
func (h *RulesHandler) Serve(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if !allowedRuleSets[name] {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
f, err := os.Open(filepath.Join(h.dir, name))
|
|
if err != nil {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
st, err := f.Stat()
|
|
if err != nil || st.IsDir() {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
http.ServeContent(w, r, name, st.ModTime(), f)
|
|
}
|