Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sub/subService.go1262
-rw-r--r--web/assets/js/model/inbound.js54
-rw-r--r--web/assets/js/model/outbound.js11
-rw-r--r--web/html/settings/panel/subscription/subpage.html19
4 files changed, 731 insertions, 615 deletions
diff --git a/sub/subService.go b/sub/subService.go
index 272bf9d5..67b931ce 100644
--- a/sub/subService.go
+++ b/sub/subService.go
@@ -3,8 +3,10 @@ package sub
import (
"encoding/base64"
"fmt"
+ "maps"
"net"
"net/url"
+ "slices"
"strings"
"time"
@@ -179,186 +181,54 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
return ""
}
+// Protocol link generators are intentionally ordered as:
+// vmess -> vless -> trojan -> shadowsocks -> hysteria.
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.VMESS {
return ""
}
- var address string
- if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
- address = s.address
- } else {
- address = inbound.Listen
- }
+ address := s.resolveInboundAddress(inbound)
obj := map[string]any{
"v": "2",
"add": address,
"port": inbound.Port,
"type": "none",
}
- var stream map[string]any
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ stream := unmarshalStreamSettings(inbound.StreamSettings)
network, _ := stream["network"].(string)
- obj["net"] = network
- switch network {
- case "tcp":
- tcp, _ := stream["tcpSettings"].(map[string]any)
- header, _ := tcp["header"].(map[string]any)
- typeStr, _ := header["type"].(string)
- obj["type"] = typeStr
- if typeStr == "http" {
- request := header["request"].(map[string]any)
- requestPath, _ := request["path"].([]any)
- obj["path"] = requestPath[0].(string)
- headers, _ := request["headers"].(map[string]any)
- obj["host"] = searchHost(headers)
- }
- case "kcp":
- kcp, _ := stream["kcpSettings"].(map[string]any)
- header, _ := kcp["header"].(map[string]any)
- obj["type"], _ = header["type"].(string)
- obj["path"], _ = kcp["seed"].(string)
- case "ws":
- ws, _ := stream["wsSettings"].(map[string]any)
- obj["path"] = ws["path"].(string)
- if host, ok := ws["host"].(string); ok && len(host) > 0 {
- obj["host"] = host
- } else {
- headers, _ := ws["headers"].(map[string]any)
- obj["host"] = searchHost(headers)
- }
- case "grpc":
- grpc, _ := stream["grpcSettings"].(map[string]any)
- obj["path"] = grpc["serviceName"].(string)
- obj["authority"] = grpc["authority"].(string)
- if grpc["multiMode"].(bool) {
- obj["type"] = "multi"
- }
- case "httpupgrade":
- httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
- obj["path"] = httpupgrade["path"].(string)
- if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
- obj["host"] = host
- } else {
- headers, _ := httpupgrade["headers"].(map[string]any)
- obj["host"] = searchHost(headers)
- }
- case "xhttp":
- xhttp, _ := stream["xhttpSettings"].(map[string]any)
- obj["path"] = xhttp["path"].(string)
- if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
- obj["host"] = host
- } else {
- headers, _ := xhttp["headers"].(map[string]any)
- obj["host"] = searchHost(headers)
- }
- obj["mode"], _ = xhttp["mode"].(string)
- // VMess base64 JSON supports arbitrary keys; copy the padding
- // settings through so clients can match the server's xhttp
- // xPaddingBytes range and, when the admin opted into obfs
- // mode, the custom key / header / placement / method.
- if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
- obj["x_padding_bytes"] = xpb
- }
- if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
- obj["xPaddingObfsMode"] = true
- for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
- if v, ok := xhttp[field].(string); ok && len(v) > 0 {
- obj[field] = v
- }
- }
- }
+ applyVmessNetworkParams(stream, network, obj)
+ if finalmask, ok := stream["finalmask"].(map[string]any); ok {
+ applyFinalMaskObj(finalmask, obj)
}
security, _ := stream["security"].(string)
obj["tls"] = security
if security == "tls" {
- tlsSetting, _ := stream["tlsSettings"].(map[string]any)
- alpns, _ := tlsSetting["alpn"].([]any)
- if len(alpns) > 0 {
- var alpn []string
- for _, a := range alpns {
- alpn = append(alpn, a.(string))
- }
- obj["alpn"] = strings.Join(alpn, ",")
- }
- if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
- obj["sni"], _ = sniValue.(string)
- }
-
- tlsSettings, _ := searchKey(tlsSetting, "settings")
- if tlsSetting != nil {
- if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
- obj["fp"], _ = fpValue.(string)
- }
- }
+ applyVmessTLSParams(stream, obj)
}
clients, _ := s.inboundService.GetClients(inbound)
- clientIndex := -1
- for i, client := range clients {
- if client.Email == email {
- clientIndex = i
- break
- }
- }
+ clientIndex := findClientIndex(clients, email)
obj["id"] = clients[clientIndex].ID
obj["scy"] = clients[clientIndex].Security
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
- links := ""
- for index, externalProxy := range externalProxies {
- ep, _ := externalProxy.(map[string]any)
- newSecurity, _ := ep["forceTls"].(string)
- newObj := map[string]any{}
- for key, value := range obj {
- if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp")) {
- newObj[key] = value
- }
- }
- newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
- newObj["add"] = ep["dest"].(string)
- newObj["port"] = int(ep["port"].(float64))
-
- if newSecurity != "same" {
- newObj["tls"] = newSecurity
- }
- if index > 0 {
- links += "\n"
- }
- jsonStr, _ := json.MarshalIndent(newObj, "", " ")
- links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
- }
- return links
+ return s.buildVmessExternalProxyLinks(externalProxies, obj, inbound, email)
}
obj["ps"] = s.genRemark(inbound, email, "")
-
- jsonStr, _ := json.MarshalIndent(obj, "", " ")
- return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
+ return buildVmessLink(obj)
}
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
- var address string
- if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
- address = s.address
- } else {
- address = inbound.Listen
- }
-
if inbound.Protocol != model.VLESS {
return ""
}
- var stream map[string]any
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ address := s.resolveInboundAddress(inbound)
+ stream := unmarshalStreamSettings(inbound.StreamSettings)
clients, _ := s.inboundService.GetClients(inbound)
- clientIndex := -1
- for i, client := range clients {
- if client.Email == email {
- clientIndex = i
- break
- }
- }
+ clientIndex := findClientIndex(clients, email)
uuid := clients[clientIndex].ID
port := inbound.Port
streamNetwork := stream["network"].(string)
@@ -372,481 +242,122 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["encryption"] = encryption
}
- switch streamNetwork {
- case "tcp":
- tcp, _ := stream["tcpSettings"].(map[string]any)
- header, _ := tcp["header"].(map[string]any)
- typeStr, _ := header["type"].(string)
- if typeStr == "http" {
- request := header["request"].(map[string]any)
- requestPath, _ := request["path"].([]any)
- params["path"] = requestPath[0].(string)
- headers, _ := request["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- params["headerType"] = "http"
- }
- case "kcp":
- kcp, _ := stream["kcpSettings"].(map[string]any)
- header, _ := kcp["header"].(map[string]any)
- params["headerType"] = header["type"].(string)
- params["seed"] = kcp["seed"].(string)
- case "ws":
- ws, _ := stream["wsSettings"].(map[string]any)
- params["path"] = ws["path"].(string)
- if host, ok := ws["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := ws["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "grpc":
- grpc, _ := stream["grpcSettings"].(map[string]any)
- params["serviceName"] = grpc["serviceName"].(string)
- params["authority"], _ = grpc["authority"].(string)
- if grpc["multiMode"].(bool) {
- params["mode"] = "multi"
- }
- case "httpupgrade":
- httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
- params["path"] = httpupgrade["path"].(string)
- if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := httpupgrade["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "xhttp":
- xhttp, _ := stream["xhttpSettings"].(map[string]any)
- params["path"] = xhttp["path"].(string)
- if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := xhttp["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- params["mode"], _ = xhttp["mode"].(string)
- applyXhttpPaddingParams(xhttp, params)
+ applyShareNetworkParams(stream, streamNetwork, params)
+ if finalmask, ok := stream["finalmask"].(map[string]any); ok {
+ applyFinalMaskParams(finalmask, params)
}
security, _ := stream["security"].(string)
- if security == "tls" {
- params["security"] = "tls"
- tlsSetting, _ := stream["tlsSettings"].(map[string]any)
- alpns, _ := tlsSetting["alpn"].([]any)
- var alpn []string
- for _, a := range alpns {
- alpn = append(alpn, a.(string))
- }
- if len(alpn) > 0 {
- params["alpn"] = strings.Join(alpn, ",")
- }
- if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
- params["sni"], _ = sniValue.(string)
- }
-
- tlsSettings, _ := searchKey(tlsSetting, "settings")
- if tlsSetting != nil {
- if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
- params["fp"], _ = fpValue.(string)
- }
- }
-
+ switch security {
+ case "tls":
+ applyShareTLSParams(stream, params)
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
- }
-
- if security == "reality" {
- params["security"] = "reality"
- realitySetting, _ := stream["realitySettings"].(map[string]any)
- realitySettings, _ := searchKey(realitySetting, "settings")
- if realitySetting != nil {
- if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
- sNames, _ := sniValue.([]any)
- params["sni"] = sNames[random.Num(len(sNames))].(string)
- }
- if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
- params["pbk"], _ = pbkValue.(string)
- }
- if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
- shortIds, _ := sidValue.([]any)
- params["sid"] = shortIds[random.Num(len(shortIds))].(string)
- }
- if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
- if fp, ok := fpValue.(string); ok && len(fp) > 0 {
- params["fp"] = fp
- }
- }
- if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
- if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
- params["pqv"] = pqv
- }
- }
- params["spx"] = "/" + random.Seq(15)
- }
-
+ case "reality":
+ applyShareRealityParams(stream, params)
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
- }
-
- if security != "tls" && security != "reality" {
+ default:
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
- links := make([]string, 0, len(externalProxies))
- for _, externalProxy := range externalProxies {
- ep, _ := externalProxy.(map[string]any)
- newSecurity, _ := ep["forceTls"].(string)
- dest, _ := ep["dest"].(string)
- port := int(ep["port"].(float64))
- link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
-
- if newSecurity != "same" {
- params["security"] = newSecurity
- } else {
- params["security"] = security
- }
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp")) {
- q.Add(k, v)
- }
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
-
- links = append(links, url.String())
- }
- return strings.Join(links, "\n")
+ return s.buildExternalProxyURLLinks(
+ externalProxies,
+ params,
+ security,
+ func(dest string, port int) string {
+ return fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
+ },
+ func(ep map[string]any) string {
+ return s.genRemark(inbound, email, ep["remark"].(string))
+ },
+ )
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- q.Add(k, v)
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, "")
- return url.String()
+ return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
- var address string
- if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
- address = s.address
- } else {
- address = inbound.Listen
- }
if inbound.Protocol != model.Trojan {
return ""
}
- var stream map[string]any
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ address := s.resolveInboundAddress(inbound)
+ stream := unmarshalStreamSettings(inbound.StreamSettings)
clients, _ := s.inboundService.GetClients(inbound)
- clientIndex := -1
- for i, client := range clients {
- if client.Email == email {
- clientIndex = i
- break
- }
- }
+ clientIndex := findClientIndex(clients, email)
password := clients[clientIndex].Password
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
params["type"] = streamNetwork
- switch streamNetwork {
- case "tcp":
- tcp, _ := stream["tcpSettings"].(map[string]any)
- header, _ := tcp["header"].(map[string]any)
- typeStr, _ := header["type"].(string)
- if typeStr == "http" {
- request := header["request"].(map[string]any)
- requestPath, _ := request["path"].([]any)
- params["path"] = requestPath[0].(string)
- headers, _ := request["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- params["headerType"] = "http"
- }
- case "kcp":
- kcp, _ := stream["kcpSettings"].(map[string]any)
- header, _ := kcp["header"].(map[string]any)
- params["headerType"] = header["type"].(string)
- params["seed"] = kcp["seed"].(string)
- case "ws":
- ws, _ := stream["wsSettings"].(map[string]any)
- params["path"] = ws["path"].(string)
- if host, ok := ws["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := ws["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "grpc":
- grpc, _ := stream["grpcSettings"].(map[string]any)
- params["serviceName"] = grpc["serviceName"].(string)
- params["authority"], _ = grpc["authority"].(string)
- if grpc["multiMode"].(bool) {
- params["mode"] = "multi"
- }
- case "httpupgrade":
- httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
- params["path"] = httpupgrade["path"].(string)
- if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := httpupgrade["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "xhttp":
- xhttp, _ := stream["xhttpSettings"].(map[string]any)
- params["path"] = xhttp["path"].(string)
- if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := xhttp["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- params["mode"], _ = xhttp["mode"].(string)
- applyXhttpPaddingParams(xhttp, params)
+ applyShareNetworkParams(stream, streamNetwork, params)
+ if finalmask, ok := stream["finalmask"].(map[string]any); ok {
+ applyFinalMaskParams(finalmask, params)
}
security, _ := stream["security"].(string)
- if security == "tls" {
- params["security"] = "tls"
- tlsSetting, _ := stream["tlsSettings"].(map[string]any)
- alpns, _ := tlsSetting["alpn"].([]any)
- var alpn []string
- for _, a := range alpns {
- alpn = append(alpn, a.(string))
- }
- if len(alpn) > 0 {
- params["alpn"] = strings.Join(alpn, ",")
- }
- if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
- params["sni"], _ = sniValue.(string)
- }
-
- tlsSettings, _ := searchKey(tlsSetting, "settings")
- if tlsSetting != nil {
- if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
- params["fp"], _ = fpValue.(string)
- }
- }
- }
-
- if security == "reality" {
- params["security"] = "reality"
- realitySetting, _ := stream["realitySettings"].(map[string]any)
- realitySettings, _ := searchKey(realitySetting, "settings")
- if realitySetting != nil {
- if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
- sNames, _ := sniValue.([]any)
- params["sni"] = sNames[random.Num(len(sNames))].(string)
- }
- if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
- params["pbk"], _ = pbkValue.(string)
- }
- if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
- shortIds, _ := sidValue.([]any)
- params["sid"] = shortIds[random.Num(len(shortIds))].(string)
- }
- if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
- if fp, ok := fpValue.(string); ok && len(fp) > 0 {
- params["fp"] = fp
- }
- }
- if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
- if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
- params["pqv"] = pqv
- }
- }
- params["spx"] = "/" + random.Seq(15)
- }
-
+ switch security {
+ case "tls":
+ applyShareTLSParams(stream, params)
+ case "reality":
+ applyShareRealityParams(stream, params)
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
- }
-
- if security != "tls" && security != "reality" {
+ default:
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
- links := ""
- for index, externalProxy := range externalProxies {
- ep, _ := externalProxy.(map[string]any)
- newSecurity, _ := ep["forceTls"].(string)
- dest, _ := ep["dest"].(string)
- port := int(ep["port"].(float64))
- link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
-
- if newSecurity != "same" {
- params["security"] = newSecurity
- } else {
- params["security"] = security
- }
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp")) {
- q.Add(k, v)
- }
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
-
- if index > 0 {
- links += "\n"
- }
- links += url.String()
- }
- return links
+ return s.buildExternalProxyURLLinks(
+ externalProxies,
+ params,
+ security,
+ func(dest string, port int) string {
+ return fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
+ },
+ func(ep map[string]any) string {
+ return s.genRemark(inbound, email, ep["remark"].(string))
+ },
+ )
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
-
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- q.Add(k, v)
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, "")
- return url.String()
+ return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
- var address string
- if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
- address = s.address
- } else {
- address = inbound.Listen
- }
if inbound.Protocol != model.Shadowsocks {
return ""
}
- var stream map[string]any
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ address := s.resolveInboundAddress(inbound)
+ stream := unmarshalStreamSettings(inbound.StreamSettings)
clients, _ := s.inboundService.GetClients(inbound)
var settings map[string]any
json.Unmarshal([]byte(inbound.Settings), &settings)
inboundPassword := settings["password"].(string)
method := settings["method"].(string)
- clientIndex := -1
- for i, client := range clients {
- if client.Email == email {
- clientIndex = i
- break
- }
- }
+ clientIndex := findClientIndex(clients, email)
streamNetwork := stream["network"].(string)
params := make(map[string]string)
params["type"] = streamNetwork
- switch streamNetwork {
- case "tcp":
- tcp, _ := stream["tcpSettings"].(map[string]any)
- header, _ := tcp["header"].(map[string]any)
- typeStr, _ := header["type"].(string)
- if typeStr == "http" {
- request := header["request"].(map[string]any)
- requestPath, _ := request["path"].([]any)
- params["path"] = requestPath[0].(string)
- headers, _ := request["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- params["headerType"] = "http"
- }
- case "kcp":
- kcp, _ := stream["kcpSettings"].(map[string]any)
- header, _ := kcp["header"].(map[string]any)
- params["headerType"] = header["type"].(string)
- params["seed"] = kcp["seed"].(string)
- case "ws":
- ws, _ := stream["wsSettings"].(map[string]any)
- params["path"] = ws["path"].(string)
- if host, ok := ws["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := ws["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "grpc":
- grpc, _ := stream["grpcSettings"].(map[string]any)
- params["serviceName"] = grpc["serviceName"].(string)
- params["authority"], _ = grpc["authority"].(string)
- if grpc["multiMode"].(bool) {
- params["mode"] = "multi"
- }
- case "httpupgrade":
- httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
- params["path"] = httpupgrade["path"].(string)
- if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := httpupgrade["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- case "xhttp":
- xhttp, _ := stream["xhttpSettings"].(map[string]any)
- params["path"] = xhttp["path"].(string)
- if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
- params["host"] = host
- } else {
- headers, _ := xhttp["headers"].(map[string]any)
- params["host"] = searchHost(headers)
- }
- params["mode"], _ = xhttp["mode"].(string)
- applyXhttpPaddingParams(xhttp, params)
+ applyShareNetworkParams(stream, streamNetwork, params)
+ if finalmask, ok := stream["finalmask"].(map[string]any); ok {
+ applyFinalMaskParams(finalmask, params)
}
security, _ := stream["security"].(string)
if security == "tls" {
- params["security"] = "tls"
- tlsSetting, _ := stream["tlsSettings"].(map[string]any)
- alpns, _ := tlsSetting["alpn"].([]any)
- var alpn []string
- for _, a := range alpns {
- alpn = append(alpn, a.(string))
- }
- if len(alpn) > 0 {
- params["alpn"] = strings.Join(alpn, ",")
- }
- if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
- params["sni"], _ = sniValue.(string)
- }
-
- tlsSettings, _ := searchKey(tlsSetting, "settings")
- if tlsSetting != nil {
- if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
- params["fp"], _ = fpValue.(string)
- }
- }
+ applyShareTLSParams(stream, params)
}
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
@@ -857,61 +368,30 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
- links := ""
- for index, externalProxy := range externalProxies {
- ep, _ := externalProxy.(map[string]any)
- newSecurity, _ := ep["forceTls"].(string)
- dest, _ := ep["dest"].(string)
- port := int(ep["port"].(float64))
- link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
-
- if newSecurity != "same" {
- params["security"] = newSecurity
- } else {
- params["security"] = security
- }
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp")) {
- q.Add(k, v)
- }
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
-
- if index > 0 {
- links += "\n"
- }
- links += url.String()
- }
- return links
+ proxyParams := cloneStringMap(params)
+ proxyParams["security"] = security
+ return s.buildExternalProxyURLLinks(
+ externalProxies,
+ proxyParams,
+ security,
+ func(dest string, port int) string {
+ return fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
+ },
+ func(ep map[string]any) string {
+ return s.genRemark(inbound, email, ep["remark"].(string))
+ },
+ )
}
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
- url, _ := url.Parse(link)
- q := url.Query()
-
- for k, v := range params {
- q.Add(k, v)
- }
-
- // Set the new query values on the URL
- url.RawQuery = q.Encode()
-
- url.Fragment = s.genRemark(inbound, email, "")
- return url.String()
+ return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
}
func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
if !model.IsHysteria(inbound.Protocol) {
return ""
}
- var stream map[string]interface{}
+ var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
@@ -925,8 +405,8 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
params := make(map[string]string)
params["security"] = "tls"
- tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
- alpns, _ := tlsSetting["alpn"].([]interface{})
+ tlsSetting, _ := stream["tlsSettings"].(map[string]any)
+ alpns, _ := tlsSetting["alpn"].([]any)
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
@@ -953,14 +433,15 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
// salamander obfs (Hysteria2). The panel-side link generator already
// emits these; keep the subscription output in sync so a client has
// the obfs password to match the server.
- if finalmask, ok := stream["finalmask"].(map[string]interface{}); ok {
- if udpMasks, ok := finalmask["udp"].([]interface{}); ok {
+ if finalmask, ok := stream["finalmask"].(map[string]any); ok {
+ applyFinalMaskParams(finalmask, params)
+ if udpMasks, ok := finalmask["udp"].([]any); ok {
for _, m := range udpMasks {
- mask, _ := m.(map[string]interface{})
+ mask, _ := m.(map[string]any)
if mask == nil || mask["type"] != "salamander" {
continue
}
- settings, _ := mask["settings"].(map[string]interface{})
+ settings, _ := mask["settings"].(map[string]any)
if pw, ok := settings["password"].(string); ok && pw != "" {
params["obfs"] = "salamander"
params["obfs-password"] = pw
@@ -970,7 +451,7 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
}
}
- var settings map[string]interface{}
+ var settings map[string]any
json.Unmarshal([]byte(inbound.Settings), &settings)
version, _ := settings["version"].(float64)
protocol := "hysteria2"
@@ -983,11 +464,11 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
// server's own IP/port even when the admin configured an alternate
// endpoint (e.g. a CDN hostname + port that forwards to the node).
// Matches the behaviour of genVlessLink / genTrojanLink / ….
- externalProxies, _ := stream["externalProxy"].([]interface{})
+ externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
links := make([]string, 0, len(externalProxies))
for _, externalProxy := range externalProxies {
- ep, ok := externalProxy.(map[string]interface{})
+ ep, ok := externalProxy.(map[string]any)
if !ok {
continue
}
@@ -1023,6 +504,319 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
return url.String()
}
+func (s *SubService) resolveInboundAddress(inbound *model.Inbound) string {
+ if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
+ return s.address
+ }
+ return inbound.Listen
+}
+
+func findClientIndex(clients []model.Client, email string) int {
+ for i, client := range clients {
+ if client.Email == email {
+ return i
+ }
+ }
+ return -1
+}
+
+func unmarshalStreamSettings(streamSettings string) map[string]any {
+ var stream map[string]any
+ json.Unmarshal([]byte(streamSettings), &stream)
+ return stream
+}
+
+func applyPathAndHostParams(settings map[string]any, params map[string]string) {
+ params["path"] = settings["path"].(string)
+ if host, ok := settings["host"].(string); ok && len(host) > 0 {
+ params["host"] = host
+ } else {
+ headers, _ := settings["headers"].(map[string]any)
+ params["host"] = searchHost(headers)
+ }
+}
+
+func applyPathAndHostObj(settings map[string]any, obj map[string]any) {
+ obj["path"] = settings["path"].(string)
+ if host, ok := settings["host"].(string); ok && len(host) > 0 {
+ obj["host"] = host
+ } else {
+ headers, _ := settings["headers"].(map[string]any)
+ obj["host"] = searchHost(headers)
+ }
+}
+
+func applyShareNetworkParams(stream map[string]any, streamNetwork string, params map[string]string) {
+ switch streamNetwork {
+ case "tcp":
+ tcp, _ := stream["tcpSettings"].(map[string]any)
+ header, _ := tcp["header"].(map[string]any)
+ typeStr, _ := header["type"].(string)
+ if typeStr == "http" {
+ request := header["request"].(map[string]any)
+ requestPath, _ := request["path"].([]any)
+ params["path"] = requestPath[0].(string)
+ headers, _ := request["headers"].(map[string]any)
+ params["host"] = searchHost(headers)
+ params["headerType"] = "http"
+ }
+ case "kcp":
+ applyKcpShareParams(stream, params)
+ case "ws":
+ ws, _ := stream["wsSettings"].(map[string]any)
+ applyPathAndHostParams(ws, params)
+ case "grpc":
+ grpc, _ := stream["grpcSettings"].(map[string]any)
+ params["serviceName"] = grpc["serviceName"].(string)
+ params["authority"], _ = grpc["authority"].(string)
+ if grpc["multiMode"].(bool) {
+ params["mode"] = "multi"
+ }
+ case "httpupgrade":
+ httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
+ applyPathAndHostParams(httpupgrade, params)
+ case "xhttp":
+ xhttp, _ := stream["xhttpSettings"].(map[string]any)
+ applyPathAndHostParams(xhttp, params)
+ params["mode"], _ = xhttp["mode"].(string)
+ applyXhttpPaddingParams(xhttp, params)
+ }
+}
+
+func applyXhttpPaddingObj(xhttp map[string]any, obj map[string]any) {
+ // VMess base64 JSON supports arbitrary keys; copy the padding
+ // settings through so clients can match the server's xhttp
+ // xPaddingBytes range and, when the admin opted into obfs
+ // mode, the custom key / header / placement / method.
+ if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
+ obj["x_padding_bytes"] = xpb
+ }
+ if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
+ obj["xPaddingObfsMode"] = true
+ for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
+ if v, ok := xhttp[field].(string); ok && len(v) > 0 {
+ obj[field] = v
+ }
+ }
+ }
+}
+
+func applyVmessNetworkParams(stream map[string]any, network string, obj map[string]any) {
+ obj["net"] = network
+ switch network {
+ case "tcp":
+ tcp, _ := stream["tcpSettings"].(map[string]any)
+ header, _ := tcp["header"].(map[string]any)
+ typeStr, _ := header["type"].(string)
+ obj["type"] = typeStr
+ if typeStr == "http" {
+ request := header["request"].(map[string]any)
+ requestPath, _ := request["path"].([]any)
+ obj["path"] = requestPath[0].(string)
+ headers, _ := request["headers"].(map[string]any)
+ obj["host"] = searchHost(headers)
+ }
+ case "kcp":
+ applyKcpShareObj(stream, obj)
+ case "ws":
+ ws, _ := stream["wsSettings"].(map[string]any)
+ applyPathAndHostObj(ws, obj)
+ case "grpc":
+ grpc, _ := stream["grpcSettings"].(map[string]any)
+ obj["path"] = grpc["serviceName"].(string)
+ obj["authority"] = grpc["authority"].(string)
+ if grpc["multiMode"].(bool) {
+ obj["type"] = "multi"
+ }
+ case "httpupgrade":
+ httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
+ applyPathAndHostObj(httpupgrade, obj)
+ case "xhttp":
+ xhttp, _ := stream["xhttpSettings"].(map[string]any)
+ applyPathAndHostObj(xhttp, obj)
+ obj["mode"], _ = xhttp["mode"].(string)
+ applyXhttpPaddingObj(xhttp, obj)
+ }
+}
+
+func applyShareTLSParams(stream map[string]any, params map[string]string) {
+ params["security"] = "tls"
+ tlsSetting, _ := stream["tlsSettings"].(map[string]any)
+ alpns, _ := tlsSetting["alpn"].([]any)
+ var alpn []string
+ for _, a := range alpns {
+ alpn = append(alpn, a.(string))
+ }
+ if len(alpn) > 0 {
+ params["alpn"] = strings.Join(alpn, ",")
+ }
+ if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
+ params["sni"], _ = sniValue.(string)
+ }
+
+ tlsSettings, _ := searchKey(tlsSetting, "settings")
+ if tlsSetting != nil {
+ if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
+ params["fp"], _ = fpValue.(string)
+ }
+ }
+}
+
+func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
+ tlsSetting, _ := stream["tlsSettings"].(map[string]any)
+ alpns, _ := tlsSetting["alpn"].([]any)
+ if len(alpns) > 0 {
+ var alpn []string
+ for _, a := range alpns {
+ alpn = append(alpn, a.(string))
+ }
+ obj["alpn"] = strings.Join(alpn, ",")
+ }
+ if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
+ obj["sni"], _ = sniValue.(string)
+ }
+
+ tlsSettings, _ := searchKey(tlsSetting, "settings")
+ if tlsSetting != nil {
+ if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
+ obj["fp"], _ = fpValue.(string)
+ }
+ }
+}
+
+func applyShareRealityParams(stream map[string]any, params map[string]string) {
+ params["security"] = "reality"
+ realitySetting, _ := stream["realitySettings"].(map[string]any)