first commit
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
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
This commit is contained in:
511
internal/config/proxy.go
Normal file
511
internal/config/proxy.go
Normal file
@@ -0,0 +1,511 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user