Files
op-packages/UA-Mask/docs/architecture.md
T

24 KiB
Raw Blame History

UA-Mask 架构说明

本文描述当前 UA-Mask 代码库的实际运行架构,重点覆盖:

  • OpenWrt 侧如何把流量导入 UA-Mask
  • Go daemon 的启动装配与生命周期
  • TCP 会话、HTTP 探测、UA 改写的热路径
  • 事件、目标画像、策略、流量卸载之间的关系
  • 统计、LuCI 与防火墙 set 的集成方式
  • SIGINT/SIGTERM 下的有序停止语义

当前架构只有一条生产流量路径:proxy 只负责接收与调度连接,业务热路径位于 session;目标级防火墙绕过统一通过 events -> state -> policy -> offload -> firewall.Executor 管线完成。旧的 proxy.Processor 与 firewall.DecisionEngine 已删除。

1. 总览

UA-Mask 是一个 OpenWrt REDIRECT 透明代理。OpenWrt 防火墙先把指定 TCP 流量重定向到本地监听端口,Go core 恢复原始目标地址并连接上游。

进入 core 后:

  • HTTP 请求会读取 User-Agent,根据规则决定是否改写。
  • 非 HTTP 流量会 fallback 成普通 TCP 转发,同时被记录为目标画像的一部分。
  • 命中 UA 防火墙白名单,或多次表现为纯非 HTTP 的 IP:Port,会被动态加入 ipset/nfset。
  • 被加入 set 的目标后续会在 OpenWrt 防火墙层直接 RETURN,不再进入 UA-Mask。
flowchart LR
    Client[客户端] --> FW[OpenWrt REDIRECT 规则]
    FW --> Ingress[core/internal/ingress]
    Ingress --> Server[core/internal/proxy.Server]
    Server --> Session[core/internal/session.Manager]
    Session --> Rewrite[core/internal/rewrite]
    Session --> Events[core/internal/events.Dispatcher]
    Events --> State[core/internal/state.Store]
    Events --> Stats[core/internal/stats.Stats]
    Events --> Offload[core/internal/offload.Coordinator]
    State --> Offload
    Offload --> Firewall[core/internal/firewall.Executor]
    Firewall --> Set[ipset / nfset]
    Session --> Upstream[目标服务器]
    Stats --> StatsFile["/tmp/UAmask.stats"]
    StatsFile --> LuCI[openwrt/luci]
    Set --> FW

2. 目录与职责

UA-Mask/
├── core/
│   ├── cmd/UAmask/             # 进程入口,只调用 app.Run(version)
│   └── internal/
│       ├── app/                # 运行时装配与生命周期
│       ├── config/             # JSON/legacy flags 加载、默认值、校验与运行时预处理
│       ├── ingress/            # REDIRECT 监听与原始目标地址恢复
│       ├── proxy/              # Accept 循环与 worker pool 调度
│       ├── session/            # 当前热路径中心:协议探测、转发、改写、事件发射
│       ├── rewrite/            # UA 判定、缓存、请求头改写
│       ├── events/             # 会话事件分发
│       ├── state/              # 按目标维护画像与冷却状态
│       ├── policy/             # 会话策略与目标卸载策略
│       ├── offload/            # 把策略信号转换为卸载动作
│       ├── firewall/           # 批量写 ipset/nfset
│       ├── stats/              # 事件计数与统计落盘
│       └── model/              # 跨模块共享的数据模型
├── openwrt/
│   ├── package/                # OpenWrt 包构建入口
│   ├── root/                   # init.d 与默认 UCI 配置
│   └── luci/                   # LuCI 页面
└── docs/

可以按三条线理解:

  • 启动装配线:cmd -> app -> config -> runtime components
  • 请求热路径线:ingress -> proxy.Server -> session.Manager -> rewrite -> upstream
  • 卸载控制线:session events -> state/profile -> policy -> offload -> firewall set

