dae/component/outbound/dialer/shadowsocks/shadowsocks.go
2023-03-14 15:01:55 +08:00

253 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package shadowsocks
import (
"encoding/base64"
"fmt"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/protocol/shadowsocks"
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/daeuniverse/dae/component/outbound/transport/simpleobfs"
"net"
"net/url"
"strconv"
"strings"
)
func init() {
// Use random salt by default to decrease the boot time
shadowsocks.DefaultSaltGeneratorType = shadowsocks.RandomSaltGeneratorType
dialer.FromLinkRegister("shadowsocks", NewShadowsocksFromLink)
dialer.FromLinkRegister("ss", NewShadowsocksFromLink)
}
type Shadowsocks struct {
Name string `json:"name"`
Server string `json:"server"`
Port int `json:"port"`
Password string `json:"password"`
Cipher string `json:"cipher"`
Plugin Sip003 `json:"plugin"`
UDP bool `json:"udp"`
Protocol string `json:"protocol"`
}
func NewShadowsocksFromLink(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseSSURL(link)
if err != nil {
return nil, err
}
return s.Dialer(option, iOption)
}
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
var err error
var d netproxy.Dialer
switch s.Plugin.Name {
case "simple-obfs":
d = direct.SymmetricDirect // Simple-obfs does not supports UDP.
switch s.Plugin.Opts.Obfs {
case "http", "tls":
default:
return nil, fmt.Errorf("unsupported obfs %v of plugin %v", s.Plugin.Opts.Obfs, s.Plugin.Name)
}
host := s.Plugin.Opts.Host
if host == "" {
host = "cloudflare.com"
}
path := s.Plugin.Opts.Path
uSimpleObfs := url.URL{
Scheme: "simple-obfs",
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
RawQuery: url.Values{
"obfs": []string{s.Plugin.Opts.Obfs},
"host": []string{host},
"uri": []string{path},
}.Encode(),
}
d, err = simpleobfs.NewSimpleObfs(uSimpleObfs.String(), d)
if err != nil {
return nil, err
}
default:
d = direct.FullconeDirect // Shadowsocks Proxy supports full-cone.
}
var nextDialerName string
switch s.Cipher {
case "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305":
nextDialerName = "shadowsocks"
case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "des-cfb", "bf-cfb", "cast5-cfb", "rc4-md5", "rc4-md5-6", "chacha20", "chacha20-ietf", "salsa20", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "rc4", "none", "plain":
nextDialerName = "shadowsocks_stream"
default:
return nil, fmt.Errorf("unsupported shadowsocks encryption method: %v", s.Cipher)
}
d, err = protocol.NewDialer(nextDialerName, d, protocol.Header{
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Cipher: s.Cipher,
Password: s.Password,
IsClient: true,
})
if err != nil {
return nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}
func ParseSSURL(u string) (data *Shadowsocks, err error) {
// parse attempts to parse ss:// links
parse := func(content string) (v *Shadowsocks, ok bool) {
// try to parse in the format of ss://BASE64(method:password)@server:port/?plugin=xxxx#name
u, err := url.Parse(content)
if err != nil {
return nil, false
}
username := u.User.String()
username, _ = common.Base64UrlDecode(username)
arr := strings.SplitN(username, ":", 2)
if len(arr) != 2 {
return nil, false
}
cipher := arr[0]
password := arr[1]
var sip003 Sip003
plugin := u.Query().Get("plugin")
if len(plugin) > 0 {
sip003 = ParseSip003(plugin)
}
port, err := strconv.Atoi(u.Port())
if err != nil {
return nil, false
}
return &Shadowsocks{
Cipher: strings.ToLower(cipher),
Password: password,
Server: u.Hostname(),
Port: port,
Name: u.Fragment,
Plugin: sip003,
UDP: sip003.Name == "",
Protocol: "shadowsocks",
}, true
}
var (
v *Shadowsocks
ok bool
)
content := u
// try to parse the ss:// link, if it fails, base64 decode first
if v, ok = parse(content); !ok {
// 进行base64解码并unmarshal到VmessInfo上
t := content[5:]
var l, r string
if ind := strings.Index(t, "#"); ind > -1 {
l = t[:ind]
r = t[ind+1:]
} else {
l = t
}
l, err = common.Base64StdDecode(l)
if err != nil {
l, err = common.Base64UrlDecode(l)
if err != nil {
return
}
}
t = "ss://" + l
if len(r) > 0 {
t += "#" + r
}
v, ok = parse(t)
}
if !ok {
return nil, fmt.Errorf("%w: unrecognized ss address", dialer.InvalidParameterErr)
}
return v, nil
}
type Sip003 struct {
Name string `json:"name"`
Opts Sip003Opts `json:"opts"`
}
type Sip003Opts struct {
Tls string `json:"tls"` // for v2ray-plugin
Obfs string `json:"obfs"`
Host string `json:"host"`
Path string `json:"uri"`
}
func ParseSip003Opts(opts string) Sip003Opts {
var sip003Opts Sip003Opts
fields := strings.Split(opts, ";")
for i := range fields {
a := strings.Split(fields[i], "=")
if len(a) == 1 {
// to avoid panic
a = append(a, "")
}
switch a[0] {
case "tls":
sip003Opts.Tls = "tls"
case "obfs", "mode":
sip003Opts.Obfs = a[1]
case "obfs-path", "obfs-uri", "path":
if !strings.HasPrefix(a[1], "/") {
a[1] += "/"
}
sip003Opts.Path = a[1]
case "obfs-host", "host":
sip003Opts.Host = a[1]
}
}
return sip003Opts
}
func ParseSip003(plugin string) Sip003 {
var sip003 Sip003
fields := strings.SplitN(plugin, ";", 2)
switch fields[0] {
case "obfs-local", "simpleobfs":
sip003.Name = "simple-obfs"
default:
sip003.Name = fields[0]
}
sip003.Opts = ParseSip003Opts(fields[1])
return sip003
}
func (s *Sip003) String() string {
list := []string{s.Name}
if s.Opts.Obfs != "" {
list = append(list, "obfs="+s.Opts.Obfs)
}
if s.Opts.Host != "" {
list = append(list, "obfs-host="+s.Opts.Host)
}
if s.Opts.Path != "" {
list = append(list, "obfs-uri="+s.Opts.Path)
}
return strings.Join(list, ";")
}
func (s *Shadowsocks) ExportToURL() string {
// sip002
u := &url.URL{
Scheme: "ss",
User: url.User(strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte(s.Cipher+":"+s.Password)), "=")),
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Fragment: s.Name,
}
if s.Plugin.Name != "" {
q := u.Query()
q.Set("plugin", s.Plugin.String())
u.RawQuery = q.Encode()
}
return u.String()
}