mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
vmess links refactor
This commit is contained in:
parent
a66a89308a
commit
27a2d2c0f5
@ -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
37
common/link/number.go
Normal 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
|
||||
}
|
@ -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{}
|
||||
if v.TLS {
|
||||
out.VMessOptions.TLS = &option.OutboundTLSOptions{
|
||||
Insecure: v.TLSAllowInsecure,
|
||||
ServerName: v.Host,
|
||||
}
|
||||
}
|
||||
|
||||
switch v.Net {
|
||||
opt := &option.V2RayTransportOptions{
|
||||
Type: v.Transport,
|
||||
}
|
||||
|
||||
switch v.Transport {
|
||||
case "":
|
||||
opt = nil
|
||||
case "tcp", "h2", "http":
|
||||
opt.Type = C.V2RayTransportTypeHTTP
|
||||
opt.HTTPOptions.Path = strings.Split(v.Path, ",")[0]
|
||||
case C.V2RayTransportTypeHTTP:
|
||||
opt.HTTPOptions.Path = v.TransportPath
|
||||
if v.Host != "" {
|
||||
opt.HTTPOptions.Host = strings.Split(v.Host, ",")
|
||||
opt.HTTPOptions.Headers["Host"] = opt.HTTPOptions.Host[0]
|
||||
opt.HTTPOptions.Host = []string{v.Host}
|
||||
opt.HTTPOptions.Headers["Host"] = v.Host
|
||||
}
|
||||
case "ws":
|
||||
opt.Type = C.V2RayTransportTypeWebsocket
|
||||
opt.WebsocketOptions.Path = v.Path
|
||||
case C.V2RayTransportTypeWebsocket:
|
||||
opt.WebsocketOptions.Path = v.TransportPath
|
||||
opt.WebsocketOptions.Headers = map[string]string{
|
||||
"Host": v.Host,
|
||||
}
|
||||
}
|
||||
|
||||
if v.TLS == "tls" {
|
||||
out.VMessOptions.TLS = &option.OutboundTLSOptions{
|
||||
Insecure: true,
|
||||
ServerName: 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
92
common/link/vmess_ng.go
Normal 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
|
||||
}
|
48
common/link/vmess_ng_test.go
Normal file
48
common/link/vmess_ng_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
case "certificate":
|
||||
switch kvp[1] {
|
||||
case "0":
|
||||
l.TLSAllowInsecure = true
|
||||
default:
|
||||
return fmt.Errorf("unsupported quantumult vmess parameter: %s", pkv)
|
||||
l.TLSAllowInsecure = false
|
||||
}
|
||||
// default:
|
||||
// return fmt.Errorf("unsupported quantumult vmess parameter: %s", pkv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
48
common/link/vmess_quantumult_test.go
Normal file
48
common/link/vmess_quantumult_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
48
common/link/vmess_rocket_test.go
Normal file
48
common/link/vmess_rocket_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user