3. 启动与运行时装配

core/cmd/UAmask/main.go 是很薄的进程入口,只保存版本号并调用 app.Run(version)。

app.Run() 负责:

  1. 调用 config.NewFromFlags() 解析进程参数,并按需加载版本化 JSON。
  2. 按 defaults -> JSON -> 显式 legacy flags -> normalize -> validate -> compile 生成最终配置。
  3. 处理 -v、-check-config、-dump-effective-config 等进程动作。
  4. 应用 GC 参数并初始化 logrus。
  5. 调用 NewRuntime(cfg, version) 装配所有运行时组件。
  6. 监听 SIGINT/SIGTERM,并用对应 context 执行 runtime.RunContext()。
sequenceDiagram
    participant Main as cmd/UAmask
    participant App as app.Run
    participant Config as config loader
    participant Runtime as app.NewRuntime
    participant Server as proxy.Server

    Main->>App: Run(version)
    App->>Config: 解析进程参数 / 加载 JSON
    Config->>Config: defaults → normalize → validate → compile
    Config-->>App: Command + Config
    App->>App: setupLogging()
    App->>Runtime: NewRuntime(cfg, version)
    Runtime-->>App: Runtime
    App->>Runtime: Runtime.RunContext(ctx)
    Runtime->>Server: server.RunContext(ctx)

app.NewRuntime() 是当前最重要的代码图谱入口。它会创建:

  • stats.Stats 与 stats.FileReporter
  • rewrite.UACache
  • firewall.Executor
  • rewrite.Engine 与 rewrite.RequestRewriter
  • state.Store
  • offload.FirewallTargetOffloader
  • policy.SessionPolicy 与 policy.TargetPolicy
  • events.Dispatcher
  • offload.Coordinator
  • session.DefaultManager
  • ingress.Redirect
  • proxy.Server

装配关系如下:

flowchart TB
    Config[config.Config]

    Config --> Stats[stats.Stats]
    Config --> Reporter[stats.FileReporter]
    Config --> Cache[rewrite.UACache]
    Config --> RewriteEngine[rewrite.Engine]
    Config --> Rewriter[rewrite.RequestRewriter]
    Config --> ProfileStore[state.Store]
    Config --> SessionPolicy[policy.SessionPolicy]
    Config --> TargetPolicy[policy.TargetPolicy]
    Config --> Ingress[ingress.Redirect]
    Config --> Server[proxy.Server]
    Config --> FirewallExecutor[firewall.Executor]

    RewriteEngine --> Rewriter
    Cache --> Rewriter

    Dispatcher[events.Dispatcher] --> ProfileStore
    Dispatcher --> Stats
    Dispatcher --> Offload[offload.Coordinator]

    ProfileStore --> Offload
    TargetPolicy --> Offload
    FirewallExecutor --> TargetOffloader[offload.FirewallTargetOffloader]
    TargetOffloader --> Offload

    SessionPolicy --> Session[session.Manager]
    Rewriter --> Session
    ProfileStore --> Session
    Offload --> Session
    Dispatcher --> Session

    Ingress --> Server
    Session --> Server

Runtime.RunContext() 的启动顺序是:

  1. 打印配置。
  2. 启动 firewall.Executor worker。
  3. 启动 state.Store 清理 worker。
  4. 启动 stats.FileReporter 落盘 worker。
  5. 启动 events.Dispatcher 事件 worker。
  6. 最后进入 proxy.Server.RunContext() 的 Accept 循环。

context 取消或 server 返回错误时:

  1. proxy.Server 关闭 ingress,停止接收新连接,并丢弃 worker pool 中尚未开始处理的 session。
  2. context 传入 session.Manager;拨号使用 DialContext,取消时同时关闭 client 和 upstream,再等待已开始的 handler 返回。
  3. offload.Coordinator.Stop() 取消待决策 timer 并拒绝新动作。
  4. firewall.Executor.Flush() 完成此前入队的批次与 completion,然后停止 worker。
  5. events.Dispatcher.WaitIdle() 完成 result event 的状态和统计投影,然后停止分发器。
  6. 最后停止 stats reporter 和 profile store。

