github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/config/vmess.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"net/url"
     9  	"os"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/laof/lite-speed-test/outbound"
    15  	"github.com/laof/lite-speed-test/utils"
    16  )
    17  
    18  var RegShadowrocketVmess = regexp.MustCompile(`(?i)vmess://(\S+?)@(\S+?):([0-9]{2,5})/?([?#][^\s]+)`)
    19  
    20  type User struct {
    21  	Email    string `json:"Email"`
    22  	ID       string `json:"ID"`
    23  	AlterId  int    `json:"alterId"`
    24  	Security string `json:"security"`
    25  }
    26  
    27  type VNext struct {
    28  	Address string `json:"address"`
    29  	Port    uint16 `json:"port"`
    30  	Users   []User `json:"users"`
    31  }
    32  
    33  type Settings struct {
    34  	Vnexts []VNext `json:"vnext"`
    35  }
    36  
    37  type WSSettings struct {
    38  	Path string `json:"path"`
    39  }
    40  
    41  type StreamSettings struct {
    42  	Network    string     `json:"network"`
    43  	Security   string     `json:"security"`
    44  	WSSettings WSSettings `json:"wsSettings,omitempty"`
    45  }
    46  
    47  type Outbound struct {
    48  	Protocol       string          `json:"protocol"`
    49  	Description    string          `json:"description"`
    50  	Settings       Settings        `json:"settings"`
    51  	StreamSettings *StreamSettings `json:"streamSettings,omitempty"`
    52  }
    53  
    54  type RawConfig struct {
    55  	Outbounds []Outbound `json:"outbounds"`
    56  }
    57  
    58  type VmessConfig struct {
    59  	Add            string          `json:"add"`
    60  	Aid            json.RawMessage `json:"aid"`
    61  	AlterId        json.RawMessage `json:"alterId,omitempty"`
    62  	Host           string          `json:"host"`
    63  	ID             string          `json:"id"`
    64  	Net            string          `json:"net"`
    65  	Path           string          `json:"path"`
    66  	Port           json.RawMessage `json:"port"`
    67  	Ps             string          `json:"ps"`
    68  	TLSRaw         json.RawMessage `json:"tls"`
    69  	Type           string          `json:"type"`
    70  	V              json.RawMessage `json:"v,omitempty"`
    71  	Security       string          `json:"security,omitempty"`
    72  	Scy            string          `json:"scy,omitempty"`
    73  	Encryption     string          `json:"encryption,omitempty"`
    74  	ResolveIP      bool            `json:"resolve_ip,omitempty"`
    75  	SkipCertVerify bool            `json:"skip-cert-verify,omitempty"`
    76  	ServerName     string          `json:"sni,omitempty"`
    77  	PortInt        int             `json:"-"`
    78  	AidInt         int             `json:"-"`
    79  	TLS            string          `json:"-"`
    80  }
    81  
    82  type VmessConfigMarshal struct {
    83  	Add            string `json:"add"`
    84  	Aid            int    `json:"aid"`
    85  	Host           string `json:"host"`
    86  	ID             string `json:"id"`
    87  	Net            string `json:"net"`
    88  	Path           string `json:"path"`
    89  	Port           uint16 `json:"port"`
    90  	Ps             string `json:"ps"`
    91  	TLS            string `json:"tls"`
    92  	Type           string `json:"type"`
    93  	V              string `json:"v,omitempty"`
    94  	Security       string `json:"security,omitempty"`
    95  	Scy            string `json:"scy,omitempty"`
    96  	ResolveIP      bool   `json:"resolve_ip,omitempty"`
    97  	SkipCertVerify bool   `json:"skip-cert-verify"`
    98  	ServerName     string `json:"sni"`
    99  }
   100  
   101  func RawConfigToVmessOption(config *RawConfig) (*outbound.VmessOption, error) {
   102  	var ob Outbound
   103  	for _, outbound := range config.Outbounds {
   104  		if outbound.Protocol == "vmess" {
   105  			ob = outbound
   106  			break
   107  		}
   108  	}
   109  	vnext := ob.Settings.Vnexts[0]
   110  	vmessOption := outbound.VmessOption{
   111  		HTTPOpts: outbound.HTTPOptions{
   112  			Method: "GET",
   113  			Path:   []string{"/"},
   114  		},
   115  		Name:           "vmess",
   116  		Server:         vnext.Address,
   117  		Port:           vnext.Port,
   118  		UUID:           vnext.Users[0].ID,
   119  		AlterID:        vnext.Users[0].AlterId,
   120  		Cipher:         vnext.Users[0].Security,
   121  		TLS:            false,
   122  		UDP:            false,
   123  		Network:        "tcp",
   124  		SkipCertVerify: false,
   125  	}
   126  	if ob.StreamSettings != nil {
   127  		if ob.StreamSettings.Security == "tls" {
   128  			vmessOption.TLS = true
   129  		}
   130  		if ob.StreamSettings.Network == "ws" {
   131  			vmessOption.Network = "ws"
   132  			vmessOption.WSPath = ob.StreamSettings.WSSettings.Path
   133  			if ob.StreamSettings.WSSettings.Path != "" {
   134  				vmessOption.WSHeaders = map[string]string{
   135  					"Host": vnext.Address,
   136  				}
   137  			}
   138  		}
   139  	}
   140  	return &vmessOption, nil
   141  }
   142  func rawMessageToInt(raw json.RawMessage) (int, error) {
   143  	var i int
   144  	err := json.Unmarshal(raw, &i)
   145  	if err != nil {
   146  		var s string
   147  		err := json.Unmarshal(raw, &s)
   148  		if err != nil {
   149  			return 0, err
   150  		}
   151  		return strconv.Atoi(s)
   152  	}
   153  	return i, nil
   154  }
   155  
   156  func rawMessageToTLS(raw json.RawMessage) (string, error) {
   157  	var s string
   158  	err := json.Unmarshal(raw, &s)
   159  	if err != nil {
   160  		var b bool
   161  		err := json.Unmarshal(raw, &b)
   162  		if err != nil {
   163  			return "", err
   164  		}
   165  		if b {
   166  			s = "tls"
   167  		}
   168  	}
   169  	return s, nil
   170  }
   171  
   172  func checkCipher(cipher string) bool {
   173  	return cipher == "auto" || cipher == "none" || cipher == "aes-128-gcm" || cipher == "chacha20-poly1305"
   174  }
   175  
   176  func VmessConfigToVmessOption(config *VmessConfig) (*outbound.VmessOption, error) {
   177  	port, err := rawMessageToInt(config.Port)
   178  	if err != nil {
   179  		port = 443
   180  	}
   181  	aid, err := rawMessageToInt(config.Aid)
   182  	if err != nil {
   183  		aid = 0
   184  	}
   185  
   186  	vmessOption := outbound.VmessOption{
   187  		// HTTPOpts: outbound.HTTPOptions{
   188  		// 	Method: "GET",
   189  		// 	Path:   []string{"/"},
   190  		// },
   191  		Name:           "vmess",
   192  		Server:         config.Add,
   193  		Port:           uint16(port),
   194  		UUID:           config.ID,
   195  		AlterID:        aid,
   196  		Cipher:         "none",
   197  		TLS:            false,
   198  		UDP:            false,
   199  		Network:        "tcp",
   200  		SkipCertVerify: config.SkipCertVerify,
   201  		Type:           config.Type,
   202  	}
   203  	// http network
   204  	if config.Type == "http" {
   205  		vmessOption.HTTPOpts = outbound.HTTPOptions{
   206  			Method: "GET",
   207  			Path:   []string{config.Path},
   208  		}
   209  		if config.Host != "" {
   210  			vmessOption.HTTPOpts.Headers = map[string][]string{
   211  				"Host":       {config.Host},
   212  				"Connection": {"keep-alive"},
   213  			}
   214  		}
   215  		vmessOption.Network = "http"
   216  	}
   217  	if config.ResolveIP {
   218  		if ipAddr, err := resolveIP(vmessOption.Server); err == nil && ipAddr != "" {
   219  			vmessOption.ServerName = vmessOption.Server
   220  			vmessOption.Server = ipAddr
   221  		}
   222  	}
   223  	if config.TLS == "tls" {
   224  		vmessOption.TLS = true
   225  		if len(config.ServerName) > 0 && config.ServerName != config.Add {
   226  			config.SkipCertVerify = true
   227  		}
   228  	}
   229  	// check cipher
   230  	if checkCipher(config.Security) {
   231  		vmessOption.Cipher = config.Security
   232  	} else if checkCipher(config.Scy) {
   233  		vmessOption.Cipher = config.Scy
   234  	} else if checkCipher(config.Encryption) {
   235  		vmessOption.Cipher = config.Encryption
   236  	}
   237  	if config.Net == "ws" {
   238  		vmessOption.Network = "ws"
   239  		vmessOption.WSPath = config.Path
   240  		vmessOption.WSHeaders = map[string]string{
   241  			"Host": config.Host,
   242  		}
   243  	}
   244  	if config.Net == "h2" {
   245  		vmessOption.Network = "h2"
   246  		if vmessOption.TLS {
   247  			vmessOption.SkipCertVerify = false
   248  		}
   249  		vmessOption.HTTP2Opts = outbound.HTTP2Options{
   250  			Host: []string{config.Host},
   251  			Path: config.Path,
   252  		}
   253  	}
   254  	return &vmessOption, nil
   255  }
   256  
   257  func VmessLinkToVmessOption(link string) (*outbound.VmessOption, error) {
   258  	opt, err := VmessLinkToVmessOptionIP(link, false)
   259  	if err != nil {
   260  		return ShadowrocketVmessLinkToVmessOptionIP(link, false)
   261  	}
   262  	return opt, nil
   263  }
   264  
   265  // TODO: safe base64
   266  func VmessLinkToVmessOptionIP(link string, resolveip bool) (*outbound.VmessOption, error) {
   267  	config, err := VmessLinkToVmessConfig(link, resolveip)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	return VmessConfigToVmessOption(config)
   272  }
   273  
   274  func VmessLinkToVmessConfig(link string, resolveip bool) (*VmessConfig, error) {
   275  	// FIXME:
   276  	regex := regexp.MustCompile(`^vmess://([A-Za-z0-9+-=/_]+)`)
   277  	res := regex.FindAllStringSubmatch(link, 1)
   278  	b64 := ""
   279  	if len(res) > 0 && len(res[0]) > 1 {
   280  		b64 = res[0][1]
   281  	}
   282  	data, err := utils.DecodeB64Bytes(b64)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  	config := VmessConfig{}
   287  	err = json.Unmarshal(data, &config)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	config.ResolveIP = resolveip
   292  	// parse raw message
   293  	if tls, err := rawMessageToTLS(config.TLSRaw); err == nil {
   294  		config.TLS = tls
   295  	}
   296  	return &config, nil
   297  }
   298  
   299  // parse shadowrocket link
   300  func ShadowrocketVmessLinkToVmessOptionIP(link string, resolveip bool) (*outbound.VmessOption, error) {
   301  	config, err := ShadowrocketVmessLinkToVmessConfig(link, resolveip)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	return VmessConfigToVmessOption(config)
   306  }
   307  
   308  func ShadowrocketLinkToVmessLink(link string) (string, error) {
   309  	config, err := ShadowrocketVmessLinkToVmessConfig(link, false)
   310  	if err != nil {
   311  		return "", err
   312  	}
   313  	src, err := json.Marshal(config)
   314  	if err != nil {
   315  		return "", err
   316  	}
   317  	return fmt.Sprintf("vmess://%s", base64.StdEncoding.EncodeToString(src)), nil
   318  }
   319  
   320  func ShadowrocketVmessLinkToVmessConfig(link string, resolveip bool) (*VmessConfig, error) {
   321  	if !RegShadowrocketVmess.MatchString(link) {
   322  		return nil, fmt.Errorf("not a vmess link: %s", link)
   323  	}
   324  	url, err := url.Parse(link)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	config := VmessConfig{}
   329  	config.V = []byte("2")
   330  
   331  	b64 := url.Host
   332  	b, err := utils.DecodeB64Bytes(b64)
   333  	if err != nil {
   334  		return shadowrocketVmessURLToVmessConfig(link, resolveip)
   335  	}
   336  
   337  	mhp := strings.SplitN(string(b), ":", 3)
   338  	if len(mhp) != 3 {
   339  		return nil, fmt.Errorf("vmess unrecognized: method:host:port -- %v", mhp)
   340  	}
   341  	config.Security = mhp[0]
   342  	// mhp[0] is the encryption method
   343  	config.Port = []byte(mhp[2])
   344  	idadd := strings.SplitN(mhp[1], "@", 2)
   345  	if len(idadd) != 2 {
   346  		return nil, fmt.Errorf("vmess unrecognized: id@addr -- %v", idadd)
   347  	}
   348  	config.ID = idadd[0]
   349  	config.Add = idadd[1]
   350  	config.Aid = []byte("0")
   351  
   352  	vals := url.Query()
   353  	if v := vals.Get("remarks"); v != "" {
   354  		config.Ps = v
   355  	}
   356  	if v := vals.Get("path"); v != "" {
   357  		config.Path = v
   358  	}
   359  	if v := vals.Get("tls"); v == "1" {
   360  		config.TLS = "tls"
   361  	}
   362  	if v := vals.Get("alterId"); v != "" {
   363  		config.Aid = []byte(v)
   364  		config.AlterId = []byte(v)
   365  	}
   366  	if v := vals.Get("obfs"); v != "" {
   367  		switch v {
   368  		case "websocket":
   369  			config.Net = "ws"
   370  		case "none":
   371  			config.Net = "tcp"
   372  			config.Type = "none"
   373  		}
   374  	}
   375  	if v := vals.Get("obfsParam"); v != "" {
   376  		config.Host = v
   377  	}
   378  	config.ResolveIP = resolveip
   379  	return &config, nil
   380  }
   381  
   382  func shadowrocketVmessURLToVmessConfig(link string, resolveip bool) (*VmessConfig, error) {
   383  	u, err := url.Parse(link)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  	if u.Scheme != "vmess" {
   388  		return nil, fmt.Errorf("not a vmess link: %s", link)
   389  	}
   390  	pass := u.User.Username()
   391  	hostport := u.Host
   392  	host, _, err := net.SplitHostPort(hostport)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	_, err = strconv.Atoi(u.Port())
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  
   401  	config := VmessConfig{
   402  		V:      []byte("2"),
   403  		ID:     pass,
   404  		Add:    host,
   405  		Port:   []byte(u.Port()),
   406  		Ps:     u.Fragment,
   407  		Net:    "tcp",
   408  		Aid:    []byte("0"),
   409  		Type:   "auto",
   410  		TLSRaw: []byte("false"),
   411  	}
   412  
   413  	vals, err := url.ParseQuery(u.RawQuery)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	if v := vals.Get("type"); v != "" {
   418  		config.Net = v
   419  	}
   420  
   421  	if v := vals.Get("encryption"); v != "" {
   422  		config.Encryption = v
   423  	}
   424  
   425  	if v := vals.Get("host"); v != "" {
   426  		config.Host = v
   427  	}
   428  
   429  	if v := vals.Get("path"); v != "" {
   430  		config.Path = v
   431  	}
   432  
   433  	if v := vals.Get("security"); v != "" {
   434  		config.TLS = v
   435  		config.TLSRaw = json.RawMessage("true")
   436  	}
   437  
   438  	if v := vals.Get("sni"); v != "" {
   439  		config.ServerName = v
   440  	}
   441  
   442  	if v := vals.Get("aid"); v != "" {
   443  		config.Aid = []byte(v)
   444  	}
   445  
   446  	config.ResolveIP = resolveip
   447  	return &config, nil
   448  }
   449  
   450  func VmessLinkToVmessConfigIP(link string, resolveip bool) (*VmessConfig, error) {
   451  	var config *VmessConfig
   452  	var err error
   453  	if strings.Contains(link, "&") {
   454  		config, err = ShadowrocketVmessLinkToVmessConfig(link, resolveip)
   455  	} else {
   456  		config, err = VmessLinkToVmessConfig(link, resolveip)
   457  	}
   458  	if err != nil {
   459  		return nil, err
   460  	}
   461  	port, err := rawMessageToInt(config.Port)
   462  	if err != nil {
   463  		port = 443
   464  	}
   465  	config.PortInt = port
   466  	aid, err := rawMessageToInt(config.Aid)
   467  	if err != nil {
   468  		aid, err = rawMessageToInt(config.AlterId)
   469  		if err != nil {
   470  			aid = 0
   471  		}
   472  	}
   473  	config.AidInt = aid
   474  	if resolveip {
   475  		if ipAddr, err := resolveIP(config.Add); err == nil && ipAddr != "" {
   476  			config.ServerName = config.Add
   477  			if config.Host == "" {
   478  				config.Host = config.ServerName
   479  			}
   480  			config.Add = ipAddr
   481  
   482  		}
   483  	}
   484  	return config, nil
   485  }
   486  
   487  func ToVmessOption(path string) (*outbound.VmessOption, error) {
   488  	data, err := os.ReadFile(path)
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  	config := RawConfig{}
   493  	err = json.Unmarshal(data, &config)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	if config.Outbounds != nil {
   498  		return RawConfigToVmessOption(&config)
   499  	}
   500  	config1 := VmessConfig{}
   501  	err = json.Unmarshal(data, &config1)
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  	return VmessConfigToVmessOption(&config1)
   506  }
   507  
   508  func init() {
   509  	outbound.RegisterDialerCreator("vmess", func(link string) (outbound.Dialer, error) {
   510  		vmessOption, err := VmessLinkToVmessOption(link)
   511  		if err != nil {
   512  			return nil, err
   513  		}
   514  		return outbound.NewVmess(vmessOption)
   515  	})
   516  }