4155fc8542
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.7 KiB
Go
103 lines
3.7 KiB
Go
package agentd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
// WarpConfig 描述节点上「部分域名走 Cloudflare WARP 干净出口」的分流配置(#29)。
|
|
// 由节点本地文件(默认 <StateDir>/warp.json)提供,agent 渲染 sing-box 配置时读取:
|
|
// 存在且 enabled 且有域名 → 注入一个 WireGuard(WARP) endpoint + 域名分流 route 规则,
|
|
// 命中域名走 WARP、其余直连。运营改域名清单只需编辑该文件并重启 agent(sing-box 无热重载)。
|
|
//
|
|
// WARP 凭证(private_key / peer_public_key / endpoint / address / reserved)由 wgcf
|
|
// 注册免费匿名 WARP 账号得到,是节点私有的,不入 git、不经控制面。
|
|
type WarpConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
PrivateKey string `json:"private_key"`
|
|
PeerPublicKey string `json:"peer_public_key"`
|
|
Endpoint string `json:"endpoint"` // host:port,如 162.159.192.1:2408
|
|
Address []string `json:"address"` // 本端 WARP 分配地址,如 ["172.16.0.2/32","2606:4700:110:...::/128"]
|
|
Reserved []int `json:"reserved"` // WARP client reserved 三字节(可空)
|
|
MTU int `json:"mtu"` // 缺省 1280
|
|
Domains []string `json:"domains"` // 走 WARP 的域名后缀,如 ["reddit.com","redd.it"]
|
|
}
|
|
|
|
// LoadWarpConfig 读取并解析 warp.json。文件不存在 → 返回 (nil, nil)(WARP 未启用,
|
|
// 不是错误)。解析失败或字段缺失才返回 error,避免坏配置静默退化。
|
|
func LoadWarpConfig(path string) (*WarpConfig, error) {
|
|
data, err := os.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("agentd: read warp config %q: %w", path, err)
|
|
}
|
|
var wc WarpConfig
|
|
if err := json.Unmarshal(data, &wc); err != nil {
|
|
return nil, fmt.Errorf("agentd: parse warp config %q: %w", path, err)
|
|
}
|
|
return &wc, nil
|
|
}
|
|
|
|
// active 报告本配置是否应真正注入分流(启用、凭证齐全、至少一个域名)。
|
|
// 任一必需字段缺失都返回 false —— 宁可不分流(全直连)也不产出坏 sing-box 配置。
|
|
func (wc *WarpConfig) active() bool {
|
|
if wc == nil || !wc.Enabled || len(wc.Domains) == 0 {
|
|
return false
|
|
}
|
|
if wc.PrivateKey == "" || wc.PeerPublicKey == "" || wc.Endpoint == "" || len(wc.Address) == 0 {
|
|
return false
|
|
}
|
|
host, _, err := net.SplitHostPort(wc.Endpoint)
|
|
return err == nil && host != ""
|
|
}
|
|
|
|
// mtu 返回配置的 MTU 或缺省 1280(WARP 常用值)。
|
|
func (wc *WarpConfig) mtu() int {
|
|
if wc.MTU > 0 {
|
|
return wc.MTU
|
|
}
|
|
return 1280
|
|
}
|
|
|
|
// cleanDomains 去空白/空项后返回域名清单(用于 domain_suffix)。
|
|
// 与 ACL 的目的地域名规范化共用 cleanHosts,避免两处逻辑漂移。
|
|
func (wc *WarpConfig) cleanDomains() []string {
|
|
return cleanHosts(wc.Domains)
|
|
}
|
|
|
|
// endpointHostPort 拆 Endpoint 为 host + port(active() 已校验可拆)。
|
|
func (wc *WarpConfig) endpointHostPort() (string, int) {
|
|
host, portStr, _ := net.SplitHostPort(wc.Endpoint)
|
|
port, _ := strconv.Atoi(portStr)
|
|
return host, port
|
|
}
|
|
|
|
// warpEndpoint 构造 sing-box 1.11+ 的 WireGuard endpoint(userspace,无需内核 wg 模块)。
|
|
// tag = "warp",route 规则以此 tag 作 outbound。
|
|
func (wc *WarpConfig) warpEndpoint() map[string]any {
|
|
host, port := wc.endpointHostPort()
|
|
peer := map[string]any{
|
|
"address": host,
|
|
"port": port,
|
|
"public_key": wc.PeerPublicKey,
|
|
"allowed_ips": []string{"0.0.0.0/0", "::/0"},
|
|
}
|
|
if len(wc.Reserved) == 3 {
|
|
peer["reserved"] = wc.Reserved
|
|
}
|
|
return map[string]any{
|
|
"type": "wireguard",
|
|
"tag": warpOutboundTag,
|
|
"system": false, // gVisor 用户态,不依赖内核 wireguard
|
|
"mtu": wc.mtu(),
|
|
"address": wc.Address,
|
|
"private_key": wc.PrivateKey,
|
|
"peers": []any{peer},
|
|
}
|
|
}
|