这个顺序保证“停止接收流量”先于“停止控制面”,并且防火墙执行结果不会因为关闭顺序而丢失。

4. 配置模型

config.Config 已经按职责拆成几个子配置:

classDiagram
    class Config {
        +Listen ListenConfig
        +Rewrite RewriteConfig
        +RewriteRuntime RewriteRuntimeConfig
        +Firewall FirewallConfig
        +Observe ObserveConfig
        +Performance PerformanceConfig
    }

    class ListenConfig {
        +Port int
        +PoolSize int
        +BufferSize int
        +DialTimeout duration
        +ClientKeepAlive duration
        +UpstreamKeepAlive duration
    }

    class RewriteConfig {
        +UserAgent string
        +Whitelist []string
        +ForceReplace bool
        +EnableRegex bool
        +EnablePartialReplace bool
        +Keywords []string
        +Pattern string
        +CacheSize int
    }

    class RewriteRuntimeConfig {
        +UARegexp *regexp.Regexp
    }

    class FirewallConfig {
        +UAWhitelist []string
        +EnableBypass bool
        +SetName string
        +Backend string
        +DropOnMatch bool
        +NonHTTPThreshold int
        +Timeout int
        +DecisionDelay duration
        +HTTPCooldownPeriod duration
        +ProfileCleanupInterval duration
        +ImmediateBypassTimeout int
    }

    class ObserveConfig {
        +LogLevel string
        +LogFile string
        +StatsFilePath string
        +StatsInterval duration
    }

    class PerformanceConfig {
        +Profile string
        +GCPercent int
    }

    Config --> ListenConfig
    Config --> RewriteConfig
    Config --> RewriteRuntimeConfig
    Config --> FirewallConfig
    Config --> ObserveConfig
    Config --> PerformanceConfig

RewriteConfig.Pattern 保存用户输入语义,RewriteRuntimeConfig.UARegexp 保存编译后的正则。这样业务代码只消费已经预处理好的运行时配置。

OpenWrt LuCI/UCI 仍然是用户配置的唯一真相源。openwrt/root/etc/init.d/UAmask 使用 jshn.sh 将 core 所需字段写入 /var/run/UAmask/config.json,调用 UAmask -check-config 校验后原子替换,再通过以下命令启动:

/usr/bin/UAmask -config /var/run/UAmask/config.json

JSON 使用 schema_version 做显式版本管理,未知字段会导致加载失败。duration 使用 30s、10m、8h 等字符串,列表使用 JSON 数组。完整示例见 docs/config.example.json。

独立运行时支持以下进程级参数:

  • -config <path>
  • -check-config
  • -dump-effective-config
  • -v

旧业务 flags 暂时作为兼容覆盖层保留;优先级为默认值、JSON、显式 legacy flag。

5. 接入层:ingress 与 proxy

ingress.Redirect 负责本地监听和原始目标地址恢复。

sequenceDiagram
    participant FW as OpenWrt REDIRECT
    participant Ingress as ingress.Redirect
    participant Server as proxy.Server
    participant Manager as session.Manager

    FW->>Ingress: TCP 连接进入监听端口
    Ingress->>Ingress: AcceptTCP()
    Ingress->>Ingress: getOriginalDst()
    Ingress-->>Server: AcceptedSession
    Server->>Manager: Handle(accepted)

proxy.Server 当前只负责连接调度:

  • 持有 ingress.Ingress
  • 持有 session.Manager
  • 根据 ListenConfig.PoolSize 选择 worker pool 或一连接一 goroutine
  • 对每个 AcceptedSession 调用 manager.Handle()

这意味着 proxy 不再理解 UA 规则、状态画像或防火墙策略。

