163 lines
4.0 KiB
Go
163 lines
4.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/zu1k/proxypool/pkg/tool"
|
|
)
|
|
|
|
var (
|
|
ErrorNotSSLink = errors.New("not a correct ss link")
|
|
)
|
|
|
|
type Shadowsocks struct {
|
|
Base
|
|
Password string `yaml:"password" json:"password"`
|
|
Cipher string `yaml:"cipher" json:"cipher"`
|
|
Plugin string `yaml:"plugin,omitempty" json:"plugin,omitempty"`
|
|
PluginOpts map[string]interface{} `yaml:"plugin-opts,omitempty" json:"plugin-opts,omitempty"`
|
|
}
|
|
|
|
func (ss Shadowsocks) Identifier() string {
|
|
return net.JoinHostPort(ss.Server, strconv.Itoa(ss.Port)) + ss.Password
|
|
}
|
|
|
|
func (ss Shadowsocks) String() string {
|
|
data, err := json.Marshal(ss)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func (ss Shadowsocks) ToClash() string {
|
|
data, err := json.Marshal(ss)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return "- " + string(data)
|
|
}
|
|
|
|
func (ss Shadowsocks) ToSurge() string {
|
|
// node1 = ss, server, port, encrypt-method=, password=, obfs=, obfs-host=, udp-relay=false
|
|
if ss.Plugin == "obfs" {
|
|
text := fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s, obfs=%s, udp-relay=false",
|
|
ss.Name, ss.Server, ss.Port, ss.Cipher, ss.Password, ss.PluginOpts["mode"])
|
|
if ss.PluginOpts["host"].(string) != "" {
|
|
text += ", obfs-host=" + ss.PluginOpts["host"].(string)
|
|
}
|
|
return text
|
|
} else {
|
|
return fmt.Sprintf("%s = ss, %s, %d, encrypt-method=%s, password=%s, udp-relay=false",
|
|
ss.Name, ss.Server, ss.Port, ss.Cipher, ss.Password)
|
|
}
|
|
}
|
|
|
|
func (ss Shadowsocks) Clone() Proxy {
|
|
return &ss
|
|
}
|
|
|
|
// https://shadowsocks.org/en/config/quick-guide.html
|
|
func (ss Shadowsocks) Link() (link string) {
|
|
payload := fmt.Sprintf("%s:%s@%s:%d", ss.Cipher, ss.Password, ss.Server, ss.Port)
|
|
payload = tool.Base64EncodeString(payload, false)
|
|
return fmt.Sprintf("ss://%s#%s", payload, ss.Name)
|
|
}
|
|
|
|
func ParseSSLink(link string) (*Shadowsocks, error) {
|
|
if !strings.HasPrefix(link, "ss://") {
|
|
return nil, ErrorNotSSRLink
|
|
}
|
|
|
|
uri, err := url.Parse(link)
|
|
if err != nil {
|
|
return nil, ErrorNotSSLink
|
|
}
|
|
|
|
cipher := ""
|
|
password := ""
|
|
if uri.User.String() == "" {
|
|
// base64的情况
|
|
infos, err := tool.Base64DecodeString(uri.Hostname())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uri, err = url.Parse("ss://" + infos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cipher = uri.User.Username()
|
|
password, _ = uri.User.Password()
|
|
} else {
|
|
cipherInfoString, err := tool.Base64DecodeString(uri.User.Username())
|
|
if err != nil {
|
|
return nil, ErrorPasswordParseFail
|
|
}
|
|
cipherInfo := strings.SplitN(cipherInfoString, ":", 2)
|
|
if len(cipherInfo) < 2 {
|
|
return nil, ErrorPasswordParseFail
|
|
}
|
|
cipher = strings.ToLower(cipherInfo[0])
|
|
password = cipherInfo[1]
|
|
}
|
|
server := uri.Hostname()
|
|
port, _ := strconv.Atoi(uri.Port())
|
|
|
|
moreInfos := uri.Query()
|
|
pluginString := moreInfos.Get("plugin")
|
|
plugin := ""
|
|
pluginOpts := make(map[string]interface{})
|
|
if strings.Contains(pluginString, ";") {
|
|
pluginInfos, err := url.ParseQuery(pluginString)
|
|
if err == nil {
|
|
if strings.Contains(pluginString, "obfs") {
|
|
plugin = "obfs"
|
|
pluginOpts["mode"] = pluginInfos.Get("obfs")
|
|
pluginOpts["host"] = pluginInfos.Get("obfs-host")
|
|
} else if strings.Contains(pluginString, "v2ray") {
|
|
plugin = "v2ray-plugin"
|
|
pluginOpts["mode"] = pluginInfos.Get("mode")
|
|
pluginOpts["host"] = pluginInfos.Get("host")
|
|
pluginOpts["tls"] = strings.Contains(pluginString, "tls")
|
|
}
|
|
}
|
|
}
|
|
if port == 0 || cipher == "" {
|
|
return nil, ErrorNotSSLink
|
|
}
|
|
|
|
return &Shadowsocks{
|
|
Base: Base{
|
|
Name: strconv.Itoa(rand.Int()),
|
|
Server: server,
|
|
Port: port,
|
|
Type: "ss",
|
|
},
|
|
Password: password,
|
|
Cipher: cipher,
|
|
Plugin: plugin,
|
|
PluginOpts: pluginOpts,
|
|
}, nil
|
|
}
|
|
|
|
var (
|
|
ssPlainRe = regexp.MustCompile("ss://([A-Za-z0-9+/_&?=@:%.-])+")
|
|
)
|
|
|
|
func GrepSSLinkFromString(text string) []string {
|
|
results := make([]string, 0)
|
|
texts := strings.Split(text, "ss://")
|
|
for _, text := range texts {
|
|
results = append(results, ssPlainRe.FindAllString("ss://"+text, -1)...)
|
|
}
|
|
return results
|
|
}
|