refactor(agent): route 块改由 buildRoute 统一产出,合并 ACL 与 WARP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,11 @@ import (
|
||||
//
|
||||
// Only the opaque dp_uuid is ever written; no account identity touches the node.
|
||||
//
|
||||
// 例外(私有目的地 ACL):若节点配置了 acl.json,渲染出的 route 规则会含一份享有私有
|
||||
// 访问权的 dp_uuid 白名单与对应的私有域名/端口。它仍不含任何账户身份(email/user_id),
|
||||
// 但确实让节点知道「这几个 dp_uuid 属于同一组权限」—— 知情接受的不变式弱化,
|
||||
// 设计与权衡见 docs/private-dest-acl-design.html §12。
|
||||
//
|
||||
// 用量统计走 v2ray_api StatsService(loopback gRPC):节点 sing-box 编入
|
||||
// with_v2ray_api,stats.users 列出全部 dp_uuid → 每个用户独立的
|
||||
// user>>>{dp_uuid}>>>traffic>>>uplink|downlink 计数器,agent 按用户精确读取
|
||||
@@ -27,7 +32,7 @@ const (
|
||||
warpOutboundTag = "warp"
|
||||
)
|
||||
|
||||
func renderSingboxConfig(creds []Cred, reality *agentv1.RealityInbound, hy2 *agentv1.Hy2Inbound, deriveKey string, warp *WarpConfig) ([]byte, error) {
|
||||
func renderSingboxConfig(creds []Cred, reality *agentv1.RealityInbound, hy2 *agentv1.Hy2Inbound, deriveKey string, warp *WarpConfig, acl *ACLConfig) ([]byte, error) {
|
||||
cfg := map[string]any{
|
||||
"log": map[string]any{"level": "warn", "timestamp": true},
|
||||
"inbounds": buildInbounds(creds, reality, hy2, deriveKey),
|
||||
@@ -47,16 +52,47 @@ func renderSingboxConfig(creds []Cred, reality *agentv1.RealityInbound, hy2 *age
|
||||
},
|
||||
}
|
||||
|
||||
// WARP 分流(#29):命中配置域名的流量走 Cloudflare WARP 干净出口,其余直连。
|
||||
// warp 为 nil 或未 active 时完全不加 endpoints/route → 与旧配置逐字节一致(向后兼容)。
|
||||
// WARP 分流(#29)只贡献 endpoints;route 块由 buildRoute 统一产出,因为它现在要
|
||||
// 同时容纳 ACL 规则 —— 原先 cfg["route"] = warp.warpRoute() 是整块覆盖,直接赋值
|
||||
// 会把对方的规则干掉。
|
||||
if warp.active() {
|
||||
cfg["endpoints"] = []any{warp.warpEndpoint()}
|
||||
cfg["route"] = warp.warpRoute()
|
||||
}
|
||||
if route := buildRoute(acl, warp); route != nil {
|
||||
cfg["route"] = route
|
||||
}
|
||||
|
||||
return json.MarshalIndent(cfg, "", " ")
|
||||
}
|
||||
|
||||
// buildRoute 合并私有目的地 ACL 与 WARP 分流,产出单一 route 块。
|
||||
// 两者都未激活时返回 nil —— 不产出 route 字段,与旧配置逐字节一致(向后兼容)。
|
||||
//
|
||||
// 规则顺序是安全语义的一部分:
|
||||
// 1. {"action":"sniff"} 唯一且最先。域名匹配依赖它取 TLS SNI(客户端多半发的是
|
||||
// 已解析 IP),WARP 与 ACL 都需要,故在此统一产出一次,不由各自重复追加。
|
||||
// 2. ACL 规则(放行在前、拒绝在后)整体排在 WARP 之前:被 ACL 拒绝的目的地永远
|
||||
// 不该还有机会被路由到 warp 出口。
|
||||
// 3. final 恒为 direct。
|
||||
func buildRoute(acl *ACLConfig, warp *WarpConfig) map[string]any {
|
||||
aclRules := acl.rules()
|
||||
warpActive := warp.active()
|
||||
if len(aclRules) == 0 && !warpActive {
|
||||
return nil
|
||||
}
|
||||
|
||||
rules := make([]any, 0, len(aclRules)+2)
|
||||
rules = append(rules, map[string]any{"action": "sniff"})
|
||||
rules = append(rules, aclRules...)
|
||||
if warpActive {
|
||||
rules = append(rules, map[string]any{
|
||||
"domain_suffix": warp.cleanDomains(),
|
||||
"outbound": warpOutboundTag,
|
||||
})
|
||||
}
|
||||
return map[string]any{"rules": rules, "final": directOutboundTag}
|
||||
}
|
||||
|
||||
// statsUsers 收集所有去重 dp_uuid,供 v2ray_api stats.users 按用户开启流量计数器。
|
||||
func statsUsers(creds []Cred) []string {
|
||||
seen := make(map[string]struct{}, len(creds))
|
||||
|
||||
Reference in New Issue
Block a user