6. 热路径中心:session.Manager

session.DefaultManager 是当前最重要的热路径模块。它负责:

  • 为每条连接创建 SessionState
  • 连接原始目标上游
  • 启动双向转发
  • 在客户端到上游方向做 HTTP 探测
  • 对 HTTP 请求调用 rewrite.RequestRewriter
  • 对非 HTTP 流量 fallback 到 io.Copy
  • 向 events.Dispatcher 发出会话事件
  • 调用 policy.SessionPolicy 得到连接动作
  • 把即时 offload signal 交给 offload.Coordinator

整体流程如下:

sequenceDiagram
    participant Client as 客户端
    participant Manager as session.Manager
    participant Upstream as 目标服务器
    participant Rewriter as rewrite.RequestRewriter
    participant Policy as policy.SessionPolicy
    participant Events as events.Dispatcher
    participant Offload as offload.Coordinator

    Client->>Manager: AcceptedSession
    Manager->>Events: EventSessionOpened
    Manager->>Upstream: Dial(target.Address)
    Manager->>Events: EventUpstreamConnected
    par client -> upstream
        Manager->>Manager: Peek(7) 判断 HTTP
        alt 非 HTTP
            Manager->>Events: EventProtocolClassified(non_http)
            Manager->>Policy: Evaluate(classification)
            Manager->>Events: EventNonHTTPObserved
            Manager->>Offload: Handle(decision.FlowSignal)
            Manager->>Events: EventSessionForwardFallback
            Manager->>Upstream: io.Copy fallback
        else HTTP
            Manager->>Manager: http.ReadRequest()
            Manager->>Events: EventProtocolClassified(http)
            Manager->>Rewriter: Rewrite(request)
            Rewriter-->>Manager: rewrite.Result
            Manager->>Policy: Evaluate(request)
            Manager->>Events: EventHTTPRequestObserved
            Manager->>Events: EventRewriteApplied 或 EventRewriteSkipped
            Manager->>Offload: Handle(decision.FlowSignal)
            Manager->>Upstream: request.Write()
        end
    and upstream -> client
        Upstream->>Manager: response bytes
        Manager->>Client: io.Copy
    end
    Manager->>Events: EventSessionClosed

协议探测非常轻量:只 Peek(7) 并判断是否以常见 HTTP 方法开头。判断为非 HTTP 后,不再尝试解析应用层协议,而是记录事件并直接转发剩余 TCP 字节。

7. Rewrite 层

rewrite 层拆成三个对象:

  • Engine:只根据 UA 和配置做纯决策。
  • UACache:缓存 originUA -> finalUA。
  • RequestRewriter:面向 http.Request 的封装,负责读写 Header 和串联缓存/决策。
flowchart LR
    Request[http.Request] --> Rewriter[RequestRewriter]
    Rewriter --> Cache[UACache]
    Rewriter --> Engine[Engine]
    Engine --> Decision[Decision]
    Decision --> Rewriter
    Rewriter --> Mutated[可能被改写的 Request]

UA 决策顺序:

flowchart TD
    Start([读取 originUA]) --> Empty{UA 为空?}
    Empty -- 是 --> NoUA[NoUserAgent: 不缓存、不改写]
    Empty -- 否 --> FWUA{命中防火墙 UA 白名单?}
    FWUA -- 是 --> FWDecision[FirewallBypassHit=true<br/>可选 DropConnection]
    FWUA -- 否 --> ExactWL{命中精确 UA 白名单?}
    ExactWL -- 是 --> Whitelist[不改写,可缓存]
    ExactWL -- 否 --> Force{ForceReplace?}
    Force -- 是 --> ForceReplace[直接替换,可缓存]
    Force -- 否 --> RegexMode{EnableRegex?}
    RegexMode -- 是 --> RegexHit{正则命中?}
    RegexHit -- 是 --> RegexReplace[完整或部分替换,可缓存]
    RegexHit -- 否 --> RegexMiss[不改写,可缓存]
    RegexMode -- 否 --> KeywordHit{命中关键词?}
    KeywordHit -- 是 --> KeywordReplace[替换,可缓存]
    KeywordHit -- 否 --> KeywordMiss[不改写,可缓存]

