Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
511 lines
12 KiB
Go
511 lines
12 KiB
Go
package config
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// ProxyConfig 表示代理节点的配置
|
||
// 该结构体包含各种代理协议的通用和特定配置
|
||
type ProxyConfig struct {
|
||
// 基本信息
|
||
Name string `yaml:"name" json:"name"`
|
||
Type string `yaml:"type" json:"type"` // ss, ssr, vmess, trojan, http, socks5
|
||
|
||
// 服务器信息
|
||
Server string `yaml:"server" json:"server"`
|
||
Port int `yaml:"port" json:"port"`
|
||
Password string `yaml:"password" json:"password,omitempty"`
|
||
|
||
// 加密和协议
|
||
Method string `yaml:"method" json:"method,omitempty"` // ss, ssr
|
||
Protocol string `yaml:"protocol" json:"protocol,omitempty"` // ssr
|
||
Obfs string `yaml:"obfs" json:"obfs,omitempty"` // ssr
|
||
ObfsParam string `yaml:"obfs_param" json:"obfs_param,omitempty"` // ssr
|
||
SSRProtocol string `yaml:"ssr_protocol" json:"ssr_protocol,omitempty"` // ssr (兼容性字段)
|
||
|
||
// VMess特定配置
|
||
VMessConfig *VMessConfig `yaml:"vmess_config,omitempty" json:"vmess_config,omitempty"`
|
||
|
||
// TLS配置
|
||
TLS bool `yaml:"tls" json:"tls"`
|
||
SNI string `yaml:"sni" json:"sni,omitempty"`
|
||
Alpn string `yaml:"alpn" json:"alpn,omitempty"`
|
||
SkipVerify bool `yaml:"skip_verify" json:"skip_verify"`
|
||
|
||
// WebSocket配置
|
||
WSPath string `yaml:"ws_path" json:"ws_path,omitempty"`
|
||
WSHeaders map[string]string `yaml:"ws_headers" json:"ws_headers,omitempty"`
|
||
|
||
// HTTP/2配置
|
||
H2Host string `yaml:"h2_host" json:"h2_host,omitempty"`
|
||
H2Path string `yaml:"h2_path" json:"h2_path,omitempty"`
|
||
|
||
// QUIC配置
|
||
QUICSecurity string `yaml:"quic_security" json:"quic_security,omitempty"`
|
||
QUICKey string `yaml:"quic_key" json:"quic_key,omitempty"`
|
||
|
||
// gRPC配置
|
||
GRPCServiceName string `yaml:"grpc_service_name" json:"grpc_service_name,omitempty"`
|
||
GRPCMultiMode bool `yaml:"grpc_multi_mode" json:"grpc_multi_mode,omitempty"`
|
||
|
||
// 其他配置
|
||
UDP bool `yaml:"udp" json:"udp"`
|
||
TFO bool `yaml:"tfo" json:"tfo"` // TCP Fast Open
|
||
Mux bool `yaml:"mux" json:"mux"` // 多路复用
|
||
Scramble bool `yaml:"scramble" json:"scramble"`
|
||
Plugin string `yaml:"plugin" json:"plugin,omitempty"`
|
||
PluginOpts string `yaml:"plugin_opts" json:"plugin_opts,omitempty"`
|
||
|
||
// 节点属性
|
||
Location string `yaml:"location" json:"location,omitempty"`
|
||
Provider string `yaml:"provider" json:"provider,omitempty"`
|
||
Ping int `yaml:"ping" json:"ping,omitempty"` // 延迟(ms)
|
||
}
|
||
|
||
// VMessConfig VMess协议特定配置
|
||
type VMessConfig struct {
|
||
VMessUUID string `yaml:"vmess_uuid" json:"vmess_uuid"`
|
||
VMessAlterID string `yaml:"vmess_alter_id" json:"vmess_alter_id"`
|
||
VMessSecurity string `yaml:"vmess_security" json:"vmess_security"`
|
||
}
|
||
|
||
// NewProxyConfig 创建新的代理配置
|
||
// 返回包含基本默认值的ProxyConfig结构体
|
||
func NewProxyConfig() *ProxyConfig {
|
||
return &ProxyConfig{
|
||
Name: "",
|
||
Type: "",
|
||
Server: "",
|
||
Port: 0,
|
||
Password: "",
|
||
Method: "",
|
||
Protocol: "",
|
||
Obfs: "",
|
||
ObfsParam: "",
|
||
TLS: false,
|
||
SNI: "",
|
||
Alpn: "",
|
||
SkipVerify: false,
|
||
WSPath: "",
|
||
WSHeaders: make(map[string]string),
|
||
H2Host: "",
|
||
H2Path: "",
|
||
QUICSecurity: "",
|
||
QUICKey: "",
|
||
GRPCServiceName: "",
|
||
GRPCMultiMode: false,
|
||
UDP: false,
|
||
TFO: false,
|
||
Mux: false,
|
||
Scramble: false,
|
||
Plugin: "",
|
||
PluginOpts: "",
|
||
Location: "",
|
||
Provider: "",
|
||
Ping: 0,
|
||
}
|
||
}
|
||
|
||
// Validate 验证代理配置的有效性
|
||
// 返回error如果配置无效,nil表示配置有效
|
||
func (p *ProxyConfig) Validate() error {
|
||
// 基本字段验证
|
||
if p.Name == "" {
|
||
return fmt.Errorf("proxy name cannot be empty")
|
||
}
|
||
|
||
if p.Type == "" {
|
||
return fmt.Errorf("proxy type cannot be empty")
|
||
}
|
||
|
||
if p.Server == "" {
|
||
return fmt.Errorf("proxy server cannot be empty")
|
||
}
|
||
|
||
if p.Port < 1 || p.Port > 65535 {
|
||
return fmt.Errorf("proxy port must be between 1 and 65535, got: %d", p.Port)
|
||
}
|
||
|
||
// 根据类型验证特定字段
|
||
switch strings.ToLower(p.Type) {
|
||
case "ss", "shadowsocks":
|
||
if p.Password == "" {
|
||
return fmt.Errorf("shadowsocks password cannot be empty")
|
||
}
|
||
if p.Method == "" {
|
||
return fmt.Errorf("shadowsocks method cannot be empty")
|
||
}
|
||
|
||
case "ssr", "shadowsocksr":
|
||
if p.Password == "" {
|
||
return fmt.Errorf("shadowsocksr password cannot be empty")
|
||
}
|
||
if p.Method == "" {
|
||
return fmt.Errorf("shadowsocksr method cannot be empty")
|
||
}
|
||
if p.Protocol == "" {
|
||
return fmt.Errorf("shadowsocksr protocol cannot be empty")
|
||
}
|
||
|
||
case "vmess":
|
||
if p.VMessConfig == nil || p.VMessConfig.VMessUUID == "" {
|
||
return fmt.Errorf("vmess uuid cannot be empty")
|
||
}
|
||
if p.VMessConfig.VMessAlterID == "" {
|
||
return fmt.Errorf("vmess alter id cannot be empty")
|
||
}
|
||
if p.VMessConfig.VMessSecurity == "" {
|
||
return fmt.Errorf("vmess security cannot be empty")
|
||
}
|
||
|
||
case "trojan":
|
||
if p.Password == "" {
|
||
return fmt.Errorf("trojan password cannot be empty")
|
||
}
|
||
|
||
case "http", "https":
|
||
if p.Password == "" && p.Type == "https" {
|
||
return fmt.Errorf("https proxy requires authentication")
|
||
}
|
||
|
||
case "socks5":
|
||
// SOCKS5可以无认证
|
||
|
||
default:
|
||
return fmt.Errorf("unsupported proxy type: %s", p.Type)
|
||
}
|
||
|
||
// TLS配置验证
|
||
if p.TLS {
|
||
if p.SNI == "" {
|
||
p.SNI = p.Server // 默认使用服务器地址作为SNI
|
||
}
|
||
}
|
||
|
||
// WebSocket配置验证
|
||
if p.WSPath != "" && p.WSHeaders == nil {
|
||
p.WSHeaders = make(map[string]string)
|
||
}
|
||
|
||
// HTTP/2配置验证
|
||
if p.H2Path != "" && p.H2Host == "" {
|
||
return fmt.Errorf("h2 host cannot be empty when h2 path is specified")
|
||
}
|
||
|
||
// QUIC配置验证
|
||
if p.QUICKey != "" && p.QUICSecurity == "" {
|
||
return fmt.Errorf("quic security cannot be empty when quic key is specified")
|
||
}
|
||
|
||
// gRPC配置验证
|
||
if p.GRPCServiceName != "" && !p.TLS {
|
||
return fmt.Errorf("grpc requires tls to be enabled")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ToURL 将代理配置转换为URL格式
|
||
// 返回代理URL字符串,适用于分享和导入
|
||
func (p *ProxyConfig) ToURL() (string, error) {
|
||
switch strings.ToLower(p.Type) {
|
||
case "ss", "shadowsocks":
|
||
return p.toSSURL()
|
||
case "ssr", "shadowsocksr":
|
||
return p.toSSRURL()
|
||
case "vmess":
|
||
return p.toVMessURL()
|
||
case "trojan":
|
||
return p.toTrojanURL()
|
||
case "http", "https":
|
||
return p.toHTTPURL()
|
||
case "socks5":
|
||
return p.toSocks5URL()
|
||
default:
|
||
return "", fmt.Errorf("unsupported proxy type: %s", p.Type)
|
||
}
|
||
}
|
||
|
||
// FromURL 从URL解析代理配置
|
||
// 根据URL类型自动识别并解析代理配置
|
||
func (p *ProxyConfig) FromURL(proxyURL string) error {
|
||
u, err := url.Parse(proxyURL)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to parse proxy URL: %v", err)
|
||
}
|
||
|
||
switch u.Scheme {
|
||
case "ss":
|
||
return p.fromSSURL(u)
|
||
case "ssr":
|
||
return p.fromSSRURL(u)
|
||
case "vmess":
|
||
return p.fromVMessURL(u)
|
||
case "trojan":
|
||
return p.fromTrojanURL(u)
|
||
case "http", "https":
|
||
return p.fromHTTPURL(u)
|
||
case "socks5":
|
||
return p.fromSocks5URL(u)
|
||
default:
|
||
return fmt.Errorf("unsupported proxy URL scheme: %s", u.Scheme)
|
||
}
|
||
}
|
||
|
||
// toSSURL 转换为SS URL格式
|
||
func (p *ProxyConfig) toSSURL() (string, error) {
|
||
if p.Type != "ss" && p.Type != "shadowsocks" {
|
||
return "", fmt.Errorf("not a shadowsocks proxy")
|
||
}
|
||
|
||
// SS URL格式: ss://method:password@server:port#name
|
||
host := fmt.Sprintf("%s:%d", p.Server, p.Port)
|
||
|
||
u := &url.URL{
|
||
Scheme: "ss",
|
||
User: url.UserPassword(p.Method, p.Password),
|
||
Host: host,
|
||
}
|
||
|
||
if p.Name != "" {
|
||
u.Fragment = p.Name
|
||
}
|
||
|
||
return u.String(), nil
|
||
}
|
||
|
||
// toSSRURL 转换为SSR URL格式
|
||
func (p *ProxyConfig) toSSRURL() (string, error) {
|
||
if p.Type != "ssr" && p.Type != "shadowsocksr" {
|
||
return "", fmt.Errorf("not a shadowsocksr proxy")
|
||
}
|
||
|
||
// SSR URL格式: ssr://base64(server:port:protocol:method:obfs:password/?obfsparam=...&protoparam=...&remarks=...)
|
||
params := make(url.Values)
|
||
params.Set("obfsparam", p.ObfsParam)
|
||
params.Set("protoparam", "")
|
||
params.Set("remarks", p.Name)
|
||
params.Set("group", "")
|
||
params.Set("udpport", "0")
|
||
params.Set("uot", "0")
|
||
|
||
baseInfo := fmt.Sprintf("%s:%d:%s:%s:%s:%s",
|
||
p.Server, p.Port, p.Protocol, p.Method, p.Obfs, p.Password)
|
||
|
||
encodedInfo := url.QueryEscape(base64Encode(baseInfo))
|
||
encodedParams := url.QueryEscape(params.Encode())
|
||
|
||
return fmt.Sprintf("ssr://%s/?%s", encodedInfo, encodedParams), nil
|
||
}
|
||
|
||
// toVMessURL 转换为VMess URL格式
|
||
func (p *ProxyConfig) toVMessURL() (string, error) {
|
||
if p.Type != "vmess" {
|
||
return "", fmt.Errorf("not a vmess proxy")
|
||
}
|
||
|
||
// VMess URL格式: vmess://base64(json_config)
|
||
config := map[string]interface{}{
|
||
"v": "2",
|
||
"ps": p.Name,
|
||
"add": p.Server,
|
||
"port": p.Port,
|
||
"id": p.VMessConfig.VMessUUID,
|
||
"aid": p.VMessConfig.VMessAlterID,
|
||
"net": "tcp", // 简化版本
|
||
"type": "none",
|
||
"host": "",
|
||
"path": "",
|
||
"tls": "",
|
||
}
|
||
|
||
if p.TLS {
|
||
config["tls"] = "tls"
|
||
}
|
||
|
||
configJSON, err := json.Marshal(config)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to marshal vmess config: %v", err)
|
||
}
|
||
|
||
return "vmess://" + base64Encode(string(configJSON)), nil
|
||
}
|
||
|
||
// toTrojanURL 转换为Trojan URL格式
|
||
func (p *ProxyConfig) toTrojanURL() (string, error) {
|
||
if p.Type != "trojan" {
|
||
return "", fmt.Errorf("not a trojan proxy")
|
||
}
|
||
|
||
// Trojan URL格式: trojan://password@server:port?allowinsecure=1#name
|
||
u := &url.URL{
|
||
Scheme: "trojan",
|
||
User: url.UserPassword(p.Password, ""),
|
||
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
|
||
}
|
||
|
||
params := u.Query()
|
||
if p.SkipVerify {
|
||
params.Set("allowinsecure", "1")
|
||
}
|
||
if p.SNI != "" {
|
||
params.Set("sni", p.SNI)
|
||
}
|
||
u.RawQuery = params.Encode()
|
||
|
||
if p.Name != "" {
|
||
u.Fragment = p.Name
|
||
}
|
||
|
||
return u.String(), nil
|
||
}
|
||
|
||
// toHTTPURL 转换为HTTP/HTTPS URL格式
|
||
func (p *ProxyConfig) toHTTPURL() (string, error) {
|
||
if p.Type != "http" && p.Type != "https" {
|
||
return "", fmt.Errorf("not an http proxy")
|
||
}
|
||
|
||
scheme := "http"
|
||
if p.Type == "https" {
|
||
scheme = "https"
|
||
}
|
||
|
||
u := &url.URL{
|
||
Scheme: scheme,
|
||
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
|
||
}
|
||
|
||
if p.Password != "" {
|
||
u.User = url.UserPassword("", p.Password)
|
||
}
|
||
|
||
if p.Name != "" {
|
||
u.Fragment = p.Name
|
||
}
|
||
|
||
return u.String(), nil
|
||
}
|
||
|
||
// toSocks5URL 转换为SOCKS5 URL格式
|
||
func (p *ProxyConfig) toSocks5URL() (string, error) {
|
||
if p.Type != "socks5" {
|
||
return "", fmt.Errorf("not a socks5 proxy")
|
||
}
|
||
|
||
u := &url.URL{
|
||
Scheme: "socks5",
|
||
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
|
||
}
|
||
|
||
if p.Password != "" {
|
||
u.User = url.UserPassword("", p.Password)
|
||
}
|
||
|
||
if p.Name != "" {
|
||
u.Fragment = p.Name
|
||
}
|
||
|
||
return u.String(), nil
|
||
}
|
||
|
||
// 以下是FromURL的各种实现方法,由于篇幅限制,这里只提供框架
|
||
func (p *ProxyConfig) fromSSURL(u *url.URL) error {
|
||
// 解析SS URL的实现
|
||
p.Type = "ss"
|
||
p.Server = u.Hostname()
|
||
if port, err := strconv.Atoi(u.Port()); err == nil {
|
||
p.Port = port
|
||
}
|
||
p.Name = u.Fragment
|
||
|
||
if u.User != nil {
|
||
p.Method = u.User.Username()
|
||
if password, ok := u.User.Password(); ok {
|
||
p.Password = password
|
||
}
|
||
}
|
||
|
||
return p.Validate()
|
||
}
|
||
|
||
func (p *ProxyConfig) fromSSRURL(u *url.URL) error {
|
||
// 解析SSR URL的实现
|
||
p.Type = "ssr"
|
||
// 实现SSR URL解析逻辑
|
||
return p.Validate()
|
||
}
|
||
|
||
func (p *ProxyConfig) fromVMessURL(u *url.URL) error {
|
||
// 解析VMess URL的实现
|
||
p.Type = "vmess"
|
||
// 实现VMess URL解析逻辑
|
||
return p.Validate()
|
||
}
|
||
|
||
func (p *ProxyConfig) fromTrojanURL(u *url.URL) error {
|
||
// 解析Trojan URL的实现
|
||
p.Type = "trojan"
|
||
p.Server = u.Hostname()
|
||
if port, err := strconv.Atoi(u.Port()); err == nil {
|
||
p.Port = port
|
||
}
|
||
p.Name = u.Fragment
|
||
|
||
if u.User != nil {
|
||
p.Password = u.User.Username()
|
||
}
|
||
|
||
return p.Validate()
|
||
}
|
||
|
||
func (p *ProxyConfig) fromHTTPURL(u *url.URL) error {
|
||
// 解析HTTP URL的实现
|
||
p.Type = u.Scheme
|
||
p.Server = u.Hostname()
|
||
if port, err := strconv.Atoi(u.Port()); err == nil {
|
||
p.Port = port
|
||
}
|
||
p.Name = u.Fragment
|
||
|
||
if u.User != nil {
|
||
if password, ok := u.User.Password(); ok {
|
||
p.Password = password
|
||
}
|
||
}
|
||
|
||
return p.Validate()
|
||
}
|
||
|
||
func (p *ProxyConfig) fromSocks5URL(u *url.URL) error {
|
||
// 解析SOCKS5 URL的实现
|
||
p.Type = "socks5"
|
||
p.Server = u.Hostname()
|
||
if port, err := strconv.Atoi(u.Port()); err == nil {
|
||
p.Port = port
|
||
}
|
||
p.Name = u.Fragment
|
||
|
||
if u.User != nil {
|
||
if password, ok := u.User.Password(); ok {
|
||
p.Password = password
|
||
}
|
||
}
|
||
|
||
return p.Validate()
|
||
}
|
||
|
||
// 辅助函数
|
||
func base64Encode(s string) string {
|
||
return base64.StdEncoding.EncodeToString([]byte(s))
|
||
}
|
||
|
||
func base64Decode(s string) (string, error) {
|
||
data, err := base64.StdEncoding.DecodeString(s)
|
||
return string(data), err
|
||
} |