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 }