Decision 字段表达了后续模块需要知道的结果:

  • Matched
  • Replace
  • FinalUA
  • Reason
  • Cacheable
  • FirewallBypassHit
  • DropConnection

FirewallBypassHit 不直接写防火墙,而是通过会话事件和目标策略进入卸载管线。

8. 事件模型

当前代码用 model.SessionEvent 作为模块之间的主要事实记录。

核心事件包括:

  • EventSessionOpened
  • EventUpstreamConnected
  • EventProtocolClassified
  • EventHTTPRequestObserved
  • EventRewriteApplied
  • EventRewriteSkipped
  • EventNonHTTPObserved
  • EventSessionForwardFallback
  • EventSessionClosed
  • EventFlowOffloadSucceeded
  • EventFlowOffloadFailed
  • EventTargetOffloadSucceeded
  • EventTargetOffloadFailed

events.Dispatcher 是一个简单的异步 fan-out:

flowchart LR
    Session[session.Manager] --> Dispatcher[events.Dispatcher]
    Dispatcher --> State[state.Store]
    Dispatcher --> Stats[stats.Stats]
    Dispatcher --> Offload[offload.Coordinator]

运行中 Dispatcher.Append(event) 会把事件放入固定容量的环形队列,由后台 worker 依次分发给 sink。队列满时拒绝最新事件并累计 Dropped() 计数,防止慢 sink 导致内存无限增长;Stop() 只排空调用时已经入队的事件,停止边界之后产生的新事件会被拒绝,确保关闭过程有限;未启动时则同步分发,便于测试。

9. 状态画像与策略

state.Store 按目标 IP:Port 维护 TargetProfile:

  • NonHTTPScore
  • HTTPCooldownUntil
  • LastActivity
  • PendingTargetOffloadAt
  • TargetOffloadUntil
  • FirewallWhitelistHits
  • LastFirewallHit
  • LastEventType
  • LastReason

它消费事件并更新画像:

flowchart TD
    Event[SessionEvent] --> Kind{事件类型}
    Kind -- HTTPRequestObserved --> HTTP[清零 NonHTTPScore<br/>设置 HTTP cooldown<br/>取消 pending target offload]
    Kind -- NonHTTPObserved --> NonHTTP[如果不在 cooldown<br/>NonHTTPScore++]
    NonHTTP --> Threshold{达到阈值?}
    Threshold -- 是 --> Pending[设置 PendingTargetOffloadAt]
    Threshold -- 否 --> Wait[继续观察]
    Kind -- RewriteSkipped 且 FirewallHit --> FWHit[记录防火墙白名单命中]
    Kind -- TargetOffloadSucceeded --> Offloaded[记录 TargetOffloadUntil<br/>清理分数和 pending]

policy 有两类策略:

  • SessionPolicy:根据一次会话输入决定继续、透传或断开。
  • TargetPolicy:根据目标画像决定是否发出 SignalTryTargetOffload。

TargetPolicy 的两个主要触发条件是:

  • HTTP 请求命中防火墙 UA 白名单,并产生 EventRewriteSkipped + FirewallHit=true。
  • 非 HTTP 目标达到阈值,且经过 DecisionDelay 后仍不处于 HTTP cooldown。

两条路径的开关彼此独立:UA 防火墙白名单始终可产生目标卸载信号,-fw-bypass 只控制非 HTTP 画像与策略。

10. 流量卸载管线

offload.Coordinator 同时扮演两个角色:

  • 作为 events.Dispatcher 的 sink,观察目标事件。
  • 作为 session.Manager 的 signal handler,处理策略产生的卸载信号。

