mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 05:44:12 +08:00
shadowsocks SIP002
This commit is contained in:
parent
917a33a190
commit
a66a89308a
@ -1,7 +1,6 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -11,21 +10,52 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var _ Link = (*SSLink)(nil)
|
||||
var _ Link = (*ShadowSocks)(nil)
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterParser(&Parser{
|
||||
Name: "Shadowsocks",
|
||||
Scheme: []string{"ss"},
|
||||
Parse: func(u *url.URL) (Link, error) {
|
||||
link := &SSLink{}
|
||||
link := &ShadowSocks{}
|
||||
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
|
||||
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" {
|
||||
return E.New("not a ss link")
|
||||
}
|
||||
@ -39,14 +69,26 @@ func (l *SSLink) Parse(u *url.URL) error {
|
||||
queries := u.Query()
|
||||
for key, values := range queries {
|
||||
switch key {
|
||||
default:
|
||||
return fmt.Errorf("unsupported shadowsocks parameter: %s=%v", key, values)
|
||||
case "plugin":
|
||||
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 pass, ok := u.User.Password(); ok {
|
||||
l.Method = uname
|
||||
l.Password = pass
|
||||
method, err := url.QueryUnescape(uname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password, err := url.QueryUnescape(pass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Method = method
|
||||
l.Password = password
|
||||
} else {
|
||||
dec, err := base64Decode(uname)
|
||||
if err != nil {
|
||||
@ -61,28 +103,3 @@ func (l *SSLink) Parse(u *url.URL) error {
|
||||
}
|
||||
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