mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
shadowsocks SIP002
This commit is contained in:
parent
917a33a190
commit
a66a89308a
@ -1,7 +1,6 @@
|
|||||||
package link
|
package link
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -11,21 +10,52 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Link = (*SSLink)(nil)
|
var _ Link = (*ShadowSocks)(nil)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(RegisterParser(&Parser{
|
common.Must(RegisterParser(&Parser{
|
||||||
Name: "Shadowsocks",
|
Name: "Shadowsocks",
|
||||||
Scheme: []string{"ss"},
|
Scheme: []string{"ss"},
|
||||||
Parse: func(u *url.URL) (Link, error) {
|
Parse: func(u *url.URL) (Link, error) {
|
||||||
link := &SSLink{}
|
link := &ShadowSocks{}
|
||||||
return link, link.Parse(u)
|
return link, link.Parse(u)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShadowSocks represents a parsed shadowsocks link
|
||||||
|
type ShadowSocks struct {
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Port uint16 `json:"port,omitempty"`
|
||||||
|
Ps string `json:"ps,omitempty"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
|
PluginOpts string `json:"plugin-opts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options implements Link
|
||||||
|
func (l *ShadowSocks) Options() *option.Outbound {
|
||||||
|
return &option.Outbound{
|
||||||
|
Type: "shadowsocks",
|
||||||
|
Tag: l.Ps,
|
||||||
|
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: l.Address,
|
||||||
|
ServerPort: l.Port,
|
||||||
|
},
|
||||||
|
Method: l.Method,
|
||||||
|
Password: l.Password,
|
||||||
|
Plugin: l.Plugin,
|
||||||
|
PluginOptions: l.PluginOpts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse implements Link
|
// Parse implements Link
|
||||||
func (l *SSLink) Parse(u *url.URL) error {
|
//
|
||||||
|
// https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
|
func (l *ShadowSocks) Parse(u *url.URL) error {
|
||||||
if u.Scheme != "ss" {
|
if u.Scheme != "ss" {
|
||||||
return E.New("not a ss link")
|
return E.New("not a ss link")
|
||||||
}
|
}
|
||||||
@ -39,14 +69,26 @@ func (l *SSLink) Parse(u *url.URL) error {
|
|||||||
queries := u.Query()
|
queries := u.Query()
|
||||||
for key, values := range queries {
|
for key, values := range queries {
|
||||||
switch key {
|
switch key {
|
||||||
default:
|
case "plugin":
|
||||||
return fmt.Errorf("unsupported shadowsocks parameter: %s=%v", key, values)
|
parts := strings.SplitN(values[0], ";", 2)
|
||||||
|
l.Plugin = parts[0]
|
||||||
|
if len(parts) == 2 {
|
||||||
|
l.PluginOpts = parts[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if uname := u.User.Username(); uname != "" {
|
if uname := u.User.Username(); uname != "" {
|
||||||
if pass, ok := u.User.Password(); ok {
|
if pass, ok := u.User.Password(); ok {
|
||||||
l.Method = uname
|
method, err := url.QueryUnescape(uname)
|
||||||
l.Password = pass
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
password, err := url.QueryUnescape(pass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Method = method
|
||||||
|
l.Password = password
|
||||||
} else {
|
} else {
|
||||||
dec, err := base64Decode(uname)
|
dec, err := base64Decode(uname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -61,28 +103,3 @@ func (l *SSLink) Parse(u *url.URL) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSLink represents a parsed shadowsocks link
|
|
||||||
type SSLink struct {
|
|
||||||
Method string `json:"method,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
Address string `json:"address,omitempty"`
|
|
||||||
Port uint16 `json:"port,omitempty"`
|
|
||||||
Ps string `json:"ps,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options implements Link
|
|
||||||
func (l *SSLink) Options() *option.Outbound {
|
|
||||||
return &option.Outbound{
|
|
||||||
Type: "shadowsocks",
|
|
||||||
Tag: l.Ps,
|
|
||||||
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
|
|
||||||
ServerOptions: option.ServerOptions{
|
|
||||||
Server: l.Address,
|
|
||||||
ServerPort: l.Port,
|
|
||||||
},
|
|
||||||
Method: l.Method,
|
|
||||||
Password: l.Password,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
75
common/link/shadowsocks_test.go
Normal file
75
common/link/shadowsocks_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package link_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/link"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShadowSocks(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
link string
|
||||||
|
want link.ShadowSocks
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
link: "ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888#Example1",
|
||||||
|
want: link.ShadowSocks{
|
||||||
|
Address: "192.168.100.1",
|
||||||
|
Port: 8888,
|
||||||
|
Ps: "Example1",
|
||||||
|
Method: "aes-128-gcm",
|
||||||
|
Password: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: "ss://cmM0LW1kNTpwYXNzd2Q@192.168.100.1:8888/?plugin=obfs-local%3Bobfs%3Dhttp%3Bobfs-host=abc.com#Example2",
|
||||||
|
want: link.ShadowSocks{
|
||||||
|
Address: "192.168.100.1",
|
||||||
|
Port: 8888,
|
||||||
|
Ps: "Example2",
|
||||||
|
Method: "rc4-md5",
|
||||||
|
Password: "passwd",
|
||||||
|
Plugin: "obfs-local",
|
||||||
|
PluginOpts: "obfs=http;obfs-host=abc.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: "ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888#Example3",
|
||||||
|
want: link.ShadowSocks{
|
||||||
|
Address: "192.168.100.1",
|
||||||
|
Port: 8888,
|
||||||
|
Ps: "Example3",
|
||||||
|
Method: "2022-blake3-aes-256-gcm",
|
||||||
|
Password: "YctPZ6U7xPPcU gp3u 0tx/tRizJN9K8y uKlW2qjlI=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: "ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888/?plugin=v2ray-plugin%3Bserver&unsupported-arguments=should-be-ignored#Example3",
|
||||||
|
want: link.ShadowSocks{
|
||||||
|
Address: "192.168.100.1",
|
||||||
|
Port: 8888,
|
||||||
|
Ps: "Example3",
|
||||||
|
Method: "2022-blake3-aes-256-gcm",
|
||||||
|
Password: "YctPZ6U7xPPcU gp3u 0tx/tRizJN9K8y uKlW2qjlI=",
|
||||||
|
Plugin: "v2ray-plugin",
|
||||||
|
PluginOpts: "server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
u, err := url.Parse(tt.link)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
link := link.ShadowSocks{}
|
||||||
|
err = link.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if link != tt.want {
|
||||||
|
t.Errorf("want %v, got %v", tt.want, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user