Browse Source

Add shadowsocksr outbound

世界 3 years ago
parent
commit
9913e0e025

+ 1 - 1
Makefile

@@ -64,7 +64,7 @@ test:
 	@go test -v . && \
 	pushd test && \
 	go mod tidy && \
-	go test -v -tags with_quic,with_wireguard,with_grpc,with_ech,with_utls . && \
+	go test -v -tags with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr . && \
 	popd
 
 clean:

+ 19 - 18
constant/proxy.go

@@ -1,24 +1,25 @@
 package constant
 
 const (
-	TypeTun         = "tun"
-	TypeRedirect    = "redirect"
-	TypeTProxy      = "tproxy"
-	TypeDirect      = "direct"
-	TypeBlock       = "block"
-	TypeDNS         = "dns"
-	TypeSocks       = "socks"
-	TypeHTTP        = "http"
-	TypeMixed       = "mixed"
-	TypeShadowsocks = "shadowsocks"
-	TypeVMess       = "vmess"
-	TypeTrojan      = "trojan"
-	TypeNaive       = "naive"
-	TypeWireGuard   = "wireguard"
-	TypeHysteria    = "hysteria"
-	TypeTor         = "tor"
-	TypeSSH         = "ssh"
-	TypeShadowTLS   = "shadowtls"
+	TypeTun          = "tun"
+	TypeRedirect     = "redirect"
+	TypeTProxy       = "tproxy"
+	TypeDirect       = "direct"
+	TypeBlock        = "block"
+	TypeDNS          = "dns"
+	TypeSocks        = "socks"
+	TypeHTTP         = "http"
+	TypeMixed        = "mixed"
+	TypeShadowsocks  = "shadowsocks"
+	TypeVMess        = "vmess"
+	TypeTrojan       = "trojan"
+	TypeNaive        = "naive"
+	TypeWireGuard    = "wireguard"
+	TypeHysteria     = "hysteria"
+	TypeTor          = "tor"
+	TypeSSH          = "ssh"
+	TypeShadowTLS    = "shadowtls"
+	TypeShadowsocksR = "shadowsocksr"
 )
 
 const (

+ 1 - 0
go.mod

@@ -23,6 +23,7 @@ require (
 	github.com/refraction-networking/utls v1.1.2
 	github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
+	github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0
 	github.com/sagernet/sing v0.0.0-20220910144724-62c4ebdbcb3f
 	github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6

+ 5 - 0
go.sum

@@ -143,6 +143,8 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
+github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 h1:lQ4RFWY/wBYmXl/zJJCwQbhiEIbgEqC7j+nhZYkgwmU=
+github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0/go.mod h1:xSHLCsdgy5QIozXFEv6uDgMWzyrRdFFjr3TgL+juu6g=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220910144724-62c4ebdbcb3f h1:w1TJq7Lw3It35tDyMsZLtYz4T2msf1UK9JxC85L5+sk=
@@ -192,6 +194,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
@@ -245,6 +248,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -264,6 +268,7 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
 golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

+ 19 - 14
option/outbound.go

@@ -8,20 +8,21 @@ import (
 )
 
 type _Outbound struct {
-	Type               string                     `json:"type"`
-	Tag                string                     `json:"tag,omitempty"`
-	DirectOptions      DirectOutboundOptions      `json:"-"`
-	SocksOptions       SocksOutboundOptions       `json:"-"`
-	HTTPOptions        HTTPOutboundOptions        `json:"-"`
-	ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
-	VMessOptions       VMessOutboundOptions       `json:"-"`
-	TrojanOptions      TrojanOutboundOptions      `json:"-"`
-	WireGuardOptions   WireGuardOutboundOptions   `json:"-"`
-	HysteriaOptions    HysteriaOutboundOptions    `json:"-"`
-	TorOptions         TorOutboundOptions         `json:"-"`
-	SSHOptions         SSHOutboundOptions         `json:"-"`
-	ShadowTLSOptions   ShadowTLSOutboundOptions   `json:"-"`
-	SelectorOptions    SelectorOutboundOptions    `json:"-"`
+	Type                string                      `json:"type"`
+	Tag                 string                      `json:"tag,omitempty"`
+	DirectOptions       DirectOutboundOptions       `json:"-"`
+	SocksOptions        SocksOutboundOptions        `json:"-"`
+	HTTPOptions         HTTPOutboundOptions         `json:"-"`
+	ShadowsocksOptions  ShadowsocksOutboundOptions  `json:"-"`
+	VMessOptions        VMessOutboundOptions        `json:"-"`
+	TrojanOptions       TrojanOutboundOptions       `json:"-"`
+	WireGuardOptions    WireGuardOutboundOptions    `json:"-"`
+	HysteriaOptions     HysteriaOutboundOptions     `json:"-"`
+	TorOptions          TorOutboundOptions          `json:"-"`
+	SSHOptions          SSHOutboundOptions          `json:"-"`
+	ShadowTLSOptions    ShadowTLSOutboundOptions    `json:"-"`
+	ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
+	SelectorOptions     SelectorOutboundOptions     `json:"-"`
 }
 
 type Outbound _Outbound
@@ -53,6 +54,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
 		v = h.SSHOptions
 	case C.TypeShadowTLS:
 		v = h.ShadowTLSOptions
+	case C.TypeShadowsocksR:
+		v = h.ShadowsocksROptions
 	case C.TypeSelector:
 		v = h.SelectorOptions
 	default:
@@ -92,6 +95,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
 		v = &h.SSHOptions
 	case C.TypeShadowTLS:
 		v = &h.ShadowTLSOptions
+	case C.TypeShadowsocksR:
+		v = &h.ShadowsocksROptions
 	case C.TypeSelector:
 		v = &h.SelectorOptions
 	default:

+ 13 - 0
option/shadowsocksr.go

@@ -0,0 +1,13 @@
+package option
+
+type ShadowsocksROutboundOptions struct {
+	DialerOptions
+	ServerOptions
+	Method        string      `json:"method"`
+	Password      string      `json:"password"`
+	Obfs          string      `json:"obfs,omitempty"`
+	ObfsParam     string      `json:"obfs_param,omitempty"`
+	Protocol      string      `json:"protocol,omitempty"`
+	ProtocolParam string      `json:"protocol_param,omitempty"`
+	Network       NetworkList `json:"network,omitempty"`
+}

+ 2 - 0
outbound/builder.go

@@ -41,6 +41,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
 		return NewSSH(ctx, router, logger, options.Tag, options.SSHOptions)
 	case C.TypeShadowTLS:
 		return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
+	case C.TypeShadowsocksR:
+		return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
 	case C.TypeSelector:
 		return NewSelector(router, logger, options.Tag, options.SelectorOptions)
 	default:

+ 140 - 0
outbound/shadowsocksr.go

@@ -0,0 +1,140 @@
+//go:build with_shadowsocksr
+
+package outbound
+
+import (
+	"context"
+	"net"
+	"strings"
+
+	"github.com/sagernet/shadowsocksr"
+	"github.com/sagernet/shadowsocksr/obfs"
+	"github.com/sagernet/shadowsocksr/protocol"
+	"github.com/sagernet/shadowsocksr/ssr"
+	"github.com/sagernet/shadowsocksr/streamCipher"
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/dialer"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-shadowsocks"
+	"github.com/sagernet/sing-shadowsocks/shadowimpl"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/bufio"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+)
+
+var _ adapter.Outbound = (*ShadowsocksR)(nil)
+
+type ShadowsocksR struct {
+	myOutboundAdapter
+	dialer         N.Dialer
+	serverAddr     M.Socksaddr
+	method         shadowsocks.Method
+	cipher         string
+	password       string
+	obfs           string
+	obfsParams     *ssr.ServerInfo
+	protocol       string
+	protocolParams *ssr.ServerInfo
+}
+
+func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
+	outbound := &ShadowsocksR{
+		myOutboundAdapter: myOutboundAdapter{
+			protocol: C.TypeShadowsocksR,
+			network:  options.Network.Build(),
+			router:   router,
+			logger:   logger,
+			tag:      tag,
+		},
+		dialer:     dialer.New(router, options.DialerOptions),
+		serverAddr: options.ServerOptions.Build(),
+		cipher:     options.Method,
+		password:   options.Password,
+		obfs:       options.Obfs,
+		protocol:   options.Protocol,
+	}
+	var err error
+	outbound.method, err = shadowimpl.FetchMethod(options.Method, options.Password)
+	if err != nil {
+		return nil, err
+	}
+	if _, err = streamCipher.NewStreamCipher(options.Method, options.Password); err != nil {
+		return nil, E.New(strings.ToLower(err.Error()))
+	}
+	if obfs.NewObfs(options.Obfs) == nil {
+		return nil, E.New("unknown obfs: " + options.Obfs)
+	}
+	outbound.obfsParams = &ssr.ServerInfo{
+		Host:   outbound.serverAddr.AddrString(),
+		Port:   outbound.serverAddr.Port,
+		TcpMss: 1460,
+		Param:  options.ObfsParam,
+	}
+	if protocol.NewProtocol(options.Protocol) == nil {
+		return nil, E.New("unknown protocol: " + options.Protocol)
+	}
+	outbound.protocolParams = &ssr.ServerInfo{
+		Host:   outbound.serverAddr.AddrString(),
+		Port:   outbound.serverAddr.Port,
+		TcpMss: 1460,
+		Param:  options.Protocol,
+	}
+	if outbound.method == nil {
+		outbound.network = common.Filter(outbound.network, func(it string) bool { return it == N.NetworkTCP })
+	}
+	return outbound, nil
+}
+
+func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+	switch network {
+	case N.NetworkTCP:
+		h.logger.InfoContext(ctx, "outbound connection to ", destination)
+		conn, err := h.dialer.DialContext(ctx, network, h.serverAddr)
+		if err != nil {
+			return nil, err
+		}
+		cipher, err := streamCipher.NewStreamCipher(h.cipher, h.password)
+		if err != nil {
+			return nil, E.New(strings.ToLower(err.Error()))
+		}
+		ssConn := shadowsocksr.NewSSTCPConn(conn, cipher)
+		ssConn.IObfs = obfs.NewObfs(h.obfs)
+		ssConn.IObfs.SetServerInfo(h.obfsParams)
+		ssConn.IProtocol = protocol.NewProtocol(h.protocol)
+		ssConn.IProtocol.SetServerInfo(h.protocolParams)
+		err = M.SocksaddrSerializer.WriteAddrPort(ssConn, destination)
+		if err != nil {
+			return nil, E.Cause(err, "write request")
+		}
+		return ssConn, nil
+	case N.NetworkUDP:
+		conn, err := h.ListenPacket(ctx, destination)
+		if err != nil {
+			return nil, err
+		}
+		return &bufio.BindPacketConn{PacketConn: conn, Addr: destination}, nil
+	default:
+		return nil, E.Extend(N.ErrUnknownNetwork, network)
+	}
+}
+
+func (h *ShadowsocksR) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
+	h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
+	outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
+	if err != nil {
+		return nil, err
+	}
+	return h.method.DialPacketConn(outConn), nil
+}
+
+func (h *ShadowsocksR) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	return NewConnection(ctx, h, conn, metadata)
+}
+
+func (h *ShadowsocksR) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
+	return NewPacketConnection(ctx, h, conn, metadata)
+}

