Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V5: ping & links command #485

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions infra/link/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package link

import (
"errors"
"fmt"
"strings"

"v2ray.com/core/infra/conf"
)

// Link is the interface for v2ray links
type Link interface {
// returns the tag of the link
Tag() string
// Detail returns human readable string of VmessLink
Detail() string
// ToOutbound converts the vmess link to *OutboundDetourConfig
ToOutbound() *conf.OutboundDetourConfig
// ToString unmarshals Link to string
ToString() string
}

// Parse parses a link string to Link
func Parse(arg string) (Link, error) {
ps, err := getParsers(arg)
if err != nil {
return nil, err
}
errs := new(strings.Builder)
errs.WriteString("collected errors:")
for _, p := range ps {
lk, err := p.Parse(arg)
if err == nil {
return lk, nil
}
errs.WriteString(fmt.Sprintf("\n not a valid %s link: %s", p.Name, err))
}
return nil, errors.New(errs.String())
}
53 changes: 53 additions & 0 deletions infra/link/parsers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package link

import (
"fmt"
"net/url"
"strings"

"v2ray.com/core/common/errors"
)

// ParseFunc is parser function to load v2ray links, like "vmess://..."
type ParseFunc func(input string) (Link, error)

// Parser is parser load v2ray links with specified schemes
type Parser struct {
Name string
Scheme []string
Parse ParseFunc
}

var (
parsers = make(map[string][]*Parser)
)

// RegisterParser add a new link parser.
func RegisterParser(parser *Parser) error {
for _, scheme := range parser.Scheme {
s := strings.ToLower(scheme)
ps := parsers[s]
if len(ps) == 0 {
ps = make([]*Parser, 0)
}
parsers[s] = append(ps, parser)
}

return nil
}

func getParsers(link string) ([]*Parser, error) {
u, err := url.Parse(link)
if err != nil {
return nil, err
}
if u.Scheme == "" {
return nil, errors.New("invalid link")
}
s := strings.ToLower(u.Scheme)
ps := parsers[s]
if len(ps) == 0 {
return nil, fmt.Errorf("unsupported link scheme: %s", u.Scheme)
}
return ps, nil
}
20 changes: 20 additions & 0 deletions infra/link/vmess.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package link

import (
"v2ray.com/core/common"
"v2ray.com/core/common/errors"
)

func init() {
common.Must(RegisterParser(&Parser{
Name: "Official",
Scheme: []string{"vmess"},
Parse: ParseVmess,
}))
}

// ParseVmess parses official vemss link to Link
func ParseVmess(vmess string) (Link, error) {
// TODO: Official vmess:// parse support
return nil, errors.New("not implemented")
}
20 changes: 20 additions & 0 deletions infra/link/vmess3p/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package vmess3p

import (
"encoding/base64"
"strings"
)

func base64Decode(b64 string) ([]byte, error) {
b64 = strings.TrimSpace(b64)
stdb64 := b64
if pad := len(b64) % 4; pad != 0 {
stdb64 += strings.Repeat("=", 4-pad)
}

b, err := base64.StdEncoding.DecodeString(stdb64)
if err != nil {
return base64.URLEncoding.DecodeString(b64)
}
return b, nil
}
48 changes: 48 additions & 0 deletions infra/link/vmess3p/ng.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package vmess3p

import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"

"v2ray.com/core/common"
"v2ray.com/core/infra/link"
)

func init() {
common.Must(link.RegisterParser(&link.Parser{
Name: "V2RayNG",
Scheme: []string{"vmess"},
Parse: func(input string) (link.Link, error) {
return NewVnVmess(input)
},
}))
}

// ToNgLink converts to V2RayNG link string
func (v TPLink) ToNgLink() string {
b, _ := json.Marshal(v)
return "vmess://" + base64.StdEncoding.EncodeToString(b)
}

