单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。 Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
// domainrules.go:域名规则 trie(后缀匹配 direct/pool:xxx/deny)。
|
||
// 规则从 SQLite rules 表加载,Reload() 热载;gateway policy 与
|
||
// proxymanager 共享同一表(design-arch §5.3)。
|
||
package policy
|
||
|
||
import (
|
||
"strings"
|
||
"sync"
|
||
|
||
"onesvm.com/onesvm/browser-server/internal/store"
|
||
)
|
||
|
||
// Action 域名路由动作。
|
||
const (
|
||
ActionDirect = "direct"
|
||
ActionDeny = "deny"
|
||
// ActionPool 前缀 pool:<name>(如 pool:us-vless)。
|
||
ActionPoolPrefix = "pool:"
|
||
)
|
||
|
||
// node trie 节点:按域名标签倒序匹配(com → example → api)。
|
||
type node struct {
|
||
children map[string]*node
|
||
action string // 命中节点上的动作(最长后缀优先)
|
||
}
|
||
|
||
func newNode() *node { return &node{children: make(map[string]*node)} }
|
||
|
||
// DomainTrie 并发安全域名规则 trie。
|
||
type DomainTrie struct {
|
||
mu sync.RWMutex
|
||
root *node
|
||
ver int64 // 加载代次(观测热载生效)
|
||
}
|
||
|
||
// NewDomainTrie 空 trie。
|
||
func NewDomainTrie() *DomainTrie {
|
||
return &DomainTrie{root: newNode()}
|
||
}
|
||
|
||
// LoadFromStore 从 rules 表全量加载(Reload 热载路径)。
|
||
// 非 suffix 类型跳过(glob 由调用方另行处理,首版以 suffix 为主)。
|
||
func (t *DomainTrie) LoadFromStore(db *store.DB) (int, error) {
|
||
rules, err := db.RulesAll()
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
root := newNode()
|
||
n := 0
|
||
for _, r := range rules {
|
||
if r.MatchType != "suffix" {
|
||
continue
|
||
}
|
||
t.insert(root, r.Value, r.Action)
|
||
n++
|
||
}
|
||
t.mu.Lock()
|
||
t.root = root
|
||
t.ver++
|
||
t.mu.Unlock()
|
||
return n, nil
|
||
}
|
||
|
||
// Insert 单条内存插入(value 如 .blocked.com,action 如 deny)。
|
||
// 供无 SQLite 场景(proxymanager 单测 / CLI 即时规则)使用;
|
||
// 与 LoadFromStore 同一 insert 路径,不破坏热载代次语义。
|
||
func (t *DomainTrie) Insert(value, action string) {
|
||
t.mu.Lock()
|
||
t.insert(t.root, value, action)
|
||
t.ver++
|
||
t.mu.Unlock()
|
||
}
|
||
|
||
// insert 插入一条后缀规则(value 形如 .onesvm.com 或 onesvm.com)。
|
||
func (t *DomainTrie) insert(root *node, value, action string) {
|
||
labels := splitLabels(value)
|
||
cur := root
|
||
for i := len(labels) - 1; i >= 0; i-- {
|
||
lk := labels[i]
|
||
next, ok := cur.children[lk]
|
||
if !ok {
|
||
next = newNode()
|
||
cur.children[lk] = next
|
||
}
|
||
cur = next
|
||
}
|
||
cur.action = action
|
||
}
|
||
|
||
// splitLabels 域名转小写标签切片(去前导点)。
|
||
func splitLabels(domain string) []string {
|
||
d := strings.ToLower(strings.TrimPrefix(strings.TrimSpace(domain), "."))
|
||
if d == "" {
|
||
return nil
|
||
}
|
||
return strings.Split(d, ".")
|
||
}
|
||
|
||
// Lookup 最长后缀匹配。返回 (action, 是否命中)。
|
||
// example.com 匹配规则 .example.com 与 .com;未命中返回 ("", false)。
|
||
func (t *DomainTrie) Lookup(host string) (string, bool) {
|
||
labels := splitLabels(host)
|
||
if labels == nil {
|
||
return "", false
|
||
}
|
||
t.mu.RLock()
|
||
defer t.mu.RUnlock()
|
||
cur := t.root
|
||
action := ""
|
||
found := false
|
||
for i := len(labels) - 1; i >= 0; i-- {
|
||
next, ok := cur.children[labels[i]]
|
||
if !ok {
|
||
break
|
||
}
|
||
if next.action != "" {
|
||
action = next.action
|
||
found = true
|
||
}
|
||
cur = next
|
||
}
|
||
return action, found
|
||
}
|
||
|
||
// IsDenied 命中 deny 的便捷判定。
|
||
func (t *DomainTrie) IsDenied(host string) bool {
|
||
a, ok := t.Lookup(host)
|
||
return ok && a == ActionDeny
|
||
}
|
||
|
||
// Generation 当前加载代次(热载观测)。
|
||
func (t *DomainTrie) Generation() int64 {
|
||
t.mu.RLock()
|
||
defer t.mu.RUnlock()
|
||
return t.ver
|
||
}
|