+ 16 - 0
outbound/shadowsocksr_stub.go

@@ -0,0 +1,16 @@
+//go:build !with_shadowsocksr
+
+package outbound
+
+import (
+	"context"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
+	E "github.com/sagernet/sing/common/exceptions"
+)
+
+func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) {
+	return nil, E.New(`ShadowsocksR is not included in this build, rebuild with -tags with_shadowsocksr`)
+}

+ 4 - 2
test/box_test.go

@@ -53,8 +53,8 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
 	dialUDP := func() (net.PacketConn, error) {
 		return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
 	}
-	// require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
-	// require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
+	require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
+	require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
 	require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
 	require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
 
@@ -80,6 +80,8 @@ func testSuitSimple(t *testing.T, clientPort uint16, testPort uint16) {
 	}
 	require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
 	require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
+	require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
+	require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
 }
 
 func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {

+ 2 - 0
test/clash_test.go

@@ -37,6 +37,7 @@ const (
 	ImageHysteria              = "tobyxdd/hysteria:latest"
 	ImageNginx                 = "nginx:stable"
 	ImageShadowTLS             = "ghcr.io/ihciah/shadow-tls:latest"
+	ImageShadowsocksR          = "teddysun/shadowsocks-r:latest"
 )
 
 var allImages = []string{
@@ -49,6 +50,7 @@ var allImages = []string{
 	ImageHysteria,
 	ImageNginx,
 	ImageShadowTLS,
+	ImageShadowsocksR,
 }
 
 var localIP = netip.MustParseAddr("127.0.0.1")

+ 19 - 0
test/config/shadowsocksr.json

@@ -0,0 +1,19 @@
+{
+  "server": "0.0.0.0",
+  "server_ipv6": "::",
+  "server_port": 10000,
+  "local_address": "127.0.0.1",
+  "local_port": 1080,
+  "password": "password0",
+  "timeout": 120,
+  "method": "aes-256-cfb",
+  "protocol": "origin",
+  "protocol_param": "",
+  "obfs": "plain",
+  "obfs_param": "",
+  "redirect": "",
+  "dns_ipv6": false,
+  "fast_open": true,
+  "workers": 1,
+  "forbidden_ip": ""
+}

+ 2 - 1
test/go.mod

@@ -66,8 +66,9 @@ require (
 	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
+	github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 // indirect
 	github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 // indirect
-	github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24 // indirect
 	github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f // indirect
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect

+ 6 - 2
test/go.sum

@@ -163,6 +163,8 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
+github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 h1:lQ4RFWY/wBYmXl/zJJCwQbhiEIbgEqC7j+nhZYkgwmU=
+github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0/go.mod h1:xSHLCsdgy5QIozXFEv6uDgMWzyrRdFFjr3TgL+juu6g=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220910144724-62c4ebdbcb3f h1:w1TJq7Lw3It35tDyMsZLtYz4T2msf1UK9JxC85L5+sk=
@@ -171,8 +173,8 @@ github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFx
 github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
-github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb h1:/swVU2mgwDwZ9l67v1Sim1ias/ZmriGTxQLnMakPhtQ=
-github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
+github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24 h1:LsmPeFvj4GhiV5Y7Rm8I845XysdxVN4MQmfZ36P5bmw=
+github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
 github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f h1:6l9aXZqAl1JqXJWi89KHpWnM/moQUPGG+XiwMc+yD0A=
 github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
@@ -216,6 +218,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
@@ -295,6 +298,7 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
 golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

+ 1 - 10
test/hysteria_test.go

@@ -9,9 +9,6 @@ import (
 )
 
 func TestHysteriaSelf(t *testing.T) {
-	if !C.QUIC_AVAILABLE {
-		t.Skip("QUIC not included")
-	}
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
 		Inbounds: []option.Inbound{
@@ -84,9 +81,6 @@ func TestHysteriaSelf(t *testing.T) {
 }
 
 func TestHysteriaInbound(t *testing.T) {
-	if !C.QUIC_AVAILABLE {
-		t.Skip("QUIC not included")
-	}
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
 		Inbounds: []option.Inbound{
@@ -120,13 +114,10 @@ func TestHysteriaInbound(t *testing.T) {
 			caPem:                  "/etc/hysteria/ca.pem",
 		},
 	})
-	testSuit(t, clientPort, testPort)
+	testSuitSimple(t, clientPort, testPort)
 }
 
 func TestHysteriaOutbound(t *testing.T) {
-	if !C.QUIC_AVAILABLE {
-		t.Skip("QUIC not included")
-	}
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startDockerContainer(t, DockerOptions{
 		Image: ImageHysteria,

+ 0 - 3
test/naive_test.go

@@ -99,9 +99,6 @@ func TestNaiveInbound(t *testing.T) {
 }
 
 func TestNaiveHTTP3Inbound(t *testing.T) {
-	if !C.QUIC_AVAILABLE {
-		t.Skip("QUIC not included")
-	}
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
 		Inbounds: []option.Inbound{

+ 48 - 0
test/shadowsocksr_test.go

@@ -0,0 +1,48 @@
+package main
+
+import (
+	"net/netip"
+	"testing"
+
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+)
+
+func TestShadowsocksR(t *testing.T) {
+	startDockerContainer(t, DockerOptions{
+		Image: ImageShadowsocksR,
+		Ports: []uint16{serverPort, testPort},
+		Bind: map[string]string{
+			"shadowsocksr.json": "/etc/shadowsocks-r/config.json",
+		},
+	})
+	startInstance(t, option.Options{
+		Inbounds: []option.Inbound{
+			{
+				Type: C.TypeMixed,
+				MixedOptions: option.HTTPMixedInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: clientPort,
+					},
+				},
+			},
+		},
+		Outbounds: []option.Outbound{
+			{
+				Type: C.TypeShadowsocksR,
+				ShadowsocksROptions: option.ShadowsocksROutboundOptions{
+					ServerOptions: option.ServerOptions{
+						Server:     "127.0.0.1",
+						ServerPort: serverPort,
+					},
+					Method:   "aes-256-cfb",
+					Password: "password0",
+					Obfs:     "plain",
+					Protocol: "origin",
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
+}

+ 3 - 0
transport/sip003/plugin.go

@@ -19,6 +19,9 @@ type Plugin interface {
 var plugins map[string]PluginConstructor
 
 func RegisterPlugin(name string, constructor PluginConstructor) {
+	if plugins == nil {
+		plugins = make(map[string]PluginConstructor)
+	}
 	plugins[name] = constructor
 }