// NewVnVmess parses V2RayN vemss link to VmessLink
func NewVnVmess(vmess string) (*TPLink, error) {
if !strings.HasPrefix(vmess, "vmess://") {
return nil, fmt.Errorf("vmess unreconized: %s", vmess)
}

b64 := vmess[8:]
b, err := base64Decode(b64)
if err != nil {
return nil, err
}

v := &TPLink{}
if err := json.Unmarshal(b, v); err != nil {
return nil, err
}
v.OrigLink = vmess

return v, nil
}
112 changes: 112 additions & 0 deletions infra/link/vmess3p/quantumult.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package vmess3p

import (
"encoding/base64"
"fmt"
"strings"

"v2ray.com/core/common"
"v2ray.com/core/infra/link"
)

func init() {
common.Must(link.RegisterParser(&link.Parser{
Name: "Quantumult",
Scheme: []string{"vmess"},
Parse: func(input string) (link.Link, error) {
return NewQuanVmess(input)
},
}))
}

// ToQuantumult converts to Quantumult link string
func (v TPLink) ToQuantumult() string {
/*
let obfs = `,obfs=${jsonConf.net === 'ws' ? 'ws' : 'http'},obfs-path="${jsonConf.path || '/'}",obfs-header="Host:${jsonConf.host || jsonConf.add}[Rr][Nn]User-Agent:${ua}"`
let quanVmess = `${jsonConf.ps} = vmess,${jsonConf.add},${jsonConf.port},${method},"${jsonConf.id}",over-tls=${jsonConf.tls === 'tls' ? 'true' : 'false'},certificate=1${jsonConf.type === 'none' && jsonConf.net !== 'ws' ? '' : obfs},group=${group}`
*/

method := "aes-128-gcm"
vbase := fmt.Sprintf("%s = vmess,%s,%s,%s,\"%s\",over-tls=%v,certificate=1", v.Ps, v.Add, v.Port, method, v.ID, v.TLS == "tls")

var obfs string
if (v.Net == "ws" || v.Net == "http") && (v.Type == "none" || v.Type == "") {
if v.Path == "" {
v.Path = "/"
}
if v.Host == "" {
v.Host = v.Add
}
obfs = fmt.Sprintf(`,obfs=ws,obfs-path="%s",obfs-header="Host:%s[Rr][Nn]User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A5366a"`, v.Path, v.Host)
}

vbase += obfs
vbase += ",group=Fndroid"
return "vmess://" + base64.URLEncoding.EncodeToString([]byte(vbase))
}

// NewQuanVmess parses Quantumult vemss link to VmessLink
func NewQuanVmess(vmess string) (*TPLink, error) {
if !strings.HasPrefix(vmess, "vmess://") {
return nil, fmt.Errorf("vmess unreconized: %s", vmess)
}
b64 := vmess[8:]
b, err := base64Decode(b64)
if err != nil {
return nil, err
}

info := string(b)
v := &TPLink{}
v.OrigLink = vmess
v.Ver = "2"

psn := strings.SplitN(info, " = ", 2)
if len(psn) != 2 {
return nil, fmt.Errorf("part error: %s", info)
}
v.Ps = psn[0]
params := strings.Split(psn[1], ",")
v.Add = params[1]
v.Port = params[2]
v.ID = strings.Trim(params[4], "\"")
v.Aid = "0"
v.Net = "tcp"
v.Type = "none"

if len(params) > 4 {
for _, pkv := range params[5:] {
kvp := strings.SplitN(pkv, "=", 2)
if kvp[0] == "over-tls" && kvp[1] == "true" {
v.TLS = "tls"
}

if kvp[0] == "obfs" && kvp[1] == "ws" {
v.Net = "ws"
}

if kvp[0] == "obfs" && kvp[1] == "http" {
v.Type = "http"
}

if kvp[0] == "obfs-path" {
v.Path = strings.Trim(kvp[1], "\"")
}

if kvp[0] == "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 != v.Add {
v.Host = host
}
break
}
}
}
}
}

return v, nil
}
Loading