init xboard node
This commit is contained in:
@@ -0,0 +1,710 @@
|
||||
package singbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/config"
|
||||
"github.com/cedar2025/xboard-node/internal/kernel"
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
)
|
||||
|
||||
// M is a shorthand for building JSON-like maps
|
||||
type M = map[string]interface{}
|
||||
|
||||
func buildConfig(kcfg config.KernelConfig, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
outbounds := []M{
|
||||
{"type": "direct", "tag": "direct"},
|
||||
{"type": "block", "tag": "block"},
|
||||
}
|
||||
|
||||
// Panel-defined custom outbounds (structured, converted to sing-box native)
|
||||
for _, co := range nc.CustomOutbounds {
|
||||
outbounds = append(outbounds, outboundConfigToSingbox(co))
|
||||
}
|
||||
// Static outbounds from local config file (already kernel-native format)
|
||||
for _, co := range kcfg.CustomOutbound {
|
||||
outbounds = append(outbounds, M(co))
|
||||
}
|
||||
|
||||
cfg := M{
|
||||
"log": M{
|
||||
"level": kcfg.LogLevel,
|
||||
"timestamp": true,
|
||||
},
|
||||
"outbounds": outbounds,
|
||||
}
|
||||
|
||||
inbound := buildInbound(nc, users, certFile, keyFile)
|
||||
if inbound != nil {
|
||||
cfg["inbounds"] = []M{inbound}
|
||||
}
|
||||
|
||||
// Merge panel routes and static config routes
|
||||
cfg["route"] = buildRoutes(nc.Routes, mergeRouteList(nc.CustomRoutes, kcfg.CustomRoute))
|
||||
|
||||
// Automatically enable rule_set caching (cache_file) when panel routes
|
||||
// reference geoip:/geosite: entries so that the downloaded .srs rule_set
|
||||
// files survive across process restarts.
|
||||
if kernel.NeedsGeoIP(nc.Routes) || kernel.NeedsGeoSite(nc.Routes) {
|
||||
cfg["experimental"] = M{
|
||||
"cache_file": M{
|
||||
"enabled": true,
|
||||
"path": filepath.Join(kcfg.ConfigDir, "cache.db"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
mergeCustomSingbox(cfg, kcfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// outboundConfigToSingbox converts a structured OutboundConfig (from the panel)
|
||||
// into a sing-box outbound object. sing-box uses a flat layout where all
|
||||
// protocol-specific fields sit at the top level alongside "type" and "tag".
|
||||
func outboundConfigToSingbox(oc panel.OutboundConfig) M {
|
||||
m := M{
|
||||
"type": oc.Protocol,
|
||||
"tag": oc.Tag,
|
||||
}
|
||||
|
||||
// Transform common protocol keys to sing-box native format
|
||||
// WireGuard: secret_key -> private_key; peers: endpoint -> address+port
|
||||
if oc.Protocol == "wireguard" {
|
||||
if sk, ok := oc.Settings["secret_key"]; ok {
|
||||
m["private_key"] = sk
|
||||
}
|
||||
if peers, ok := oc.Settings["peers"].([]any); ok {
|
||||
var wgPeers []M
|
||||
for _, p := range peers {
|
||||
if peerMap, ok := p.(map[string]any); ok {
|
||||
newPeer := M{}
|
||||
for k, v := range peerMap {
|
||||
if k == "endpoint" {
|
||||
if ep, ok := v.(string); ok {
|
||||
host, portStr, err := net.SplitHostPort(ep)
|
||||
if err == nil {
|
||||
newPeer["address"] = host
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
newPeer["port"] = port
|
||||
} else {
|
||||
newPeer["address"] = ep
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newPeer[k] = v
|
||||
}
|
||||
}
|
||||
wgPeers = append(wgPeers, newPeer)
|
||||
}
|
||||
}
|
||||
m["peers"] = wgPeers
|
||||
}
|
||||
|
||||
// Copy any other top-level settings not handled above
|
||||
for k, v := range oc.Settings {
|
||||
if k != "secret_key" && k != "peers" {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for k, v := range oc.Settings {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if oc.ProxyTag != "" {
|
||||
m["proxy_tag"] = oc.ProxyTag
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func mergeRouteList(a, b []map[string]any) []map[string]any {
|
||||
res := make([]map[string]any, 0, len(a)+len(b))
|
||||
res = append(res, a...)
|
||||
res = append(res, b...)
|
||||
return res
|
||||
}
|
||||
|
||||
func buildRoutes(panelRoutes []panel.RouteRule, custom []map[string]any) M {
|
||||
var rules []M
|
||||
|
||||
// Custom Routes (Panel-pushed or Local) go FIRST to have highest priority
|
||||
for _, cr := range custom {
|
||||
rules = append(rules, M(cr))
|
||||
}
|
||||
|
||||
// Standard blocks for private/loopback
|
||||
rules = append(rules, M{
|
||||
"outbound": "block",
|
||||
"ip_cidr": []string{
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"::1/128",
|
||||
},
|
||||
}, M{
|
||||
"outbound": "block",
|
||||
"domain": []string{
|
||||
"geoip:private",
|
||||
},
|
||||
})
|
||||
|
||||
// Panel-defined routes (usually specific blocks/proxies)
|
||||
for _, pr := range panelRoutes {
|
||||
if len(pr.Match) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var domains, cidrs []string
|
||||
for _, m := range pr.Match {
|
||||
m = strings.TrimSpace(m)
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
// Strip leading wildcard for domain matching
|
||||
if strings.HasPrefix(m, "*.") {
|
||||
m = strings.TrimPrefix(m, "*.")
|
||||
}
|
||||
// Check if it's a CIDR block (contains /)
|
||||
if strings.Contains(m, "/") {
|
||||
cidrs = append(cidrs, m)
|
||||
} else {
|
||||
// Otherwise treat as domain
|
||||
domains = append(domains, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine outbound tag based on action
|
||||
outbound := "block"
|
||||
if pr.Action == "direct" {
|
||||
outbound = "direct"
|
||||
} else if pr.Action == "dns" {
|
||||
// Special case for sing-box DNS routing:
|
||||
// If action is "dns" and action_value is provided, we use it as the server name.
|
||||
// Otherwise it defaults to "dns-out".
|
||||
server := "dns-out"
|
||||
if pr.ActionValue != "" {
|
||||
server = pr.ActionValue
|
||||
}
|
||||
outbound = server
|
||||
} else if pr.Action == "proxy" {
|
||||
// If panel provides a specific outbound tag in action_value, use it.
|
||||
// This allows routing to WARP_JP etc. via normal routes.
|
||||
if pr.ActionValue != "" {
|
||||
outbound = pr.ActionValue
|
||||
}
|
||||
}
|
||||
|
||||
// Create separate rule for domains
|
||||
if len(domains) > 0 {
|
||||
rule := M{
|
||||
"domain_suffix": domains,
|
||||
"outbound": outbound,
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
// Create separate rule for CIDRs
|
||||
if len(cidrs) > 0 {
|
||||
rule := M{
|
||||
"ip_cidr": cidrs,
|
||||
"outbound": outbound,
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return M{
|
||||
"final": "direct",
|
||||
"rules": rules,
|
||||
}
|
||||
}
|
||||
|
||||
func mergeCustomSingbox(cfg M, kcfg config.KernelConfig) {
|
||||
custom, err := kernel.LoadCustomConfig(kcfg.CustomConfig)
|
||||
if err != nil {
|
||||
slog.Error("failed to load custom sing-box config", "error", err)
|
||||
return
|
||||
}
|
||||
if custom == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// dns — custom replaces
|
||||
if v, ok := custom["dns"]; ok {
|
||||
cfg["dns"] = v
|
||||
}
|
||||
|
||||
// experimental — custom replaces
|
||||
if v, ok := custom["experimental"]; ok {
|
||||
cfg["experimental"] = v
|
||||
}
|
||||
|
||||
// outbounds — append custom entries
|
||||
if v, ok := custom["outbounds"]; ok {
|
||||
if existing, ok := cfg["outbounds"].([]M); ok {
|
||||
cfg["outbounds"] = kernel.MergeAppendList(existing, v)
|
||||
}
|
||||
}
|
||||
|
||||
// endpoints (sing-box 1.11+ wireguard etc.) — append
|
||||
if v, ok := custom["endpoints"]; ok {
|
||||
if existing, ok := cfg["endpoints"].([]M); ok {
|
||||
cfg["endpoints"] = kernel.MergeAppendList(existing, v)
|
||||
} else {
|
||||
if items := kernel.MergeAppendList(nil, v); len(items) > 0 {
|
||||
cfg["endpoints"] = items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// route — merge sub-fields
|
||||
if customRoute, ok := custom["route"]; ok {
|
||||
if customRouteMap, ok := customRoute.(map[string]any); ok {
|
||||
mergeCustomSingboxRoute(cfg, customRouteMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mergeCustomSingboxRoute(cfg M, customRoute map[string]any) {
|
||||
route, ok := cfg["route"].(M)
|
||||
if !ok {
|
||||
route = M{}
|
||||
cfg["route"] = route
|
||||
}
|
||||
|
||||
// rules — custom rules prepended (so they match before panel rules)
|
||||
if v, ok := customRoute["rules"]; ok {
|
||||
if existing, ok := route["rules"].([]M); ok {
|
||||
route["rules"] = kernel.MergePrependList(existing, v)
|
||||
}
|
||||
}
|
||||
|
||||
// rule_set — custom rule_sets appended
|
||||
if v, ok := customRoute["rule_set"]; ok {
|
||||
if existing, ok := route["rule_set"].([]M); ok {
|
||||
route["rule_set"] = kernel.MergeAppendList(existing, v)
|
||||
} else {
|
||||
route["rule_set"] = kernel.MergeAppendList(nil, v)
|
||||
}
|
||||
}
|
||||
|
||||
// final, auto_detect_interface, default_interface, etc. — custom overrides
|
||||
for k, v := range customRoute {
|
||||
if k == "rules" || k == "rule_set" {
|
||||
continue
|
||||
}
|
||||
route[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func buildInbound(nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base := M{
|
||||
"tag": nc.Protocol + "-in",
|
||||
"listen": "::",
|
||||
"listen_port": nc.ServerPort,
|
||||
}
|
||||
|
||||
switch nc.Protocol {
|
||||
case "shadowsocks":
|
||||
return buildShadowsocks(base, nc, users)
|
||||
case "vmess":
|
||||
return buildVMess(base, nc, users, certFile, keyFile)
|
||||
case "vless":
|
||||
return buildVLESS(base, nc, users, certFile, keyFile)
|
||||
case "trojan":
|
||||
return buildTrojan(base, nc, users, certFile, keyFile)
|
||||
case "hysteria":
|
||||
return buildHysteria(base, nc, users, certFile, keyFile)
|
||||
case "tuic":
|
||||
return buildTUIC(base, nc, users, certFile, keyFile)
|
||||
case "anytls":
|
||||
return buildAnyTLS(base, nc, users, certFile, keyFile)
|
||||
case "naive":
|
||||
return buildNaive(base, nc, users, certFile, keyFile)
|
||||
case "socks":
|
||||
return buildSocks(base, users)
|
||||
case "http":
|
||||
return buildHTTP(base, nc, users, certFile, keyFile)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M {
|
||||
base["type"] = "shadowsocks"
|
||||
base["method"] = nc.Cipher
|
||||
|
||||
if strings.HasPrefix(nc.Cipher, "2022-blake3-") {
|
||||
base["password"] = nc.ServerKey
|
||||
}
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.Plugin != "" {
|
||||
base["plugin"] = nc.Plugin
|
||||
base["plugin_opts"] = nc.PluginOpt
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
func buildVMess(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "vmess"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"uuid": u.UUID,
|
||||
"alterId": 0,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
func buildVLESS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "vless"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
user := M{
|
||||
"name": u.UUID,
|
||||
"uuid": u.UUID,
|
||||
}
|
||||
if nc.Flow != "" {
|
||||
user["flow"] = nc.Flow
|
||||
}
|
||||
userList = append(userList, user)
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
} else if nc.TLS == 2 {
|
||||
base["tls"] = buildRealityConfig(nc)
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
func buildTrojan(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "trojan"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
} else if nc.TLS == 2 {
|
||||
base["tls"] = buildRealityConfig(nc)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func buildHysteria(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
if nc.Version == 2 {
|
||||
base["type"] = "hysteria2"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.Obfs != "" {
|
||||
base["obfs"] = M{
|
||||
"type": nc.Obfs,
|
||||
"password": nc.ObfsPassword,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
base["type"] = "hysteria"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"auth_str": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
base["up_mbps"] = nc.UpMbps
|
||||
base["down_mbps"] = nc.DownMbps
|
||||
|
||||
if nc.Obfs != "" {
|
||||
base["obfs"] = nc.Obfs
|
||||
}
|
||||
}
|
||||
|
||||
tls := buildTLSConfig(nc, certFile, keyFile)
|
||||
// Hysteria/Hysteria2 uses QUIC and requires ALPN; default to h3 if not set.
|
||||
if _, ok := tls["alpn"]; !ok {
|
||||
tls["alpn"] = []string{"h3"}
|
||||
}
|
||||
base["tls"] = tls
|
||||
return base
|
||||
}
|
||||
|
||||
func buildTUIC(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "tuic"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"uuid": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.CongestionControl != "" {
|
||||
base["congestion_control"] = nc.CongestionControl
|
||||
}
|
||||
|
||||
tls := buildTLSConfig(nc, certFile, keyFile)
|
||||
// TUIC requires ALPN for QUIC negotiation; default to h3 if not set by panel.
|
||||
if _, ok := tls["alpn"]; !ok {
|
||||
tls["alpn"] = []string{"h3"}
|
||||
}
|
||||
base["tls"] = tls
|
||||
return base
|
||||
}
|
||||
|
||||
func buildAnyTLS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "anytls"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.PaddingScheme != "" {
|
||||
base["padding_scheme"] = string(nc.PaddingScheme)
|
||||
}
|
||||
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
return base
|
||||
}
|
||||
|
||||
func buildNaive(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "naive"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"username": strconv.Itoa(u.ID),
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func buildSocks(base M, users []panel.User) M {
|
||||
base["type"] = "socks"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"username": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
return base
|
||||
}
|
||||
|
||||
func buildHTTP(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M {
|
||||
base["type"] = "http"
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"username": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func applyTransport(base M, nc *panel.NodeConfig) {
|
||||
if nc.Network == "" || nc.Network == "tcp" {
|
||||
return
|
||||
}
|
||||
|
||||
transport := M{"type": nc.Network}
|
||||
|
||||
if nc.NetworkSettings != nil {
|
||||
switch nc.Network {
|
||||
case "ws":
|
||||
if v, ok := nc.NetworkSettings["path"]; ok {
|
||||
transport["path"] = v
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["headers"]; ok {
|
||||
transport["headers"] = v
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["host"]; ok {
|
||||
if transport["headers"] == nil {
|
||||
transport["headers"] = M{"Host": v}
|
||||
}
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["max_early_data"]; ok {
|
||||
transport["max_early_data"] = v
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["early_data_header_name"]; ok {
|
||||
transport["early_data_header_name"] = v
|
||||
}
|
||||
case "grpc":
|
||||
if v, ok := nc.NetworkSettings["service_name"]; ok {
|
||||
transport["service_name"] = v
|
||||
}
|
||||
case "httpupgrade":
|
||||
if v, ok := nc.NetworkSettings["path"]; ok {
|
||||
transport["path"] = v
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["host"]; ok {
|
||||
transport["host"] = v
|
||||
}
|
||||
case "h2", "http":
|
||||
transport["type"] = "http"
|
||||
if v, ok := nc.NetworkSettings["path"]; ok {
|
||||
transport["path"] = v
|
||||
}
|
||||
if v, ok := nc.NetworkSettings["host"]; ok {
|
||||
transport["host"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base["transport"] = transport
|
||||
}
|
||||
|
||||
func buildTLSConfig(nc *panel.NodeConfig, certFile, keyFile string) M {
|
||||
tls := M{"enabled": true}
|
||||
|
||||
serverName := nc.ServerName
|
||||
if serverName == "" && nc.Host != "" {
|
||||
serverName = nc.Host
|
||||
}
|
||||
if serverName != "" {
|
||||
tls["server_name"] = serverName
|
||||
}
|
||||
|
||||
if nc.TLSSettings != nil {
|
||||
if sn, ok := nc.TLSSettings["server_name"]; ok && sn != "" {
|
||||
tls["server_name"] = sn
|
||||
}
|
||||
if alpn, ok := nc.TLSSettings["alpn"]; ok {
|
||||
tls["alpn"] = alpn
|
||||
}
|
||||
}
|
||||
|
||||
if certFile != "" && keyFile != "" {
|
||||
tls["certificate_path"] = certFile
|
||||
tls["key_path"] = keyFile
|
||||
}
|
||||
|
||||
return tls
|
||||
}
|
||||
|
||||
func buildRealityConfig(nc *panel.NodeConfig) M {
|
||||
tls := M{"enabled": true}
|
||||
|
||||
if nc.TLSSettings == nil {
|
||||
return tls
|
||||
}
|
||||
|
||||
reality := M{"enabled": true}
|
||||
|
||||
if pk, ok := nc.TLSSettings["private_key"]; ok {
|
||||
reality["private_key"] = pk
|
||||
}
|
||||
if sid, ok := nc.TLSSettings["short_id"]; ok {
|
||||
switch v := sid.(type) {
|
||||
case string:
|
||||
reality["short_id"] = []string{v}
|
||||
case []interface{}:
|
||||
reality["short_id"] = v
|
||||
}
|
||||
}
|
||||
|
||||
handshake := M{}
|
||||
if dest, ok := nc.TLSSettings["dest"]; ok {
|
||||
destStr := fmt.Sprintf("%v", dest)
|
||||
parts := strings.SplitN(destStr, ":", 2)
|
||||
handshake["server"] = parts[0]
|
||||
if len(parts) == 2 {
|
||||
if port, err := strconv.Atoi(parts[1]); err == nil {
|
||||
handshake["server_port"] = port
|
||||
}
|
||||
} else {
|
||||
handshake["server_port"] = 443
|
||||
}
|
||||
}
|
||||
if sn, ok := nc.TLSSettings["server_name"]; ok {
|
||||
handshake["server"] = sn
|
||||
if _, exists := handshake["server_port"]; !exists {
|
||||
handshake["server_port"] = 443
|
||||
}
|
||||
}
|
||||
|
||||
if len(handshake) > 0 {
|
||||
reality["handshake"] = handshake
|
||||
}
|
||||
|
||||
tls["reality"] = reality
|
||||
return tls
|
||||
}
|
||||
Reference in New Issue
Block a user