package shadowsocksr import ( "encoding/base64" "fmt" "net" "net/url" "strconv" "strings" "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/component/outbound/dialer" "github.com/daeuniverse/softwind/netproxy" "github.com/daeuniverse/softwind/protocol" "github.com/daeuniverse/softwind/protocol/shadowsocks_stream" "github.com/daeuniverse/softwind/transport/shadowsocksr/obfs" "github.com/daeuniverse/softwind/transport/shadowsocksr/proto" ) func init() { dialer.FromLinkRegister("shadowsocksr", NewShadowsocksR) dialer.FromLinkRegister("ssr", NewShadowsocksR) } type ShadowsocksR struct { Name string `json:"name"` Server string `json:"server"` Port int `json:"port"` Password string `json:"password"` Cipher string `json:"cipher"` Proto string `json:"proto"` ProtoParam string `json:"protoParam"` Obfs string `json:"obfs"` ObfsParam string `json:"obfsParam"` Protocol string `json:"protocol"` } func NewShadowsocksR(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { s, err := ParseSSRURL(link) if err != nil { return nil, nil, err } return s.Dialer(option, nextDialer) } func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { d := nextDialer obfsDialer, err := obfs.NewDialer(d, &obfs.ObfsParam{ ObfsHost: s.Server, ObfsPort: uint16(s.Port), Obfs: s.Obfs, ObfsParam: s.ObfsParam, }) if err != nil { return nil, nil, err } d = obfsDialer d, err = shadowsocks_stream.NewDialer(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, nil, err } d = &proto.Dialer{ NextDialer: d, Protocol: s.Proto, ProtocolParam: s.ProtoParam, ObfsOverhead: obfsDialer.ObfsOverhead(), } return d, &dialer.Property{ Name: s.Name, Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), Protocol: s.Protocol, Link: s.ExportToURL(), }, nil } func ParseSSRURL(u string) (data *ShadowsocksR, err error) { // parse attempts to parse ss:// links parse := func(content string) (v ShadowsocksR, ok bool) { arr := strings.Split(content, "/?") if strings.Contains(content, ":") && len(arr) < 2 { content += "/?remarks=&protoparam=&obfsparam=" arr = strings.Split(content, "/?") } else if len(arr) != 2 { return v, false } pre := strings.Split(arr[0], ":") if len(pre) > 6 { //if the length is more than 6, it means that the host contains the characters:, //re-merge the first few groups into the host pre[len(pre)-6] = strings.Join(pre[:len(pre)-5], ":") pre = pre[len(pre)-6:] } else if len(pre) < 6 { return v, false } q, err := url.ParseQuery(arr[1]) if err != nil { return v, false } pswd, _ := common.Base64UrlDecode(pre[5]) add, _ := common.Base64UrlDecode(pre[0]) remarks, _ := common.Base64UrlDecode(q.Get("remarks")) protoparam, _ := common.Base64UrlDecode(q.Get("protoparam")) obfsparam, _ := common.Base64UrlDecode(q.Get("obfsparam")) port, err := strconv.Atoi(pre[1]) if err != nil { return v, false } v = ShadowsocksR{ Name: remarks, Server: add, Port: port, Password: pswd, Cipher: pre[3], Proto: pre[2], ProtoParam: protoparam, Obfs: pre[4], ObfsParam: obfsparam, Protocol: "shadowsocksr", } return v, true } content := u[6:] var ( info ShadowsocksR ok bool ) // try parsing the ssr:// link, if it fails, base64 decode first if info, ok = parse(content); !ok { // perform base64 decoding and parse again content, err = common.Base64StdDecode(content) if err != nil { content, err = common.Base64UrlDecode(content) if err != nil { return } } info, ok = parse(content) } if !ok { err = fmt.Errorf("%w: unrecognized ssr address", dialer.InvalidParameterErr) return } return &info, nil } func (s *ShadowsocksR) ExportToURL() string { /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ return fmt.Sprintf("ssr://%v", strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte( fmt.Sprintf( "%v:%v:%v:%v:%v/?remarks=%v&protoparam=%v&obfsparam=%v", net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), s.Proto, s.Cipher, s.Obfs, base64.URLEncoding.EncodeToString([]byte(s.Password)), base64.URLEncoding.EncodeToString([]byte(s.Name)), base64.URLEncoding.EncodeToString([]byte(s.ProtoParam)), base64.URLEncoding.EncodeToString([]byte(s.ObfsParam)), ), )), "=")) }