Pangolin 可配置分流 · 配置说明文档

Task 3 设计 · 让用户像 Shadowrocket 那样自定义路由规则(哪些直连 / 走隧道 / 拒绝)。

配套视觉原型:交互原型 → · 状态:Phase 1 已实现(feat/configurable-proxy;实现计划 阅读版)

1. 目标与背景

当前 pangolin 客户端的分流是服务端固定渲染的:LAN 直连、DNS 劫持、可选国内分流(geoip/geosite-cn)、私有域名走隧道,用户无法自定义。实际需求(如 CI 编译源、公司内网、某些国内站要直连;某些站要强制走隧道;广告要拒绝)无处配置——这次暴露的「CI 流量被灌进隧道拖垮小节点」就是典型。

本设计给用户一套可配置路由规则,心智模型对齐 Shadowrocket 的 [Rule] 段:有序规则表,首命中生效,动作三选一(直连 / 走隧道 / 拒绝)。

2. 架构铁律:客户端不拼配置

ARCHITECTURE.md §3.1:Dart/Flutter 客户端不得自行拼装或修改 sing-box 配置——配置一律服务端渲染、原样下发。所以"客户端可配置"不能变成"客户端本地改 config"。

解法:用户在 App 编规则 → 存为服务端 per-user「路由档案(routing profile)」→ connect 时服务端把档案翻译进渲染的配置。客户端只负责编辑 UI + 存/取档案,永不碰 sing-box JSON。

App 规则编辑器  ──PUT /v1/routing-profile──▶  服务端存 per-user 档案(DB)
                                                      │
App 点连接  ──POST /v1/nodes/{id}/connect──▶  BuildClientConfig(读档案→翻译成 route.rules)
                                                      │
App  ◀────────── 完整 sing-box 配置(含用户规则)─────┘  原样喂内核

3. 规则模型

一条规则 = { type, value, action, note?, enabled }。整个档案:

{
  "mode": "rule",            // global | rule | direct(对齐 shadowrocket 三模式)
  "builtin": {
    "china_direct": true,    // 国内分流(geoip-cn/geosite-cn → 直连)开关
    "lan_direct": true,      // LAN/私网直连(强制,不可关)
    "private_via_tunnel": true // 私有服务域名走隧道(服务端下发,不可关)
  },
  "rules": [                 // 用户自定义,有序,首命中生效
    { "type": "domain_suffix", "value": "git.51yanmei.com", "action": "direct", "note": "CI 源", "enabled": true },
    { "type": "geosite",       "value": "category-ads",     "action": "reject" },
    { "type": "ip_cidr",       "value": "35.190.0.0/16",    "action": "proxy" }
  ],
  "final": "proxy"           // 兜底:未命中任何规则的动作
}

类型 type

type值示例sing-box 字段
domainexample.comdomain(精确)
domain_suffixaliyun.comdomain_suffix(含子域)
domain_keywordgoogledomain_keyword
ip_cidr35.190.0.0/16 · ::/0ip_cidr(v4/v6)
geoipCN · USrule_set(自托管 geoip-*.srs)
geositecn · netflix · category-adsrule_set(自托管 geosite-*.srs)

动作 action → sing-box outbound

actionoutbound说明
directdirect物理网卡直连(配合 route_exclude/reverse_mapping 真直连)
proxyauto经节点(REALITY/Hy2 urltest 择优)
rejectblock阻断

4. 优先级(渲染顺序)

服务端把规则按固定层级拼进 route.rules,自上而下首命中:

