Files

296 lines
7.0 KiB
Go

package proxy
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
)
func buildOutboundVmess(node string) (map[string]interface{}, error) {
raw := strings.TrimPrefix(node, "vmess://")
decoded, err := decodeBase64String(strings.TrimSpace(raw))
if err != nil {
return nil, fmt.Errorf("vmess 解析失败: %v", err)
}
var v struct {
Add string `json:"add"`
Port string `json:"port"`
ID string `json:"id"`
Net string `json:"net"`
Type string `json:"type"`
Host string `json:"host"`
Path string `json:"path"`
TLS string `json:"tls"`
Sni string `json:"sni"`
Alpn string `json:"alpn"`
}
if err := json.Unmarshal(decoded, &v); err != nil {
return nil, fmt.Errorf("vmess 配置解析失败: %v", err)
}
p, _ := strconv.Atoi(v.Port)
out := map[string]interface{}{
"protocol": "vmess",
"tag": "proxy-out",
"settings": map[string]interface{}{
"vnext": []interface{}{
map[string]interface{}{
"address": v.Add,
"port": p,
"users": []interface{}{
map[string]interface{}{
"id": v.ID,
"security": "auto",
},
},
},
},
},
}
stream := map[string]interface{}{}
if v.TLS == "tls" {
stream["security"] = "tls"
if v.Sni != "" {
stream["tlsSettings"] = map[string]interface{}{"serverName": v.Sni}
}
}
if v.Net == "ws" {
stream["network"] = "ws"
ws := map[string]interface{}{}
if v.Path != "" {
ws["path"] = v.Path
}
if v.Host != "" {
ws["headers"] = map[string]interface{}{"Host": v.Host}
}
if len(ws) > 0 {
stream["wsSettings"] = ws
}
}
if len(stream) > 0 {
out["streamSettings"] = stream
}
return out, nil
}
func buildOutboundVless(node string) (map[string]interface{}, error) {
u, err := url.Parse(node)
if err != nil {
return nil, fmt.Errorf("vless 解析失败: %v", err)
}
host := u.Hostname()
portStr := u.Port()
p, _ := strconv.Atoi(portStr)
id := u.User.Username()
q := u.Query()
flow := q.Get("flow")
sec := strings.ToLower(q.Get("security"))
sni := q.Get("sni")
fingerprint := firstNonEmptyQueryValue(q, "fp", "client-fingerprint", "fingerprint")
insecure := queryBool(q, "insecure", "allowInsecure")
out := map[string]interface{}{
"protocol": "vless",
"tag": "proxy-out",
"settings": map[string]interface{}{
"vnext": []interface{}{
map[string]interface{}{
"address": host,
"port": p,
"users": []interface{}{
map[string]interface{}{
"id": id,
"flow": flow,
"encryption": "none",
},
},
},
},
},
}
stream := map[string]interface{}{}
if sec == "tls" || sec == "reality" {
stream["security"] = "tls"
tlsSettings := map[string]interface{}{}
if sni != "" {
tlsSettings["serverName"] = sni
}
if fingerprint != "" {
tlsSettings["fingerprint"] = fingerprint
}
if insecure {
tlsSettings[xrayTLSInsecurePinKey] = true
}
if len(tlsSettings) > 0 {
stream["tlsSettings"] = tlsSettings
}
}
network := q.Get("type")
if network == "" {
network = q.Get("network")
}
if network == "ws" {
stream["network"] = "ws"
ws := map[string]interface{}{}
if pth := q.Get("path"); pth != "" {
ws["path"] = pth
}
hostH := q.Get("host")
if hostH == "" {
hostH = u.Hostname()
}
if hostH != "" {
ws["headers"] = map[string]interface{}{"Host": hostH}
}
stream["wsSettings"] = ws
}
if len(stream) > 0 {
out["streamSettings"] = stream
}
return out, nil
}
func buildOutboundHysteria2(node string) (map[string]interface{}, error) {
return nil, fmt.Errorf("Xray 不支持 hysteria2 协议,请使用 vless/vmess/socks5/http 格式的代理")
}
// buildOutboundTrojan 解析 trojan:// URI 格式
func buildOutboundTrojan(node string) (map[string]interface{}, error) {
u, err := url.Parse(node)
if err != nil {
return nil, fmt.Errorf("trojan 解析失败: %v", err)
}
host := u.Hostname()
portStr := u.Port()
p, _ := strconv.Atoi(portStr)
password := u.User.Username()
q := u.Query()
sni := q.Get("sni")
if sni == "" {
sni = q.Get("peer")
}
fingerprint := firstNonEmptyQueryValue(q, "fp", "client-fingerprint", "fingerprint")
insecure := queryBool(q, "insecure", "allowInsecure")
network := q.Get("type")
out := map[string]interface{}{
"protocol": "trojan",
"tag": "proxy-out",
"settings": map[string]interface{}{
"servers": []interface{}{
map[string]interface{}{
"address": host,
"port": p,
"password": password,
},
},
},
}
stream := map[string]interface{}{
"security": "tls",
"tlsSettings": map[string]interface{}{},
}
tlsSettings := stream["tlsSettings"].(map[string]interface{})
if sni != "" {
tlsSettings["serverName"] = sni
}
if fingerprint != "" {
tlsSettings["fingerprint"] = fingerprint
}
if insecure {
tlsSettings[xrayTLSInsecurePinKey] = true
}
if network == "ws" {
stream["network"] = "ws"
ws := map[string]interface{}{}
if pth := q.Get("path"); pth != "" {
ws["path"] = pth
}
if h := q.Get("host"); h != "" {
ws["headers"] = map[string]interface{}{"Host": h}
}
stream["wsSettings"] = ws
}
out["streamSettings"] = stream
return out, nil
}
// buildOutboundSS 解析 ss:// URI 格式
// 支持两种格式:
// 1. ss://BASE64(method:password)@host:port
// 2. ss://BASE64(method:password@host:port)
func buildOutboundSS(node string) (map[string]interface{}, error) {
raw := strings.TrimPrefix(node, "ss://")
if idx := strings.Index(raw, "#"); idx >= 0 {
raw = raw[:idx]
}
raw = strings.TrimSpace(raw)
var host, method, password string
var port int
if strings.Contains(raw, "@") {
u, err := url.Parse("ss://" + raw)
if err != nil {
return nil, fmt.Errorf("ss 解析失败: %v", err)
}
host = u.Hostname()
port, _ = strconv.Atoi(u.Port())
userInfo := u.User.String()
if decoded, err := decodeBase64String(userInfo); err == nil {
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) == 2 {
method = parts[0]
password = parts[1]
}
} else {
parts := strings.SplitN(userInfo, ":", 2)
if len(parts) == 2 {
method = parts[0]
password = parts[1]
}
}
} else {
decoded, err := decodeBase64String(raw)
if err != nil {
return nil, fmt.Errorf("ss base64 解析失败: %v", err)
}
s := string(decoded)
atIdx := strings.LastIndex(s, "@")
if atIdx < 0 {
return nil, fmt.Errorf("ss 格式错误")
}
userPart := s[:atIdx]
hostPart := s[atIdx+1:]
parts := strings.SplitN(userPart, ":", 2)
if len(parts) == 2 {
method = parts[0]
password = parts[1]
}
parsedHost, parsedPort, splitErr := splitHostPortLenient(hostPart)
if splitErr == nil {
host = parsedHost
port = parsedPort
}
}
if host == "" || port == 0 || method == "" {
return nil, fmt.Errorf("ss 节点信息不完整")
}
return map[string]interface{}{
"protocol": "shadowsocks",
"tag": "proxy-out",
"settings": map[string]interface{}{
"servers": []interface{}{
map[string]interface{}{
"address": host,
"port": port,
"method": method,
"password": password,
},
},
},
}, nil
}