Add obfs-local and v2ray-plugin support for shadowsocks outbound

This commit is contained in:
世界
2022-09-12 14:51:20 +08:00
parent 5a9913eca5
commit ce567ffdde
9 changed files with 608 additions and 1 deletions

119
transport/sip003/args.go Normal file
View File

@@ -0,0 +1,119 @@
package sip003
import (
"bytes"
"fmt"
)
// mod from https://github.com/shadowsocks/v2ray-plugin/blob/master/args.go
// Args maps a string key to a list of values. It is similar to url.Values.
type Args map[string][]string
// Get the first value associated with the given key. If there are any values
// associated with the key, the value return has the value and ok is set to
// true. If there are no values for the given key, value is "" and ok is false.
// If you need access to multiple values, use the map directly.
func (args Args) Get(key string) (value string, ok bool) {
if args == nil {
return "", false
}
vals, ok := args[key]
if !ok || len(vals) == 0 {
return "", false
}
return vals[0], true
}
// Add Append value to the list of values for key.
func (args Args) Add(key, value string) {
args[key] = append(args[key], value)
}
// Return the index of the next unescaped byte in s that is in the term set, or
// else the length of the string if no terminators appear. Additionally return
// the unescaped string up to the returned index.
func indexUnescaped(s string, term []byte) (int, string, error) {
var i int
unesc := make([]byte, 0)
for i = 0; i < len(s); i++ {
b := s[i]
// A terminator byte?
if bytes.IndexByte(term, b) != -1 {
break
}
if b == '\\' {
i++
if i >= len(s) {
return 0, "", fmt.Errorf("nothing following final escape in %q", s)
}
b = s[i]
}
unesc = append(unesc, b)
}
return i, string(unesc), nil
}
// ParsePluginOptions Parse a namevalue mapping as from SS_PLUGIN_OPTIONS.
//
// "<value> is a k=v string value with options that are to be passed to the
// transport. semicolons, equal signs and backslashes must be escaped
// with a backslash."
// Example: secret=nou;cache=/tmp/cache;secret=yes
func ParsePluginOptions(s string) (opts Args, err error) {
opts = make(Args)
if len(s) == 0 {
return
}
i := 0
for {
var key, value string
var offset, begin int
if i >= len(s) {
break
}
begin = i
// Read the key.
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
if err != nil {
return
}
if len(key) == 0 {
err = fmt.Errorf("empty key in %q", s[begin:i])
return
}
i += offset
// End of string or no equals sign?
if i >= len(s) || s[i] != '=' {
opts.Add(key, "1")
// Skip the semicolon.
i++
continue
}
// Skip the equals sign.
i++
// Read the value.
offset, value, err = indexUnescaped(s[i:], []byte{';'})
if err != nil {
return
}
i += offset
opts.Add(key, value)
// Skip the semicolon.
i++
}
return opts, nil
}
// Escape backslashes and all the bytes that are in set.
func backslashEscape(s string, set []byte) string {
var buf bytes.Buffer
for _, b := range []byte(s) {
if b == '\\' || bytes.IndexByte(set, b) != -1 {
buf.WriteByte('\\')
}
buf.WriteByte(b)
}
return buf.String()
}

59
transport/sip003/obfs.go Normal file
View File

@@ -0,0 +1,59 @@
package sip003
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/transport/simple-obfs"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ Plugin = (*ObfsLocal)(nil)
func init() {
RegisterPlugin("obfs-local", newObfsLocal)
}
func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
var plugin ObfsLocal
mode := "http"
if obfsMode, loaded := pluginOpts.Get("obfs"); loaded {
mode = obfsMode
}
if obfsHost, loaded := pluginOpts.Get("obfs-host"); loaded {
plugin.host = obfsHost
}
switch mode {
case "http":
case "tls":
plugin.tls = true
default:
return nil, E.New("unknown obfs mode ", mode)
}
plugin.port = F.ToString(serverAddr.Port)
return &plugin, nil
}
type ObfsLocal struct {
dialer N.Dialer
serverAddr M.Socksaddr
tls bool
host string
port string
}
func (o *ObfsLocal) DialContext(ctx context.Context) (net.Conn, error) {
conn, err := o.dialer.DialContext(ctx, N.NetworkTCP, o.serverAddr)
if err != nil {
return nil, err
}
if !o.tls {
return obfs.NewHTTPObfs(conn, o.host, o.port), nil
} else {
return obfs.NewTLSObfs(conn, o.host), nil
}
}

View File

@@ -0,0 +1,35 @@
package sip003
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type PluginConstructor func(pluginArgs Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error)
type Plugin interface {
DialContext(ctx context.Context) (net.Conn, error)
}
var plugins map[string]PluginConstructor
func RegisterPlugin(name string, constructor PluginConstructor) {
plugins[name] = constructor
}
func CreatePlugin(name string, pluginArgs string, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
pluginOptions, err := ParsePluginOptions(pluginArgs)
if err != nil {
return nil, E.Cause(err, "parse plugin_opts")
}
constructor, loaded := plugins[name]
if !loaded {
return nil, E.New("plugin not found: ", name)
}
return constructor(pluginOptions, router, dialer, serverAddr)
}

80
transport/sip003/v2ray.go Normal file
View File

@@ -0,0 +1,80 @@
package sip003
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func init() {
RegisterPlugin("v2ray-plugin", newV2RayPlugin)
}
func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
var tlsOptions option.OutboundTLSOptions
if _, loaded := pluginOpts.Get("tls"); loaded {
tlsOptions.Enabled = true
}
if certPath, certLoaded := pluginOpts.Get("cert"); certLoaded {
tlsOptions.CertificatePath = certPath
}
if certRaw, certLoaded := pluginOpts.Get("certRaw"); certLoaded {
certHead := "-----BEGIN CERTIFICATE-----"
certTail := "-----END CERTIFICATE-----"
fixedCert := certHead + "\n" + certRaw + "\n" + certTail
tlsOptions.Certificate = fixedCert
}
mode := "websocket"
if modeOpt, loaded := pluginOpts.Get("mode"); loaded {
mode = modeOpt
}
host := "cloudfront.com"
path := "/"
if hostOpt, loaded := pluginOpts.Get("host"); loaded {
host = hostOpt
}
if pathOpt, loaded := pluginOpts.Get("path"); loaded {
path = pathOpt
}
var tlsClient tls.Config
var err error
if tlsOptions.Enabled {
tlsClient, err = tls.NewClient(router, serverAddr.AddrString(), tlsOptions)
if err != nil {
return nil, err
}
}
var transportOptions option.V2RayTransportOptions
switch mode {
case "websocket":
transportOptions = option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
Headers: map[string]string{
"Host": host,
},
Path: path,
},
}
case "quic":
transportOptions = option.V2RayTransportOptions{
Type: C.V2RayTransportTypeQUIC,
}
default:
return nil, E.New("v2ray-plugin: unknown mode: " + mode)
}
return v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)
}