1DNS 劫持(port 53 → hijack-dns)· 系统强制
2LAN / 私网直连(10/8·192.168/16·127/8)· 系统强制
3私有服务域名 → 走隧道(PANGOLIN_PRIVATE_SPLIT_DOMAINS服务端
4用户规则(档案 rules[] 顺序展开)← 新增
5国内分流 geoip-cn/geosite-cn → 直连(china_direct 开时)
6FINAL 兜底(final:proxy / direct)
用户规则排在国内分流之前——这样"我要 github 走隧道""我要某国内站走隧道"能压过 geoip-cn 的直连默认。系统层(1-3)永远在用户规则之上,防止用户误配把 DNS/LAN/私有服务弄坏。

默认规则 vs 自定义规则 · 冲突处理

确定性,不靠猜——冲突由层级 + 顺序唯一裁决:

UI 主动提示两类冲突(编辑器里实时标记,不静默):

冲突提示
被前面规则遮蔽、永不命中规则灰掉 + 「已被上面「X」覆盖,永不命中」
撞到锁定的系统目标(如给私有服务域名加规则)「该域名由系统强制走隧道,此规则不生效」

三模式语义 · 用户规则仅「智能分流」生效

模式行为用户规则
智能分流(默认)完整规则链:系统层 → 用户规则 → 国内分流 → FINAL生效
全局代理除系统层(LAN/私有服务)外全走隧道忽略
全部直连除系统层(私有服务走隧道)外全直连忽略

系统层(层1-3)在任何模式都强制生效——全局/直连只改 FINAL 兜底 + 是否套用用户规则,不会关掉 DNS 劫持/LAN 直连/私有服务。UI 在非智能分流模式下把「我的规则」区灰化并注明"当前模式忽略以下规则"。

与现有 Settings「Smart routing」的衔接

现在 Settings 的 Smart routing 是二元开关(= builtin.china_direct)。改为渐进式披露入口:该行变成「分流规则」设置行(右侧显示当前模式 + chevron →),点进去即分流规则屏。休闲用户维持"智能分流"不点即可(等价现在开关开着),高级用户点进去自定义。老的二元开关被三模式(全局/智能分流/直连)涵盖。

5. 直连是否真"不走 VPN"

direct outbound 让 sing-box 从物理网卡直接出连接。但 TUN strict_route 会把包重新捕回隧道——已有两个机制解决,用户规则复用:

这是关键实现难点:域名类直连规则要真生效,必须开 reverse_mapping 且用 local DNS 解析该域名;IP 类直连规则要真生效,值必须同时进 route_exclude_address。这两点在实现计划里逐条落。

6. 存储与传输(API)

端点作用
GET /v1/routing-profile拉当前用户档案(App 编辑器初始化;无则返回内置默认)
PUT /v1/routing-profile保存档案(服务端校验:CIDR 合法、type 合法、条数上限、去重)
POST …/connect(现有)渲染时读该用户档案,翻译进 route.rules

档案存 DB(routing_profiles 表:user_id · profile_json · updated_at),不走 connect body(与现有 split_cn 走 query 的约束一致——大规则集不塞 query/body)。客户端本地也缓存一份(离线可看/编,连接时以服务端为准)。

7. 校验与兜底(fail-safe)

8. 与现有机制的关系

现有本设计如何吸收
SplitCN(geoip/geosite-cn 直连,#5)降为档案里的 builtin.china_direct 开关(层级 5)
PrivateSplitDomains(走隧道)保留为系统层 3(服务端 env,用户不可动)
route_exclude_address(LAN)保留 + 扩展:用户 ip_cidr→direct 规则动态并入
reverse_mapping / local DNS复用:域名直连规则靠它命中

换句话说,本设计是把三个散落的分流机制统一收进一个用户可见、可配的规则模型,底层复用已验证的 sing-box 手法。

9. 待你确认的取舍

Q1 规则粒度:先做"域名/IP/GeoIP/GeoSite + 三动作"的规则表(本设计)?还是要更细(URL 正则、UA、进程名)?建议先前者,后者 shadowrocket 也少人用。
Q2 分应用代理(per-app):按 App 分流(仅 Android/桌面可行,iOS 系统扩展做不到)。较重,建议后置为独立任务。
Q3「从文本导入」:粘贴 Shadowrocket/Clash 规则批量建(原型第三屏)。锦上添花,可 Phase 2
Q4 档案作用域:per-user(跨设备同步,推荐)还是 per-device(每台独立)?推荐 per-user + 单档案起步。

10. 分期建议

确认这份设计后,我用 writing-plans 出 Phase 1 的实现计划(TDD、多端、服务端翻译逐条测),再开发。