package main import ( "context" "crypto/ecdh" "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "math/big" "net" "strings" ) // ---------- catalog data (mirrors lib/protocols.js) ---------- type Profile struct { ID string `json:"id"` Role string `json:"role"` Name string `json:"name"` Status string `json:"status"` Deployable bool `json:"deployable"` Capabilities []string `json:"capabilities"` Description string `json:"description"` } type RealityPreset struct { ID string `json:"id"` Label string `json:"label"` ServerName string `json:"serverName"` DestPort int `json:"destPort"` } var RealityPresets = []RealityPreset{ {ID: "tesla", Label: "Tesla", ServerName: "www.tesla.com", DestPort: 443}, {ID: "amazon", Label: "Amazon", ServerName: "www.amazon.com", DestPort: 443}, {ID: "apple", Label: "Apple", ServerName: "www.apple.com", DestPort: 443}, {ID: "intel", Label: "Intel", ServerName: "www.intel.com", DestPort: 443}, {ID: "amd", Label: "AMD", ServerName: "www.amd.com", DestPort: 443}, } var ProfileCatalog = []Profile{ {ID: "vless-reality-vision", Role: "relay-ingress", Name: "VLESS · Reality · Vision", Status: "stable", Deployable: true, Capabilities: []string{"sing-box", "reality"}, Description: "推荐公网入口,无需在节点机部署证书。"}, {ID: "vless-reality", Role: "relay-ingress", Name: "VLESS · Reality", Status: "stable", Deployable: true, Capabilities: []string{"sing-box", "reality"}, Description: "不启用 Vision 流控的 Reality 入口。"}, {ID: "vless-ws", Role: "relay-ingress", Name: "VLESS · WebSocket", Status: "beta", Deployable: true, Capabilities: []string{"sing-box", "websocket"}, Description: "无 TLS 的兼容模式,建议仅在可信或外层 TLS 环境使用。"}, {ID: "shadowsocks-2022-aes128", Role: "relay-ingress", Name: "SS 2022 · AES-128", Status: "stable", Deployable: true, Capabilities: []string{"sing-box"}, Description: "轻量、推荐的 AEAD 2022 入口。"}, {ID: "shadowsocks-2022-aes256", Role: "relay-ingress", Name: "SS 2022 · AES-256", Status: "stable", Deployable: true, Capabilities: []string{"sing-box"}, Description: "32 字节密钥的 AEAD 2022 入口。"}, {ID: "shadowsocks-aes128-gcm", Role: "relay-ingress", Name: "SS · AES-128-GCM", Status: "stable", Deployable: true, Capabilities: []string{"sing-box"}, Description: "兼容主流旧版 Shadowsocks 客户端。"}, {ID: "shadowsocks-aes256-gcm", Role: "relay-ingress", Name: "SS · AES-256-GCM", Status: "stable", Deployable: true, Capabilities: []string{"sing-box"}, Description: "兼容主流旧版 Shadowsocks 客户端。"}, {ID: "socks5-auth", Role: "relay-ingress", Name: "SOCKS5 · 用户密码", Status: "stable", Deployable: true, Capabilities: []string{"sing-box"}, Description: "适合受控网络或程序级代理。"}, {ID: "vless-ws-tls", Role: "relay-ingress", Name: "VLESS · WS · TLS", Status: "beta", Deployable: true, Capabilities: []string{"sing-box", "tls-domain"}, Description: "需为每台入口设备填写 TLS 域名,并在 Agent 上安装对应证书。"}, {ID: "hysteria2", Role: "relay-ingress", Name: "Hysteria 2 · UDP", Status: "beta", Deployable: true, Capabilities: []string{"sing-box", "udp", "tls-domain"}, Description: "由 sing-box 承载;入口须安装有效节点证书并放行所选 UDP 端口。"}, {ID: "anytls", Role: "relay-ingress", Name: "AnyTLS · TLS", Status: "beta", Deployable: true, Capabilities: []string{"sing-box", "tls-domain"}, Description: "由 sing-box 独立承载,需入口设备的有效节点证书;支持直连或转发到现有出口。"}, {ID: "shadowsocks-2022-aes128", Role: "exit-transport", Name: "SS 2022 · AES-128", Status: "stable", Deployable: true, Capabilities: []string{"encrypted"}, Description: "推荐的转发到出口传输。"}, {ID: "shadowsocks-2022-aes256", Role: "exit-transport", Name: "SS 2022 · AES-256", Status: "stable", Deployable: true, Capabilities: []string{"encrypted"}, Description: "增强密钥长度的转发传输。"}, {ID: "shadowsocks-aes128-gcm", Role: "exit-transport", Name: "SS · AES-128-GCM", Status: "stable", Deployable: true, Capabilities: []string{"encrypted"}, Description: "兼容旧环境的转发传输。"}, {ID: "shadowsocks-aes256-gcm", Role: "exit-transport", Name: "SS · AES-256-GCM", Status: "stable", Deployable: true, Capabilities: []string{"encrypted"}, Description: "兼容旧环境的转发传输。"}, {ID: "vless-tcp", Role: "exit-transport", Name: "VLESS · TCP", Status: "beta", Deployable: true, Capabilities: []string{}, Description: "适合可信内网或已有加密隧道,不建议裸露公网。"}, {ID: "socks5-auth", Role: "exit-transport", Name: "SOCKS5 · 用户密码", Status: "beta", Deployable: true, Capabilities: []string{}, Description: "不加密,仅用于可信网络或外层隧道。"}, } type SSMethod struct { Method string KeyBytes int } var ShadowsocksMethods = map[string]SSMethod{ "shadowsocks-2022-aes128": {Method: "2022-blake3-aes-128-gcm", KeyBytes: 16}, "shadowsocks-2022-aes256": {Method: "2022-blake3-aes-256-gcm", KeyBytes: 32}, "shadowsocks-aes128-gcm": {Method: "aes-128-gcm", KeyBytes: 24}, "shadowsocks-aes256-gcm": {Method: "aes-256-gcm", KeyBytes: 32}, } // ---------- small helpers ---------- func xbDeref(p *string) string { if p == nil { return "" } return *p } func xbDerefInt(p *int) int { if p == nil { return 0 } return *p } func xbRandBytes(n int) []byte { b := make([]byte, n) if _, err := rand.Read(b); err != nil { panic(err) } return b } // b64 mirrors JS b64(): standard base64 (with padding) of n random bytes. func b64(n int) string { return base64.StdEncoding.EncodeToString(xbRandBytes(n)) } func shortId() string { return hex.EncodeToString(xbRandBytes(8)) } func xbRandInt(n int) int { v, err := rand.Int(rand.Reader, big.NewInt(int64(n))) if err != nil { panic(err) } return int(v.Int64()) } // ---------- encoding helpers (JS-exact) ---------- const xbHexUpper = "0123456789ABCDEF" func xbPctEncodeByte(sb *strings.Builder, c byte) { sb.WriteByte('%') sb.WriteByte(xbHexUpper[c>>4]) sb.WriteByte(xbHexUpper[c&0x0f]) } // formEncode is URLSearchParams' application/x-www-form-urlencoded // serialization: alphanumerics and * - . _ pass through, space becomes +, // everything else (including ~) is %XX uppercase. func formEncode(s string) string { var sb strings.Builder for i := 0; i < len(s); i++ { c := s[i] switch { case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '*' || c == '-' || c == '.' || c == '_': sb.WriteByte(c) case c == ' ': sb.WriteByte('+') default: xbPctEncodeByte(&sb, c) } } return sb.String() } // encodeURIComponent: A-Za-z0-9-_.!~*'() pass through, everything else %XX. func encodeURIComponent(s string) string { var sb strings.Builder for i := 0; i < len(s); i++ { c := s[i] switch { case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')': sb.WriteByte(c) default: xbPctEncodeByte(&sb, c) } } return sb.String() } type xbQueryParam struct{ k, v string } type xbQuery []xbQueryParam func (q *xbQuery) add(k, v string) { *q = append(*q, xbQueryParam{k, v}) } // set mirrors URLSearchParams.set: replace first occurrence, drop later // duplicates, append when absent. func (q *xbQuery) set(k, v string) { out := (*q)[:0] found := false for _, p := range *q { if p.k == k { if found { continue } found = true out = append(out, xbQueryParam{k, v}) } else { out = append(out, p) } } if !found { out = append(out, xbQueryParam{k, v}) } *q = out } func (q xbQuery) String() string { parts := make([]string, len(q)) for i, p := range q { parts[i] = formEncode(p.k) + "=" + formEncode(p.v) } return strings.Join(parts, "&") } // ---------- node secrets ---------- func NodeEmail(customerID string) string { return "ng:" + customerID } // NewCustomerCredentials mints the single credential set a customer uses on // every node (round-2 item 2, 3x-ui model). The relay password is always a // base64 32-byte root key: SS2022 user keys are derived from it per method // (see EffectiveRelayPassword), so one set serves aes128 and aes256 nodes // alike; every other protocol uses it verbatim. func NewCustomerCredentials() Credentials { return Credentials{ ClientID: NewUUID(), RelayPassword: b64(32), ShortID: shortId(), } } // EffectiveRelayPassword maps a customer's relay password onto a node's // protocol. SS2022 methods require the user key to be base64 of exactly the // method's key length, so the user key is the deterministic prefix // truncation of the 32-byte root key. Legacy SS methods and all other // protocols take the password verbatim. func EffectiveRelayPassword(protocol, relayPassword string) string { cipher, ok := ShadowsocksMethods[protocol] if !ok || !strings.HasPrefix(cipher.Method, "2022-blake3-") { return relayPassword } raw, err := base64.StdEncoding.DecodeString(relayPassword) if err != nil || len(raw) < cipher.KeyBytes { return relayPassword } return base64.StdEncoding.EncodeToString(raw[:cipher.KeyBytes]) } // clientCredentials resolves the credentials stamped onto a binding for // resource building. Stored bindings no longer carry credentials (they live // on the customer); builders receive resolved copies, and this accessor // keeps a nil hole from becoming a panic. func clientCredentials(nc *NodeClient) Credentials { if nc == nil || nc.Credentials == nil { return Credentials{} } return *nc.Credentials } func EnsureNodeSecrets(node *Node) { if node.WSPath == nil || *node.WSPath == "" { node.WSPath = StrPtr("/" + hex.EncodeToString(xbRandBytes(9))) } if cipher, ok := ShadowsocksMethods[node.Protocol]; ok { if node.SSServerKey == nil || *node.SSServerKey == "" { node.SSServerKey = StrPtr(b64(cipher.KeyBytes)) } } if IsRealityProtocol(node.Protocol) { if node.Reality == nil { node.Reality = &RealityConfig{KeyID: "reality-" + node.ID} } serverName := node.RealityServerName if serverName == "" { serverName = "www.tesla.com" } node.Reality.ServerName = serverName destPort := node.RealityDestPort if destPort == 0 { destPort = 443 } node.Reality.DestPort = destPort } topology := node.Topology if topology == "" { topology = "forward" } if topology != "direct" && node.ExitCredentials == nil { keyBytes := 24 if cipher, ok := ShadowsocksMethods[xbDeref(node.ExitProtocol)]; ok { keyBytes = cipher.KeyBytes } node.ExitCredentials = &ExitCredentials{ ClientID: NewUUID(), Password: b64(keyBytes), SocksUser: "ng_" + hex.EncodeToString(xbRandBytes(5)), } } } // ---------- addresses ---------- func listenAddress(networkMode string, server *Server, bindAddress string) string { if bindAddress != "" { return bindAddress } if networkMode == "ipv6" { if server != nil && server.PublicAddressV6 != "" { return server.PublicAddressV6 } return "::" } if networkMode == "dual" { return "::" } if server != nil && server.PublicAddress != "" { return server.PublicAddress } return "0.0.0.0" } func serverAddress(server *Server, networkMode string) (string, error) { if server == nil { return "", errors.New("Missing server for address selection") } if networkMode == "ipv6" { return server.PublicAddressV6, nil } return server.PublicAddress, nil } // ResolveBindAddress mirrors resolveBindAddress: dual/absent -> ("", nil). // DNS failures are HTTPError 400; the parenthesized code approximates // err.code with the Go resolver's short error text (e.g. "no such host"), // since Go exposes no getaddrinfo code. func ResolveBindAddress(networkMode string, server *Server) (string, error) { if networkMode == "dual" { return "", nil } var raw string if server != nil { if networkMode == "ipv6" { raw = server.PublicAddressV6 } else { raw = server.PublicAddress } } if raw == "" { return "", nil } text := strings.TrimSpace(raw) family := 4 record := "A" network := "ip4" if networkMode == "ipv6" { family = 6 record = "AAAA" network = "ip6" } _ = family if ip := net.ParseIP(text); ip != nil { return text, nil } addrs, err := net.DefaultResolver.LookupIP(context.Background(), network, text) if err != nil || len(addrs) == 0 { code := "" if err != nil { var dnsErr *net.DNSError if errors.As(err, &dnsErr) && dnsErr.Err != "" { code = dnsErr.Err } else { code = err.Error() } } else { code = "no addresses" } return "", httpErr(400, "绑定地址 DNS 解析失败:%s 没有 %s 记录(%s),请检查该服务器登记的地址", text, record, code) } return addrs[0].String(), nil } // ---------- sing-box resource builders ---------- func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, server *Server, bindAddress string) (map[string]any, error) { protocol := node.Protocol port := node.Port networkMode := node.NetworkMode if networkMode == "" { networkMode = "ipv4" } listen := listenAddress(networkMode, server, bindAddress) tlsDomain := "" if server != nil { tlsDomain = server.TLSDomain } if protocol == "vless-reality-vision" || protocol == "vless-reality" { if node.Reality == nil { return nil, errors.New("Reality 节点缺少密钥配置") } users := make([]any, 0, len(nodeClients)) shortIDs := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { creds := clientCredentials(nc) user := map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": creds.ClientID} if protocol == "vless-reality-vision" { user["flow"] = "xtls-rprx-vision" } users = append(users, user) shortIDs = append(shortIDs, creds.ShortID) } destPort := node.Reality.DestPort if destPort == 0 { destPort = 443 } return map[string]any{ "type": "vless", "tag": tag, "listen": listen, "listen_port": port, "users": users, "tls": map[string]any{ "enabled": true, "server_name": node.Reality.ServerName, "reality": map[string]any{ "enabled": true, "handshake": map[string]any{"server": node.Reality.ServerName, "server_port": destPort}, "private_key": "${REALITY_PRIVATE:" + node.Reality.KeyID + "}", "short_id": shortIDs, }, }, }, nil } if protocol == "vless-ws" || protocol == "vless-ws-tls" { if protocol == "vless-ws-tls" && tlsDomain == "" { return nil, errors.New("TLS 节点缺少有效证书域名") } users := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": clientCredentials(nc).ClientID}) } wsPath := xbDeref(node.WSPath) if wsPath == "" { wsPath = "/" } inbound := map[string]any{ "type": "vless", "tag": tag, "listen": listen, "listen_port": port, "users": users, "transport": map[string]any{"type": "ws", "path": wsPath}, } if protocol == "vless-ws-tls" { inbound["tls"] = map[string]any{ "enabled": true, "server_name": tlsDomain, "certificate_path": "/etc/nexusgate/tls/" + tlsDomain + "/fullchain.pem", "key_path": "/etc/nexusgate/tls/" + tlsDomain + "/privkey.pem", } } return inbound, nil } if cipher, ok := ShadowsocksMethods[protocol]; ok { users := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": EffectiveRelayPassword(protocol, clientCredentials(nc).RelayPassword)}) } return map[string]any{ "type": "shadowsocks", "tag": tag, "listen": listen, "listen_port": port, "method": cipher.Method, "password": xbDeref(node.SSServerKey), "users": users, }, nil } if protocol == "socks5-auth" { users := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { users = append(users, map[string]any{"username": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword}) } return map[string]any{"type": "socks", "tag": tag, "listen": listen, "listen_port": port, "users": users}, nil } if protocol == "hysteria2" { if tlsDomain == "" { return nil, errors.New("Hysteria 2 入口缺少有效证书域名") } users := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword}) } return map[string]any{ "type": "hysteria2", "tag": tag, "listen": listen, "listen_port": port, "users": users, "tls": map[string]any{ "enabled": true, "server_name": tlsDomain, "alpn": []any{"h3"}, "certificate_path": "/etc/nexusgate/tls/" + tlsDomain + "/fullchain.pem", "key_path": "/etc/nexusgate/tls/" + tlsDomain + "/privkey.pem", }, }, nil } if protocol == "anytls" { if tlsDomain == "" { return nil, errors.New("AnyTLS 入口缺少有效证书域名") } users := make([]any, 0, len(nodeClients)) for _, nc := range nodeClients { users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword}) } return map[string]any{ "type": "anytls", "tag": tag, "listen": listen, "listen_port": port, "users": users, "tls": map[string]any{ "enabled": true, "server_name": tlsDomain, "certificate_path": "/etc/nexusgate/tls/" + tlsDomain + "/fullchain.pem", "key_path": "/etc/nexusgate/tls/" + tlsDomain + "/privkey.pem", }, }, nil } return nil, fmt.Errorf("sing-box 不支持的协议: %s", protocol) } type BuildNodeResourceParams struct { ResourceID string TagPrefix string Node *Node NodeClients []*NodeClient Server *Server BindAddress string ExitServer *Server } func BuildNodeResource(p BuildNodeResourceParams) (map[string]any, error) { node := p.Node EnsureNodeSecrets(node) tag := p.TagPrefix + "-in" topology := node.Topology if topology == "" { topology = "forward" } forward := topology == "forward" outboundTag := p.TagPrefix + "-direct" if forward { outboundTag = p.TagPrefix + "-exit" } networkMode := node.NetworkMode if networkMode == "" { networkMode = "ipv4" } var outbound map[string]any if !forward { outbound = map[string]any{"type": "direct", "tag": outboundTag} } else { exitAddr, err := serverAddress(p.ExitServer, networkMode) if err != nil { return nil, err } base := map[string]any{"tag": outboundTag, "server": exitAddr, "server_port": xbDerefInt(node.ExitPort)} creds := node.ExitCredentials if creds == nil { creds = &ExitCredentials{} } exitProtocol := xbDeref(node.ExitProtocol) if cipher, ok := ShadowsocksMethods[exitProtocol]; ok { outbound = cloneMap(base) outbound["type"] = "shadowsocks" outbound["method"] = cipher.Method outbound["password"] = creds.Password } else if exitProtocol == "socks5-auth" { outbound = cloneMap(base) outbound["type"] = "socks" outbound["version"] = "5" outbound["username"] = creds.SocksUser outbound["password"] = creds.Password } else if exitProtocol == "vless-tcp" { outbound = cloneMap(base) outbound["type"] = "vless" outbound["uuid"] = creds.ClientID } else { return nil, fmt.Errorf("Unsupported exit transport: %s", exitProtocol) } } inbound, err := buildSingBoxInbound(tag, node, p.NodeClients, p.Server, p.BindAddress) if err != nil { return nil, err } return map[string]any{ "id": p.ResourceID, "engine": "sing-box", "meta": map[string]any{"kind": "node", "nodeId": node.ID, "metricsTag": tag, "protocol": node.Protocol}, "inbounds": []any{inbound}, "outbounds": []any{outbound}, "routingRules": []any{ map[string]any{"inbound": []any{tag}, "action": "route", "outbound": outboundTag}, }, }, nil } func cloneMap(m map[string]any) map[string]any { out := make(map[string]any, len(m)) for k, v := range m { out[k] = v } return out } type BuildNodeExitResourceParams struct { ResourceID string TagPrefix string Node *Node Server *Server BindAddress string } func BuildNodeExitResource(p BuildNodeExitResourceParams) (map[string]any, error) { node := p.Node tag := p.TagPrefix + "-exit-in" directTag := p.TagPrefix + "-exit-direct" protocol := xbDeref(node.ExitProtocol) creds := node.ExitCredentials if creds == nil { creds = &ExitCredentials{} } networkMode := node.NetworkMode if networkMode == "" { networkMode = "ipv4" } listen := listenAddress(networkMode, p.Server, p.BindAddress) var inbound map[string]any if cipher, ok := ShadowsocksMethods[protocol]; ok { inbound = map[string]any{ "type": "shadowsocks", "tag": tag, "listen": listen, "listen_port": xbDerefInt(node.ExitPort), "method": cipher.Method, "password": creds.Password, } } else if protocol == "vless-tcp" { inbound = map[string]any{ "type": "vless", "tag": tag, "listen": listen, "listen_port": xbDerefInt(node.ExitPort), "users": []any{map[string]any{"uuid": creds.ClientID}}, } } else if protocol == "socks5-auth" { inbound = map[string]any{ "type": "socks", "tag": tag, "listen": listen, "listen_port": xbDerefInt(node.ExitPort), "users": []any{map[string]any{"username": creds.SocksUser, "password": creds.Password}}, } } else { return nil, fmt.Errorf("Unsupported exit transport: %s", protocol) } return map[string]any{ "id": p.ResourceID, "engine": "sing-box", "meta": map[string]any{"kind": "node-exit", "nodeId": node.ID, "metricsTag": tag}, "inbounds": []any{inbound}, "outbounds": []any{ map[string]any{"type": "direct", "tag": directTag}, }, "routingRules": []any{ map[string]any{"inbound": []any{tag}, "action": "route", "outbound": directTag}, }, }, nil } // ---------- client URIs ---------- func uriHost(address string) string { if ip := net.ParseIP(address); ip != nil && strings.Contains(address, ":") { return "[" + address + "]" } return address } type BuildClientURIParams struct { Protocol string RelayServer *Server RelayPort int ClientID string RelayPassword string ShortID string WSPath string SocksUser string Reality *RealityConfig PublicKey string Name string NetworkMode string TLSDomain string SSServerKey string } // BuildClientURI mirrors buildClientUri; ok=false mirrors JS null. func BuildClientURI(p BuildClientURIParams) (string, bool) { label := encodeURIComponent(p.Name) publicAddress, err := serverAddress(p.RelayServer, p.NetworkMode) if err != nil { return "", false } host := uriHost(publicAddress) protocol := p.Protocol if protocol == "hysteria2" { var q xbQuery q.add("sni", p.TLSDomain) q.add("alpn", "h3") return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", encodeURIComponent(p.RelayPassword), host, p.RelayPort, q.String(), label), true } if protocol == "anytls" { var q xbQuery q.add("sni", p.TLSDomain) return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", encodeURIComponent(p.RelayPassword), host, p.RelayPort, q.String(), label), true } if protocol == "vless-reality-vision" || protocol == "vless-reality" { if p.PublicKey == "" { return "", false } serverName := "" if p.Reality != nil { serverName = p.Reality.ServerName } var q xbQuery q.add("encryption", "none") q.add("security", "reality") q.add("sni", serverName) q.add("fp", "chrome") q.add("pbk", p.PublicKey) q.add("sid", p.ShortID) q.add("type", "tcp") q.add("headerType", "none") if protocol == "vless-reality-vision" { q.set("flow", "xtls-rprx-vision") } return fmt.Sprintf("vless://%s@%s:%d?%s#%s", p.ClientID, host, p.RelayPort, q.String(), label), true } if protocol == "vless-ws" || protocol == "vless-ws-tls" { security := "none" if protocol == "vless-ws-tls" { security = "tls" } var q xbQuery q.add("encryption", "none") q.add("security", security) q.add("type", "ws") q.add("path", p.WSPath) if protocol == "vless-ws-tls" { q.set("sni", p.TLSDomain) } return fmt.Sprintf("vless://%s@%s:%d?%s#%s", p.ClientID, host, p.RelayPort, q.String(), label), true } if cipher, ok := ShadowsocksMethods[protocol]; ok { password := EffectiveRelayPassword(protocol, p.RelayPassword) if strings.HasPrefix(cipher.Method, "2022-blake3-") && p.SSServerKey != "" { password = p.SSServerKey + ":" + password } user := base64.RawURLEncoding.EncodeToString([]byte(cipher.Method + ":" + password)) return fmt.Sprintf("ss://%s@%s:%d#%s", user, host, p.RelayPort, label), true } if protocol == "socks5-auth" { return fmt.Sprintf("socks5://%s:%s@%s:%d#%s", encodeURIComponent(p.SocksUser), encodeURIComponent(p.RelayPassword), host, p.RelayPort, label), true } return "", false } // ---------- validation ---------- func profileFor(role, protocol string) *Profile { for i := range ProfileCatalog { if ProfileCatalog[i].Role == role && ProfileCatalog[i].ID == protocol { return &ProfileCatalog[i] } } return nil } func ValidateEntryProtocol(protocol string) *HTTPError { item := profileFor("relay-ingress", protocol) if item == nil || !item.Deployable { return httpErr(400, "Unsupported relay ingress: %s", protocol) } return nil } func ValidateProtocolPair(relayProtocol, exitProtocol string) *HTTPError { if err := ValidateEntryProtocol(relayProtocol); err != nil { return err } exit := profileFor("exit-transport", exitProtocol) if exit == nil || !exit.Deployable { return httpErr(400, "Unsupported exit transport: %s", exitProtocol) } return nil } func IsRealityProtocol(protocol string) bool { return protocol == "vless-reality-vision" || protocol == "vless-reality" } // ---------- Reality keys ---------- func GenerateRealityKeyPair() KeyPair { priv, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { panic(err) } return KeyPair{ PrivateKey: base64.RawURLEncoding.EncodeToString(priv.Bytes()), PublicKey: base64.RawURLEncoding.EncodeToString(priv.PublicKey().Bytes()), } } func realityPublicKeyFromPrivate(privateKey string) (string, error) { raw, err := base64.RawURLEncoding.DecodeString(privateKey) if err != nil { // Tolerate padded input. raw, err = base64.URLEncoding.DecodeString(privateKey) if err != nil { return "", err } } priv, err := ecdh.X25519().NewPrivateKey(raw) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(priv.PublicKey().Bytes()), nil } func RealityKeyIDForNode(node *Node) string { if node.Reality != nil && node.Reality.KeyID != "" { return node.Reality.KeyID } return "reality-" + node.ID } func EnsureRealityKey(d *Data, node *Node) *KeyEntry { if !IsRealityProtocol(node.Protocol) { return nil } EnsureNodeSecrets(node) keyID := RealityKeyIDForNode(node) if d.KeyStore == nil { d.KeyStore = map[string]*KeyEntry{} } entry := d.KeyStore[keyID] if entry == nil || entry.PrivateKey == "" || entry.PublicKey == "" { pair := GenerateRealityKeyPair() entry = &KeyEntry{PrivateKey: pair.PrivateKey, PublicKey: pair.PublicKey, UpdatedAt: NowISO()} d.KeyStore[keyID] = entry } return entry } func KeyMaterialForNode(d *Data, node *Node) map[string]KeyPair { entry := EnsureRealityKey(d, node) if entry == nil { return nil } return map[string]KeyPair{ RealityKeyIDForNode(node): {PrivateKey: entry.PrivateKey, PublicKey: entry.PublicKey}, } } func DropRealityKey(d *Data, node *Node) { if d.KeyStore != nil { delete(d.KeyStore, RealityKeyIDForNode(node)) } }