onesvm-browser-server/server/internal/policy/domainrules.go
chii eb972dfa93 feat: 落地 browser-server 控制面并打通 mgr1 海外订阅
单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:05:12 +08:00

136 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}