目标卸载流程如下:

sequenceDiagram
    participant Session as session.Manager
    participant Events as events.Dispatcher
    participant Store as state.Store
    participant Coord as offload.Coordinator
    participant Policy as policy.TargetPolicy
    participant Executor as firewall.Executor
    participant FW as ipset/nfset

    Session->>Events: EventNonHTTPObserved 或 EventRewriteSkipped
    Events->>Store: 更新 TargetProfile
    Events->>Coord: observeTargetEvent(event)
    Coord->>Store: Snapshot(target)

    alt 非 HTTP 达到阈值
        Coord->>Coord: scheduleTargetEvaluation(delay)
        Coord->>Store: 到期后重新 Snapshot(target)
        Coord->>Policy: Evaluate(snapshot)
    else UA 防火墙白名单命中
        Coord->>Policy: Evaluate(snapshot)
    end

    Policy-->>Coord: SignalTryTargetOffload
    Coord->>Executor: TryEnqueue(BypassTarget, completion)
    Executor->>FW: 批量写入 set
    FW-->>Executor: 实际执行结果
    Executor-->>Coord: completion(error)
    Coord->>Events: EventTargetOffloadSucceeded 或 Failed

offload.FirewallTargetOffloader 把 model.OffloadSignal 转成 firewall.BypassTarget 并投递给 firewall.Executor。Coordinator 只在 Executor 的批处理命令实际完成后发出成功或失败事实;单纯进入队列不会再被视为卸载成功。

NoopFlowOffloader 当前是空实现,说明代码模型里预留了“单连接级 flow offload”的概念,但当前实际起作用的是目标级 IP:Port offload。

11. Firewall 执行层

firewall.Executor 是异步批处理执行器,不负责策略判断。

它的职责是:

  • 校验 BypassTarget
  • 接收待写入 set 的目标
  • 按 backend:setName 分桶
  • 对同一 IP:Port 去重
  • 达到批次大小或最大等待时间后执行
  • 停止时 drain 队列并执行剩余 batch
flowchart LR
    Offload[offload.FirewallTargetOffloader] --> Executor[firewall.Executor]
    Executor --> Queue[queue]
    Queue --> Batch[按 backend:setName 分桶<br/>按 IP:Port 去重]
    Batch --> Applier[firewall.CommandApplier]
    Applier -->|nft add element| NFT[nfset]
    Applier -->|ipset restore| IPT[ipset]

firewall.CommandApplier 根据 backend 选择命令:

  • nft:执行 nft add element inet fw4 <set> { ip . port timeout Ns }
  • ipt:执行 ipset restore

基础 REDIRECT 规则和 set 创建不在 Go 里完成,而是在 OpenWrt init 脚本里完成。Go 进程只动态追加需要绕过的目标。

12. Stats 与 LuCI

stats.Stats 是事件 sink,使用原子计数器记录:

  • 当前连接数
  • HTTP 请求总数
  • UA 修改次数
  • 缓存命中并修改次数
  • 缓存命中但不修改次数

stats.FileReporter 周期性读取快照并写入 /tmp/UAmask.stats。

flowchart LR
    Events[events.Dispatcher] --> Stats[stats.Stats]
    Stats --> Reporter[stats.FileReporter]
    Reporter --> File["/tmp/UAmask.stats"]
    File --> LuCI[openwrt/luci/model/cbi/UAmask.lua]

当前落盘指标包括:

  • current_connections
  • total_requests
  • rps
  • successful_modifications
  • direct_passthrough
  • rule_processing
  • cache_hit_modify
  • cache_hit_pass
  • total_cache_ratio

LuCI 页面读取这个文件并展示运行状态、请求数、RPS、修改数和缓存效率。

13. OpenWrt 集成

OpenWrt 侧不是单纯打包壳,而是运行架构的一部分。

