路由详情加附加规则:每路入站自己的分流表
用内核的 sub-rules 机制:listener 写 rule: 指向顶层 sub-rules 里的表 (listener/inbound/base.go:96,tunnel.go:713-721)。两个硬约束决定了 形态:proxy 优先级更高且完全短路规则匹配(tunnel.go:318-325),有附加 规则时 listener 只能写 rule;子规则表没命中时兜底是 DIRECT 而不是这一 路的出站(tunnel.go match 循环末尾),表底那条 MATCH,专属组 是承重的, 由 Patchbay 自动维护、界面上单独画不参与编辑。不变式:附加规则空 ⇔ proxy 直连专属组,非空 ⇔ rule 形态。 三处既有逻辑教会认 rule 形态:applySetup 不再无条件写 proxy(会把附加 规则静默短路掉);referencesTo 扫 sub-rules 里的规则——表底 MATCH 是 rule 形态下专属组活着的唯一理由,不数它就会被当孤儿清掉;pruneGenerated 清理没有 listener 引用的生成表。panelGroups 和 syncGenerated 的「是否 由 Patchbay 接线」收敛成一个 managedWiring。 listener 的 rule 指向不存在的表时内核静默退回全局规则(getRules 的 else 分支),-t 和重载都不拦,自检里补上这条(vendor 本地改动,记入 ORIGIN.md)。界面:详情列新增附加规则区块,逐条增删改和排序复用 RuleForm,带内网直连预设;生成形状过了 mihomo -t 实测。
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import {
|
||||
applySetup,
|
||||
applySubRules,
|
||||
chainGroupName,
|
||||
chainProviderName,
|
||||
managedWiring,
|
||||
panelGroups,
|
||||
readSubRules,
|
||||
sharedPeers,
|
||||
subRuleName,
|
||||
syncGenerated,
|
||||
isGenerated,
|
||||
linesMatching,
|
||||
@@ -356,3 +360,70 @@ describe('清理', () => {
|
||||
expect((pruneGenerated(orphaned)['proxy-groups'] as ConfigObject[]).length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('附加规则', () => {
|
||||
const managed = () => applySetup(base(), 'test1', plain)
|
||||
const listenerOf = (data: ConfigObject) => (data.listeners as ConfigObject[])[0]!
|
||||
|
||||
it('写入后 listener 换成 rule 形态,表底自动补兜底 MATCH', () => {
|
||||
const next = applySubRules(managed(), 'test1', ['DOMAIN-SUFFIX,lan,DIRECT'])
|
||||
|
||||
const listener = listenerOf(next)
|
||||
expect(listener.rule).toBe(subRuleName('test1'))
|
||||
expect(listener.proxy).toBeUndefined()
|
||||
expect((next['sub-rules'] as ConfigObject)[subRuleName('test1')]).toEqual([
|
||||
'DOMAIN-SUFFIX,lan,DIRECT',
|
||||
'MATCH,patchbay:test1',
|
||||
])
|
||||
})
|
||||
|
||||
it('读出来不含兜底 MATCH,往返一致', () => {
|
||||
const rules = ['DOMAIN-SUFFIX,lan,DIRECT', 'GEOIP,CN,DIRECT']
|
||||
const next = applySubRules(managed(), 'test1', rules)
|
||||
expect(readSubRules(next, 'test1')).toEqual(rules)
|
||||
expect(readSubRules(managed(), 'test1')).toEqual([])
|
||||
})
|
||||
|
||||
it('清空后恢复 proxy 直连形态,sub-rules 键整个消失', () => {
|
||||
const withRules = applySubRules(managed(), 'test1', ['GEOIP,CN,DIRECT'])
|
||||
const cleared = applySubRules(withRules, 'test1', [])
|
||||
|
||||
const listener = listenerOf(cleared)
|
||||
expect(listener.proxy).toBe(proxyGroupName('test1'))
|
||||
expect(listener.rule).toBeUndefined()
|
||||
expect(cleared['sub-rules']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rule 形态仍被认成 Patchbay 接线,重建设置不会把 proxy 加回来', () => {
|
||||
// proxy 会短路规则匹配(tunnel.go:318-325),加回来附加规则就静默失效了
|
||||
const withRules = applySubRules(managed(), 'test1', ['GEOIP,CN,DIRECT'])
|
||||
const inbound = collectInbounds(withRules).find((i) => i.name === 'test1')!
|
||||
expect(managedWiring(withRules, inbound)).toBe(true)
|
||||
|
||||
const rebuilt = syncGenerated(withRules, collectInbounds(withRules))
|
||||
const listener = listenerOf(rebuilt)
|
||||
expect(listener.rule).toBe(subRuleName('test1'))
|
||||
expect(listener.proxy).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rule 形态下专属组靠表底 MATCH 活着,不会被当孤儿清掉', () => {
|
||||
const withRules = applySubRules(managed(), 'test1', ['GEOIP,CN,DIRECT'])
|
||||
const pruned = pruneGenerated(withRules)
|
||||
const groupNames = (pruned['proxy-groups'] as ConfigObject[]).map((g) => g.name)
|
||||
expect(groupNames).toContain(proxyGroupName('test1'))
|
||||
})
|
||||
|
||||
it('删掉入站后,附加规则表和组连锁消失', () => {
|
||||
const withRules = applySubRules(managed(), 'test1', ['GEOIP,CN,DIRECT'])
|
||||
const removed: ConfigObject = { ...withRules, listeners: [] }
|
||||
|
||||
const cleaned = pruneGenerated(removed)
|
||||
expect(cleaned['sub-rules']).toBeUndefined()
|
||||
expect(cleaned['proxy-groups']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('用户自己的 sub-rules 表不被清理', () => {
|
||||
const data: ConfigObject = { ...base(), 'sub-rules': { mine: ['MATCH,DIRECT'] } }
|
||||
expect((pruneGenerated(data)['sub-rules'] as ConfigObject).mine).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user