vmess links refactor

This commit is contained in:
jebbs 2022-10-17 14:52:59 +08:00
parent a66a89308a
commit 27a2d2c0f5
9 changed files with 360 additions and 120 deletions

View File

@ -1,44 +0,0 @@
package link
import (
"encoding/json"
"net/url"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
func init() {
common.Must(RegisterParser(&Parser{
Name: "V2RayNG",
Scheme: []string{"vmess"},
Parse: func(u *url.URL) (Link, error) {
link := &VMessV2RayNG{}
return link, link.Parse(u)
},
}))
}
// VMessV2RayNG is the vmess link of V2RayNG
type VMessV2RayNG struct {
vmess
}
// Parse implements Link
func (l *VMessV2RayNG) Parse(u *url.URL) error {
if u.Scheme != "vmess" {
return E.New("not a vmess link")
}
b64 := u.Host
b, err := base64Decode(b64)
if err != nil {
return err
}
if err := json.Unmarshal(b, l); err != nil {
return err
}
return nil
}

37
common/link/number.go Normal file
View File

@ -0,0 +1,37 @@
package link
import (
"encoding/json"
"fmt"
"strconv"
)
// number supports json unmarshaling from number or string
type number int64
// UnmarshalJSON implements json.Unmarshaler
func (i *number) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case string:
switch value {
case "", "null":
*i = 0
default:
var err error
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
*i = number(v)
}
case float64:
*i = number(value)
default:
return fmt.Errorf("invalid var int: %v", v)
}
return nil
}

View File

