Files
proxypool/pkg/proxy/vmess.go
2020-09-04 16:40:50 +08:00

277 lines
6.8 KiB
Go

package proxy
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/zu1k/proxypool/pkg/tool"
)
var (
ErrorNotVmessLink = errors.New("not a correct vmess link")
ErrorVmessPayloadParseFail = errors.New("vmess link payload parse failed")
)
type Vmess struct {
Base
UUID string `yaml:"uuid" json:"uuid"`
AlterID int `yaml:"alterId" json:"alterId"`
Cipher string `yaml:"cipher" json:"cipher"`
TLS bool `yaml:"tls,omitempty" json:"tls,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
HTTPOpts HTTPOptions `yaml:"http-opts,omitempty" json:"http-opts,omitempty"`
WSPath string `yaml:"ws-path,omitempty" json:"ws-path,omitempty"`
WSHeaders map[string]string `yaml:"ws-headers,omitempty" json:"ws-headers,omitempty"`
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty" json:"skip-cert-verify,omitempty"`
ServerName string `yaml:"servername,omitempty" json:"servername,omitempty"`
}
type HTTPOptions struct {
Method string `yaml:"method,omitempty" json:"method,omitempty"`
Path []string `yaml:"path,omitempty" json:"path,omitempty"`
Headers map[string][]string `yaml:"headers,omitempty" json:"headers,omitempty"`
}
func (v Vmess) Identifier() string {
return net.JoinHostPort(v.Server, strconv.Itoa(v.Port)) + v.Cipher + v.UUID
}
func (v Vmess) String() string {
data, err := json.Marshal(v)
if err != nil {
return ""
}
return string(data)
}
func (v Vmess) ToClash() string {
data, err := json.Marshal(v)
if err != nil {
return ""
}
return "- " + string(data)
}
func (v Vmess) ToSurge() string {
// node2 = vmess, server, port, username=, ws=true, ws-path=, ws-headers=
if v.Network == "ws" {
wsHeasers := ""
for k, v := range v.WSHeaders {
if wsHeasers == "" {
wsHeasers = k + ":" + v
} else {
wsHeasers += "|" + k + ":" + v
}
}
text := fmt.Sprintf("%s = vmess, %s, %d, username=%s, ws=true, tls=%t, ws-path=%s",
v.Name, v.Server, v.Port, v.UUID, v.TLS, v.WSPath)
if wsHeasers != "" {
text += ", ws-headers=" + wsHeasers
}
return text
} else {
return fmt.Sprintf("%s = vmess, %s, %d, username=%s, tls=%t",
v.Name, v.Server, v.Port, v.UUID, v.TLS)
}
}
func (v Vmess) Clone() Proxy {
return &v
}
func (v Vmess) Link() (link string) {
vjv, err := json.Marshal(v.toLinkJson())
if err != nil {
return
}
return fmt.Sprintf("vmess://%s", tool.Base64EncodeBytes(vjv))
}
type vmessLinkJson struct {
Add string `json:"add"`
V string `json:"v"`
Ps string `json:"ps"`
Port interface{} `json:"port"`
Id string `json:"id"`
Aid string `json:"aid"`
Net string `json:"net"`
Type string `json:"type"`
Host string `json:"host"`
Path string `json:"path"`
Tls string `json:"tls"`
}
func (v Vmess) toLinkJson() vmessLinkJson {
vj := vmessLinkJson{
Add: v.Server,
Ps: v.Name,
Port: v.Port,
Id: v.UUID,
Aid: strconv.Itoa(v.AlterID),
Net: v.Network,
Path: v.WSPath,
Host: v.ServerName,
V: "2",
}
if v.TLS {
vj.Tls = "tls"
}
if host, ok := v.WSHeaders["HOST"]; ok && host != "" {
vj.Host = host
}
return vj
}
func ParseVmessLink(link string) (*Vmess, error) {
if !strings.HasPrefix(link, "vmess") {
return nil, ErrorNotVmessLink
}
vmessmix := strings.SplitN(link, "://", 2)
if len(vmessmix) < 2 {
return nil, ErrorNotVmessLink
}
linkPayload := vmessmix[1]
if strings.Contains(linkPayload, "?") {
// 使用第二种解析方法
var infoPayloads []string
if strings.Contains(linkPayload, "/?") {
infoPayloads = strings.SplitN(linkPayload, "/?", 2)
} else {
infoPayloads = strings.SplitN(linkPayload, "?", 2)
}
if len(infoPayloads) < 2 {
return nil, ErrorNotVmessLink
}
baseInfo, err := tool.Base64DecodeString(infoPayloads[0])
if err != nil {
return nil, ErrorVmessPayloadParseFail
}
baseInfoPath := strings.Split(baseInfo, ":")
if len(baseInfoPath) < 3 {
return nil, ErrorPathNotComplete
}
// base info
cipher := baseInfoPath[0]
mixInfo := strings.SplitN(baseInfoPath[1], "@", 2)
if len(mixInfo) < 2 {
return nil, ErrorVmessPayloadParseFail
}
uuid := mixInfo[0]
server := mixInfo[1]
portStr := baseInfoPath[2]
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, ErrorVmessPayloadParseFail
}
moreInfo, _ := url.ParseQuery(infoPayloads[1])
remarks := moreInfo.Get("remarks")
obfs := moreInfo.Get("obfs")
network := "tcp"
if obfs == "websocket" {
network = "ws"
}
//obfsParam := moreInfo.Get("obfsParam")
path := moreInfo.Get("path")
if path == "" {
path = "/"
}
tls := moreInfo.Get("tls") == "1"
wsHeaders := make(map[string]string)
return &Vmess{
Base: Base{
Name: remarks + "_" + strconv.Itoa(rand.Int()),
Server: server,
Port: port,
Type: "vmess",
UDP: false,
},
UUID: uuid,
AlterID: 0,
Cipher: cipher,
TLS: tls,
Network: network,
HTTPOpts: HTTPOptions{},
WSPath: path,
WSHeaders: wsHeaders,
SkipCertVerify: true,
ServerName: server,
}, nil
} else {
payload, err := tool.Base64DecodeString(linkPayload)
if err != nil {
return nil, ErrorVmessPayloadParseFail
}
vmessJson := vmessLinkJson{}
err = json.Unmarshal([]byte(payload), &vmessJson)
if err != nil {
return nil, err
}
port := 443
portInterface := vmessJson.Port
switch portInterface.(type) {
case int:
port = portInterface.(int)
case string:
port, _ = strconv.Atoi(portInterface.(string))
}
alterId, err := strconv.Atoi(vmessJson.Aid)
if err != nil {
alterId = 0
}
tls := vmessJson.Tls == "tls"
wsHeaders := make(map[string]string)
if vmessJson.Host != "" {
wsHeaders["HOST"] = vmessJson.Host
}
if vmessJson.Path == "" {
vmessJson.Path = "/"
}
return &Vmess{
Base: Base{
Name: vmessJson.Ps + "_" + strconv.Itoa(rand.Int()),
Server: vmessJson.Add,
Port: port,
Type: "vmess",
UDP: false,
},
UUID: vmessJson.Id,
AlterID: alterId,
Cipher: "auto",
TLS: tls,
Network: vmessJson.Net,
HTTPOpts: HTTPOptions{},
WSPath: vmessJson.Path,
WSHeaders: wsHeaders,
SkipCertVerify: true,
ServerName: vmessJson.Host,
}, nil
}
}
var (
vmessPlainRe = regexp.MustCompile("vmess://([A-Za-z0-9+/_?&=-])+")
)
func GrepVmessLinkFromString(text string) []string {
results := make([]string, 0)
texts := strings.Split(text, "vmess://")
for _, text := range texts {
results = append(results, vmessPlainRe.FindAllString("vmess://"+text, -1)...)
}
return results
}