主要文件:

  • openwrt/package/Makefile:构建并安装 Go daemon、init 脚本、默认配置、LuCI 文件。
  • openwrt/root/etc/config/UAmask:默认 UCI 配置。
  • openwrt/root/etc/init.d/UAmask:procd 生命周期、flags 转换、防火墙规则装配与清理。
  • openwrt/luci/model/cbi/UAmask.lua:配置页面与统计展示。
  • openwrt/luci/controller/UAmask.lua:LuCI 菜单入口。

服务启动关系:

sequenceDiagram
    participant LuCI as LuCI / UCI
    participant Init as /etc/init.d/UAmask
    participant Core as /usr/bin/UAmask
    participant FW as nftables / iptables

    LuCI->>Init: start / reload / stop
    Init->>Init: detect_firewall()
    Init->>Init: 从 UCI 读取配置
    Init->>Init: 从 UCI 生成并校验 config.json
    Init->>Core: procd 启动并传 -config
    Init->>FW: 创建 REDIRECT 规则和 bypass set
    Core->>FW: 运行中动态追加绕过目标
    Core->>LuCI: 写 /tmp/UAmask.stats

init.d 会根据是否存在 fw4 选择 nftables 或 iptables:

  • nftables 路径:生成 /tmp/UAmask_rules.nft,注册为 firewall include,并创建 inet fw4 UAmask_bypass_set。
  • iptables 路径:创建 UAmask_prerouting、UAmask_output 链,并可创建 ipset hash:ip,port。

当启用 proxy_host 时,init 脚本还会用 GID 规则避免 UA-Mask 自身流量和 OpenClash 流量再次被重定向。

14. 唯一生产路径与阅读顺序

旧的 proxy.Processor、firewall.DecisionEngine 和直接写防火墙的 sink 已删除。业务行为不得再绕过 session -> event/state/policy/offload 边界增加第二条路径。

proxy.Server 只负责 Accept、worker pool、活跃 session 跟踪和 context 关闭;HTTP 检测、rewrite、drop、fallback 和事件发射全部由 session.Manager 负责。firewall 只保留系统执行边界,目标状态与决策不放回该包。

建立代码图谱时按以下顺序阅读:

阅读代码时,应优先从这些入口建立图谱:

  1. core/cmd/UAmask/main.go
  2. core/internal/app/app.go
  3. core/internal/session/manager.go
  4. core/internal/rewrite/engine.go
  5. core/internal/events/dispatcher.go
  6. core/internal/state/store.go
  7. core/internal/policy/evaluator.go
  8. core/internal/offload/coordinator.go
  9. core/internal/firewall/executor.go

15. 扩展点

当前代码最自然的扩展点在这些位置:

  • 新增 UA 匹配模式:扩展 rewrite.Engine。
  • 新增请求级行为:扩展 policy.SessionPolicy 与 model.PolicyDecision。
  • 调整非 HTTP 卸载策略:扩展 state.Store 的画像字段或 policy.TargetPolicy。
  • 支持新的防火墙后端:实现新的 firewall.Applier 或调整 CommandApplier。
  • 增加观测方式:给 events.Dispatcher 添加新的 sink,或替换/扩展 stats.FileReporter。
  • 实现真正的 flow-level offload:替换当前 offload.NoopFlowOffloader。

16. 一句话总结

当前 UA-Mask 可以理解成一条清晰的双管线:

flowchart LR
    Conn[连接接入] --> Session[session 热路径]
    Session --> Rewrite[HTTP UA 改写]
    Rewrite --> Forward[转发上游]
    Session --> Event[事件流]
    Event --> Profile[目标画像]
    Profile --> Policy[卸载策略]
    Policy --> Firewall[动态写 set]
    Firewall --> Bypass[后续流量绕过]

也就是:热路径负责尽快识别、改写、转发;控制路径异步观察流量特征,并把确定可以绕过的目标交给内核防火墙处理。