@ -1,67 +1,70 @@
package link
import (
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
type vmess struct {
Ver string `json:"v,omitempty"`
Add string `json:"add,omitempty"`
Aid int `json:"aid,omitempty"`
Host string `json:"host,omitempty"`
ID string `json:"id,omitempty"`
Net string `json:"net,omitempty"`
Path string `json:"path,omitempty"`
Port uint16 `json:"port,omitempty"`
Ps string `json:"ps,omitempty"`
TLS string `json:"tls,omitempty"`
Type string `json:"type,omitempty"`
// Vmess is the base struct of vmess link
type Vmess struct {
Tag string
Server string
ServerPort uint16
UUID string
AlterID int
Security string
Transport string
TransportPath string
Host string
TLS bool
TLSAllowInsecure bool
}
// Options implements Link
func (v *vmess) Options() *option.Outbound {
func (v *Vmess) Options() *option.Outbound {
out := &option.Outbound{
Type: "vmess",
Tag: v.Ps,
Tag: v.Tag,
VMessOptions: option.VMessOutboundOptions{
ServerOptions: option.ServerOptions{
Server: v.Add,
ServerPort: v.Port,
Server: v.Server,
ServerPort: v.ServerPort,
},
UUID: v.ID,
AlterId: v.Aid,
Security: "auto",
UUID: v.UUID,
AlterId: v.AlterID,
Security: v.Security,
},
}
opt := &option.V2RayTransportOptions{}
switch v.Net {
case "":
opt = nil
case "tcp", "h2", "http":
opt.Type = C.V2RayTransportTypeHTTP
opt.HTTPOptions.Path = strings.Split(v.Path, ",")[0]
if v.Host != "" {
opt.HTTPOptions.Host = strings.Split(v.Host, ",")
opt.HTTPOptions.Headers["Host"] = opt.HTTPOptions.Host[0]
}
case "ws":
opt.Type = C.V2RayTransportTypeWebsocket
opt.WebsocketOptions.Path = v.Path
opt.WebsocketOptions.Headers = map[string]string{
"Host": v.Host,
if v.TLS {
out.VMessOptions.TLS = &option.OutboundTLSOptions{
Insecure: v.TLSAllowInsecure,
ServerName: v.Host,
}
}
if v.TLS == "tls" {
out.VMessOptions.TLS = &option.OutboundTLSOptions{
Insecure: true,
ServerName: v.Host,
opt := &option.V2RayTransportOptions{
Type: v.Transport,
}
switch v.Transport {
case "":
opt = nil
case C.V2RayTransportTypeHTTP:
opt.HTTPOptions.Path = v.TransportPath
if v.Host != "" {
opt.HTTPOptions.Host = []string{v.Host}
opt.HTTPOptions.Headers["Host"] = v.Host
}
case C.V2RayTransportTypeWebsocket:
opt.WebsocketOptions.Path = v.TransportPath
opt.WebsocketOptions.Headers = map[string]string{
"Host": v.Host,
}
case C.V2RayTransportTypeQUIC:
// do nothing
case C.V2RayTransportTypeGRPC:
opt.GRPCOptions.ServiceName = v.Host
}
out.VMessOptions.Transport = opt

92
common/link/vmess_ng.go Normal file
View File

@ -0,0 +1,92 @@
package link
import (
"encoding/json"
"net/url"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
func init() {
common.Must(RegisterParser(&Parser{
Name: "V2RayNG",
Scheme: []string{"vmess"},
Parse: func(u *url.URL) (Link, error) {
link := &VMessV2RayNG{}
return link, link.Parse(u)
},
}))
}
// VMessV2RayNG is the vmess link of V2RayNG
type VMessV2RayNG struct {
Vmess
Ver string
}
type _vmessV2RayNG struct {
V number `json:"v,omitempty"`
Ps string `json:"ps,omitempty"`
Add string `json:"add,omitempty"`
Port number `json:"port,omitempty"`
ID string `json:"id,omitempty"`
Aid number `json:"aid,omitempty"`
Scy string `json:"scy,omitempty"`
Security string `json:"security,omitempty"`
SkipCertVerify bool `json:"skip-cert-verify,omitempty"`
Net string `json:"net,omitempty"`
Type string `json:"type,omitempty"`
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
TLS string `json:"tls,omitempty"`
SNI string `json:"sni,omitempty"`
ALPN string `json:"alpn,omitempty"`
}
// Parse implements Link
func (l *VMessV2RayNG) Parse(u *url.URL) error {
if u.Scheme != "vmess" {
return E.New("not a vmess link")
}
b64 := u.Host + u.Path
b, err := base64Decode(b64)
if err != nil {
return err
}
v := _vmessV2RayNG{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
l.Tag = v.Ps
l.Server = v.Add
l.ServerPort = uint16(v.Port)
l.UUID = v.ID
l.AlterID = int(v.Aid)
if v.Scy != "" {
l.Security = v.Scy
} else {
l.Security = v.Security
}
l.Host = v.Host
l.TransportPath = v.Path
l.TLS = v.TLS == "tls"
l.TLSAllowInsecure = v.SkipCertVerify
// _ = v.Type
// _ = v.SNI
// _ = v.ALPN
switch v.Net {
case "ws", "websocket":
l.Transport = C.V2RayTransportTypeWebsocket
case "http":
l.Transport = C.V2RayTransportTypeHTTP
}
return nil
}

View File

@ -0,0 +1,48 @@
package link_test
import (
"net/url"
"testing"
"github.com/sagernet/sing-box/common/link"
C "github.com/sagernet/sing-box/constant"
)
func TestVMessV2RayNG(t *testing.T) {
tests := []struct {
link string
want link.Vmess
}{
{
link: "vmess://ewoiYWRkIjogIjE5Mi4xNjguMTAwLjEiLAoidiI6ICIyIiwKInBzIjogInBzIiwKInBvcnQiOiA0NDMsCiJpZCI6ICJ1dWlkIiwKImFpZCI6ICI0IiwKIm5ldCI6ICJ3cyIsCiJ0eXBlIjogInR5cGUiLAoiaG9zdCI6ICJob3N0IiwKInBhdGgiOiAiL3BhdGgiLAoidGxzIjogInRscyIsCiJzbmkiOiAic25pIiwKImFscG4iOiJhbHBuIiwKInNlY3VyaXR5IjogImF1dG8iLAoic2tpcC1jZXJ0LXZlcmlmeSI6IGZhbHNlCn0=",
want: link.Vmess{
Tag: "ps",
Server: "192.168.100.1",
ServerPort: 443,
UUID: "uuid",
AlterID: 4,
Security: "auto",
Host: "host",
Transport: C.V2RayTransportTypeWebsocket,
TransportPath: "/path",
TLS: true,
TLSAllowInsecure: false,
},
},
}
for _, tt := range tests {
u, err := url.Parse(tt.link)
if err != nil {
t.Fatal(err)
}
link := link.VMessV2RayNG{}
err = link.Parse(u)
if err != nil {
t.Error(err)
return
}
if link.Vmess != tt.want {
t.Errorf("want %#v, got %#v", tt.want, link.Vmess)
}
}
}

View File

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
@ -23,7 +24,7 @@ func init() {
// VMessQuantumult is the vmess link of Quantumult
type VMessQuantumult struct {
vmess
Vmess
}
// Parse implements Link
@ -31,31 +32,30 @@ func (l *VMessQuantumult) Parse(u *url.URL) error {
if u.Scheme != "vmess" {
return E.New("not a vmess link")
}
b, err := base64Decode(u.Host)
b, err := base64Decode(u.Host + u.Path)
if err != nil {
return err
}
info := string(b)
l.Ver = "2"
psn := strings.SplitN(info, " = ", 2)
if len(psn) != 2 {
return fmt.Errorf("part error: %s", info)
}
l.Ps = psn[0]
l.Tag = psn[0]
params := strings.Split(psn[1], ",")
port, err := strconv.ParseUint(params[2], 10, 16)
if err != nil {
return E.Cause(err, "invalid port")
}
l.Add = params[1]
l.Port = uint16(port)
l.ID = strings.Trim(params[4], "\"")
l.Aid = 0
l.Net = "tcp"
l.Type = "none"
l.Server = params[1]
l.ServerPort = uint16(port)
l.Security = params[3]
l.UUID = strings.Trim(params[4], "\"")
l.AlterID = 0
l.Transport = ""
if len(params) > 4 {
for _, pkv := range params[5:] {
@ -63,30 +63,36 @@ func (l *VMessQuantumult) Parse(u *url.URL) error {
switch kvp[0] {
case "over-tls":
if kvp[1] == "true" {
l.TLS = "tls"
l.TLS = true
}
case "obfs":
switch kvp[1] {
case "ws", "http":
l.Net = kvp[1]
case "ws":
l.Transport = C.V2RayTransportTypeWebsocket
case "http":
l.Transport = C.V2RayTransportTypeHTTP
default:
return fmt.Errorf("unsupported quantumult vmess obfs parameter: %s", kvp[1])
}
case "obfs-path":
l.Path = strings.Trim(kvp[1], "\"")
l.TransportPath = strings.Trim(kvp[1], "\"")
case "obfs-header":
hd := strings.Trim(kvp[1], "\"")
for _, hl := range strings.Split(hd, "[Rr][Nn]") {
if strings.HasPrefix(hl, "Host:") {
host := hl[5:]
if host != l.Add {
l.Host = host
}
l.Host = hl[5:]
break
}
}
default:
return fmt.Errorf("unsupported quantumult vmess parameter: %s", pkv)
case "certificate":
switch kvp[1] {
case "0":
l.TLSAllowInsecure = true
default:
l.TLSAllowInsecure = false
}
// default:
// return fmt.Errorf("unsupported quantumult vmess parameter: %s", pkv)
}
}
}

View File

@ -0,0 +1,48 @@
package link_test
import (
"net/url"
"testing"
"github.com/sagernet/sing-box/common/link"
C "github.com/sagernet/sing-box/constant"
)
func TestVMessQuantumult(t *testing.T) {
tests := []struct {
link string
want link.Vmess
}{
{
link: "vmess://cHMgPSB2bWVzcywxOTIuMTY4LjEwMC4xLDQ0MyxhZXMtMTI4LWdjbSwidXVpZCIsb3Zlci10bHM9dHJ1ZSxjZXJ0aWZpY2F0ZT0wLG9iZnM9d3Msb2Jmcy1wYXRoPSIvcGF0aCIsb2Jmcy1oZWFkZXI9Ikhvc3Q6aG9zdFtScl1bTm5dd2hhdGV2ZXI=",
want: link.Vmess{
Tag: "ps",
Server: "192.168.100.1",
ServerPort: 443,
UUID: "uuid",
AlterID: 0,
Security: "aes-128-gcm",
Host: "host",
Transport: C.V2RayTransportTypeWebsocket,
TransportPath: "/path",
TLS: true,
TLSAllowInsecure: true,
},
},
}
for _, tt := range tests {
u, err := url.Parse(tt.link)
if err != nil {
t.Fatal(err)
}
link := link.VMessQuantumult{}
err = link.Parse(u)
if err != nil {
t.Error(err)
return
}
if link.Vmess != tt.want {
t.Errorf("want %#v, got %#v", tt.want, link.Vmess)
}
}
}

View File

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
@ -25,7 +26,9 @@ func init() {
// VMessRocket is the vmess link of ShadowRocket
type VMessRocket struct {
vmess
Vmess
Ver string
}
// Parse implements Link
@ -50,35 +53,34 @@ func (l *VMessRocket) Parse(u *url.URL) error {
return E.Cause(err, "invalid port")
}
// mhp[0] is the encryption method
l.Port = uint16(port)
l.ServerPort = uint16(port)
idadd := strings.SplitN(mhp[1], "@", 2)
if len(idadd) != 2 {
return fmt.Errorf("vmess unreconized: id@addr -- %v", idadd)
}
l.ID = idadd[0]
l.Add = idadd[1]
l.Aid = 0
l.UUID = idadd[0]
l.Server = idadd[1]
l.AlterID = 0
l.Security = "auto"
for key, values := range u.Query() {
switch key {
case "remarks":
l.Ps = firstValueOf(values)
l.Tag = firstValueOf(values)
case "path":
l.Path = firstValueOf(values)
l.TransportPath = firstValueOf(values)
case "tls":
l.TLS = firstValueOf(values)
l.TLS = firstValueOf(values) == "tls"
case "obfs":
v := firstValueOf(values)
switch v {
case "websocket":
l.Net = "ws"
case "none":
l.Net = ""
case "ws", "websocket":
l.Transport = C.V2RayTransportTypeWebsocket
case "http":
l.Transport = C.V2RayTransportTypeHTTP
}
case "obfsParam":
l.Host = firstValueOf(values)
default:
return fmt.Errorf("unsupported shadowrocket vmess parameter: %s=%v", key, values)
}
}
return nil

View File

@ -0,0 +1,48 @@
package link_test
import (
"net/url"
"testing"
"github.com/sagernet/sing-box/common/link"
C "github.com/sagernet/sing-box/constant"
)
func TestVMessRocket(t *testing.T) {
tests := []struct {
link string
want link.Vmess
}{
{
link: "vmess://YXV0bzp1dWlkQDE5Mi4xNjguMTAwLjE6NDQz/?remarks=remarks&obfs=ws&path=/path&obfsParam=host&tls=tls",
want: link.Vmess{
Tag: "remarks",
Server: "192.168.100.1",
ServerPort: 443,
UUID: "uuid",
AlterID: 0,
Security: "auto",
Host: "host",
Transport: C.V2RayTransportTypeWebsocket,
TransportPath: "/path",
TLS: true,
TLSAllowInsecure: false,
},
},
}
for _, tt := range tests {
u, err := url.Parse(tt.link)
if err != nil {
t.Fatal(err)
}
link := link.VMessRocket{}
err = link.Parse(u)
if err != nil {
t.Error(err)
return
}
if link.Vmess != tt.want {
t.Errorf("want %#v, got %#v", tt.want, link.Vmess)
}
}
}