Browse Source

Add shadowsocks-2022 inbound/outbound (#1061)

世界 3 years ago
parent
commit
087f0d1240

+ 17 - 1
common/session/context.go

@@ -1,6 +1,10 @@
 package session
 
-import "context"
+import (
+	"context"
+
+	"github.com/xtls/xray-core/features/routing"
+)
 
 type sessionKey int
 
@@ -12,6 +16,7 @@ const (
 	muxPreferedSessionKey
 	sockoptSessionKey
 	trackedConnectionErrorKey
+	dispatcherKey
 )
 
 // ContextWithID returns a new context with the given ID.
@@ -115,3 +120,14 @@ func SubmitOutboundErrorToOriginator(ctx context.Context, err error) {
 func TrackedConnectionError(ctx context.Context, tracker TrackedRequestErrorFeedback) context.Context {
 	return context.WithValue(ctx, trackedConnectionErrorKey, tracker)
 }
+
+func ContextWithDispatcher(ctx context.Context, dispatcher routing.Dispatcher) context.Context {
+	return context.WithValue(ctx, dispatcherKey, dispatcher)
+}
+
+func DispatcherFromContext(ctx context.Context) routing.Dispatcher {
+	if dispatcher, ok := ctx.Value(dispatcherKey).(routing.Dispatcher); ok {
+		return dispatcher
+	}
+	return nil
+}

+ 11 - 7
go.mod

@@ -9,19 +9,23 @@ require (
 	github.com/google/go-cmp v0.5.8
 	github.com/gorilla/websocket v1.5.0
 	github.com/lucas-clemente/quic-go v0.27.0
+	github.com/marten-seemann/qtls-go1-16 v0.1.5
+	github.com/marten-seemann/qtls-go1-17 v0.1.1
+	github.com/marten-seemann/qtls-go1-18 v0.1.1
 	github.com/miekg/dns v1.1.49
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/refraction-networking/utls v1.1.0
+	github.com/sagernet/sing v0.0.0-20220523024450-2216901ec429
 	github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
 	github.com/stretchr/testify v1.7.1
 	github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
 	github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
 	go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd
-	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
-	golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
+	golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
+	golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
+	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
 	google.golang.org/grpc v1.46.2
 	google.golang.org/protobuf v1.28.0
 	h12.io/socks v1.0.3
@@ -34,19 +38,19 @@ require (
 	github.com/francoispqt/gojay v1.2.13 // indirect
 	github.com/fsnotify/fsnotify v1.5.3 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
-	github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
-	github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.12 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.10 // indirect
+	golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237 // indirect
 	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
+	golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
 	google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	lukechampine.com/blake3 v1.1.7 // indirect
 )

+ 19 - 8
go.sum

@@ -111,6 +111,9 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
+github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -171,6 +174,10 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sagernet/sing v0.0.0-20220522025344-b21aa294fca3 h1:z6cVzCta0vPVKo4lsiHMEDmw3cSFC5TGojXobbTS1DA=
+github.com/sagernet/sing v0.0.0-20220522025344-b21aa294fca3/go.mod h1:7aFl1bLxdWLIhptc1DwhMWpRfT62rJYzKPYuYlzYs5w=
+github.com/sagernet/sing v0.0.0-20220523024450-2216901ec429 h1:31vx64WHtycaqw0bGLl6ImJvmHsXqHoPzkuwKmTqbx8=
+github.com/sagernet/sing v0.0.0-20220523024450-2216901ec429/go.mod h1:7aFl1bLxdWLIhptc1DwhMWpRfT62rJYzKPYuYlzYs5w=
 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -226,8 +233,8 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
-golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -255,8 +262,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
-golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -292,8 +299,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -317,14 +324,16 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237 h1:mAhaIX1KEgotq+ju3XYdXUHvll7bzJDTgiDzIAKDdPc=
+golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
 golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
+golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -393,5 +402,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
+lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
 sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
 sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

+ 46 - 0
infra/conf/shadowsocks_2022.go

@@ -0,0 +1,46 @@
+package conf
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/proxy/shadowsocks_2022"
+)
+
+type Shadowsocks2022ServerConfig struct {
+	Cipher      string       `json:"method"`
+	Key         string       `json:"key"`
+	NetworkList *NetworkList `json:"network"`
+}
+
+func (v *Shadowsocks2022ServerConfig) Build() (proto.Message, error) {
+	var network []net.Network
+	if v.NetworkList != nil {
+		network = v.NetworkList.Build()
+	}
+	return &shadowsocks_2022.ServerConfig{
+		Method:  v.Cipher,
+		Key:     v.Key,
+		Network: network,
+	}, nil
+}
+
+type Shadowsocks2022ClientConfig struct {
+	Address              *Address `json:"address"`
+	Port                 uint16   `json:"port"`
+	Cipher               string   `json:"method"`
+	Key                  string   `json:"key"`
+	ReducedIvHeadEntropy bool     `json:"reducedIvHeadEntropy"`
+}
+
+func (v *Shadowsocks2022ClientConfig) Build() (proto.Message, error) {
+	if v.Address == nil {
+		return nil, newError("shadowsocks 2022: missing server address")
+	}
+	return &shadowsocks_2022.ClientConfig{
+		Address:              v.Address.Build(),
+		Port:                 uint32(v.Port),
+		Method:               v.Cipher,
+		Key:                  v.Key,
+		ReducedIvHeadEntropy: v.ReducedIvHeadEntropy,
+	}, nil
+}

+ 21 - 19
infra/conf/xray.go

@@ -18,28 +18,30 @@ import (
 
 var (
 	inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
-		"dokodemo-door": func() interface{} { return new(DokodemoConfig) },
-		"http":          func() interface{} { return new(HTTPServerConfig) },
-		"shadowsocks":   func() interface{} { return new(ShadowsocksServerConfig) },
-		"socks":         func() interface{} { return new(SocksServerConfig) },
-		"vless":         func() interface{} { return new(VLessInboundConfig) },
-		"vmess":         func() interface{} { return new(VMessInboundConfig) },
-		"trojan":        func() interface{} { return new(TrojanServerConfig) },
-		"mtproto":       func() interface{} { return new(MTProtoServerConfig) },
+		"dokodemo-door":    func() interface{} { return new(DokodemoConfig) },
+		"http":             func() interface{} { return new(HTTPServerConfig) },
+		"shadowsocks":      func() interface{} { return new(ShadowsocksServerConfig) },
+		"socks":            func() interface{} { return new(SocksServerConfig) },
+		"vless":            func() interface{} { return new(VLessInboundConfig) },
+		"vmess":            func() interface{} { return new(VMessInboundConfig) },
+		"trojan":           func() interface{} { return new(TrojanServerConfig) },
+		"mtproto":          func() interface{} { return new(MTProtoServerConfig) },
+		"shadowsocks-2022": func() interface{} { return new(Shadowsocks2022ServerConfig) },
 	}, "protocol", "settings")
 
 	outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
-		"blackhole":   func() interface{} { return new(BlackholeConfig) },
-		"loopback":    func() interface{} { return new(LoopbackConfig) },
-		"freedom":     func() interface{} { return new(FreedomConfig) },
-		"http":        func() interface{} { return new(HTTPClientConfig) },
-		"shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) },
-		"socks":       func() interface{} { return new(SocksClientConfig) },
-		"vless":       func() interface{} { return new(VLessOutboundConfig) },
-		"vmess":       func() interface{} { return new(VMessOutboundConfig) },
-		"trojan":      func() interface{} { return new(TrojanClientConfig) },
-		"mtproto":     func() interface{} { return new(MTProtoClientConfig) },
-		"dns":         func() interface{} { return new(DNSOutboundConfig) },
+		"blackhole":        func() interface{} { return new(BlackholeConfig) },
+		"loopback":         func() interface{} { return new(LoopbackConfig) },
+		"freedom":          func() interface{} { return new(FreedomConfig) },
+		"http":             func() interface{} { return new(HTTPClientConfig) },
+		"shadowsocks":      func() interface{} { return new(ShadowsocksClientConfig) },
+		"socks":            func() interface{} { return new(SocksClientConfig) },
+		"vless":            func() interface{} { return new(VLessOutboundConfig) },
+		"vmess":            func() interface{} { return new(VMessOutboundConfig) },
+		"trojan":           func() interface{} { return new(TrojanClientConfig) },
+		"mtproto":          func() interface{} { return new(MTProtoClientConfig) },
+		"dns":              func() interface{} { return new(DNSOutboundConfig) },
+		"shadowsocks-2022": func() interface{} { return new(Shadowsocks2022ClientConfig) },
 	}, "protocol", "settings")
 
 	ctllog = log.New(os.Stderr, "xctl> ", 0)

+ 283 - 0
proxy/shadowsocks_2022/config.pb.go

@@ -0,0 +1,283 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.0
+// 	protoc        v3.20.1
+// source: proxy/shadowsocks_2022/config.proto
+
+package shadowsocks_2022
+
+import (
+	net "github.com/xtls/xray-core/common/net"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ServerConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Method  string        `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+	Key     string        `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
+	Network []net.Network `protobuf:"varint,3,rep,packed,name=network,proto3,enum=xray.common.net.Network" json:"network,omitempty"`
+}
+
+func (x *ServerConfig) Reset() {
+	*x = ServerConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ServerConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ServerConfig) ProtoMessage() {}
+
+func (x *ServerConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
+func (*ServerConfig) Descriptor() ([]byte, []int) {
+	return file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ServerConfig) GetMethod() string {
+	if x != nil {
+		return x.Method
+	}
+	return ""
+}
+
+func (x *ServerConfig) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *ServerConfig) GetNetwork() []net.Network {
+	if x != nil {
+		return x.Network
+	}
+	return nil
+}
+
+type ClientConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Address              *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	Port                 uint32          `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+	Method               string          `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
+	Key                  string          `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
+	ReducedIvHeadEntropy bool            `protobuf:"varint,5,opt,name=reduced_iv_head_entropy,json=reducedIvHeadEntropy,proto3" json:"reduced_iv_head_entropy,omitempty"`
+}
+
+func (x *ClientConfig) Reset() {
+	*x = ClientConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ClientConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientConfig) ProtoMessage() {}
+
+func (x *ClientConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
+func (*ClientConfig) Descriptor() ([]byte, []int) {
+	return file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ClientConfig) GetAddress() *net.IPOrDomain {
+	if x != nil {
+		return x.Address
+	}
+	return nil
+}
+
+func (x *ClientConfig) GetPort() uint32 {
+	if x != nil {
+		return x.Port
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetMethod() string {
+	if x != nil {
+		return x.Method
+	}
+	return ""
+}
+
+func (x *ClientConfig) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *ClientConfig) GetReducedIvHeadEntropy() bool {
+	if x != nil {
+		return x.ReducedIvHeadEntropy
+	}
+	return false
+}
+
+var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor
+
+var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{
+	0x0a, 0x23, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f,
+	0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78,
+	0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30,
+	0x32, 0x32, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e,
+	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6c, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10,
+	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+	0x12, 0x32, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74,
+	0x77, 0x6f, 0x72, 0x6b, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04,
+	0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74,
+	0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65,
+	0x64, 0x75, 0x63, 0x65, 0x64, 0x5f, 0x69, 0x76, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x65, 0x6e,
+	0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x72, 0x65, 0x64,
+	0x75, 0x63, 0x65, 0x64, 0x49, 0x76, 0x48, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70,
+	0x79, 0x42, 0x72, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f,
+	0x32, 0x30, 0x32, 0x32, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
+	0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f,
+	0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e,
+	0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b,
+	0x73, 0x32, 0x30, 0x32, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proxy_shadowsocks_2022_config_proto_rawDescOnce sync.Once
+	file_proxy_shadowsocks_2022_config_proto_rawDescData = file_proxy_shadowsocks_2022_config_proto_rawDesc
+)
+
+func file_proxy_shadowsocks_2022_config_proto_rawDescGZIP() []byte {
+	file_proxy_shadowsocks_2022_config_proto_rawDescOnce.Do(func() {
+		file_proxy_shadowsocks_2022_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_shadowsocks_2022_config_proto_rawDescData)
+	})
+	return file_proxy_shadowsocks_2022_config_proto_rawDescData
+}
+
+var file_proxy_shadowsocks_2022_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proxy_shadowsocks_2022_config_proto_goTypes = []interface{}{
+	(*ServerConfig)(nil),   // 0: xray.proxy.shadowsocks_2022.ServerConfig
+	(*ClientConfig)(nil),   // 1: xray.proxy.shadowsocks_2022.ClientConfig
+	(net.Network)(0),       // 2: xray.common.net.Network
+	(*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain
+}
+var file_proxy_shadowsocks_2022_config_proto_depIdxs = []int32{
+	2, // 0: xray.proxy.shadowsocks_2022.ServerConfig.network:type_name -> xray.common.net.Network
+	3, // 1: xray.proxy.shadowsocks_2022.ClientConfig.address:type_name -> xray.common.net.IPOrDomain
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_proxy_shadowsocks_2022_config_proto_init() }
+func file_proxy_shadowsocks_2022_config_proto_init() {
+	if File_proxy_shadowsocks_2022_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proxy_shadowsocks_2022_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ServerConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proxy_shadowsocks_2022_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ClientConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_proxy_shadowsocks_2022_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_proxy_shadowsocks_2022_config_proto_goTypes,
+		DependencyIndexes: file_proxy_shadowsocks_2022_config_proto_depIdxs,
+		MessageInfos:      file_proxy_shadowsocks_2022_config_proto_msgTypes,
+	}.Build()
+	File_proxy_shadowsocks_2022_config_proto = out.File
+	file_proxy_shadowsocks_2022_config_proto_rawDesc = nil
+	file_proxy_shadowsocks_2022_config_proto_goTypes = nil
+	file_proxy_shadowsocks_2022_config_proto_depIdxs = nil
+}

+ 24 - 0
proxy/shadowsocks_2022/config.proto

@@ -0,0 +1,24 @@
+syntax = "proto3";
+
+package xray.proxy.shadowsocks_2022;
+option csharp_namespace = "Xray.Proxy.Shadowsocks2022";
+option go_package = "github.com/xtls/xray-core/proxy/shadowsocks_2022";
+option java_package = "com.xray.proxy.shadowsocks_2022";
+option java_multiple_files = true;
+
+import "common/net/network.proto";
+import "common/net/address.proto";
+
+message ServerConfig {
+  string method = 1;
+  string key = 2;
+  repeated xray.common.net.Network network = 3;
+}
+
+message ClientConfig {
+  xray.common.net.IPOrDomain address = 1;
+  uint32 port = 2;
+  string method = 3;
+  string key = 4;
+  bool reduced_iv_head_entropy = 5;
+}

+ 9 - 0
proxy/shadowsocks_2022/errors.generated.go

@@ -0,0 +1,9 @@
+package shadowsocks_2022
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}

+ 158 - 0
proxy/shadowsocks_2022/inbound.go

@@ -0,0 +1,158 @@
+package shadowsocks_2022
+
+import (
+	"context"
+	"encoding/base64"
+
+	C "github.com/sagernet/sing/common"
+	B "github.com/sagernet/sing/common/buf"
+	"github.com/sagernet/sing/common/bufio"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/random"
+	"github.com/sagernet/sing/protocol/shadowsocks"
+	"github.com/sagernet/sing/protocol/shadowsocks/shadowaead_2022"
+	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/buf"
+	"github.com/xtls/xray-core/common/log"
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/common/session"
+	"github.com/xtls/xray-core/features/routing"
+	"github.com/xtls/xray-core/transport/internet/stat"
+)
+
+func init() {
+	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewServer(ctx, config.(*ServerConfig))
+	}))
+}
+
+type Inbound struct {
+	networks []net.Network
+	service  shadowsocks.Service
+}
+
+func NewServer(ctx context.Context, config *ServerConfig) (*Inbound, error) {
+	networks := config.Network
+	if len(networks) == 0 {
+		networks = []net.Network{
+			net.Network_TCP,
+			net.Network_UDP,
+		}
+	}
+	inbound := &Inbound{
+		networks: networks,
+	}
+	if !C.Contains(shadowaead_2022.List, config.Method) {
+		return nil, newError("unsupported method ", config.Method)
+	}
+	if config.Key == "" {
+		return nil, newError("missing key")
+	}
+	psk, err := base64.StdEncoding.DecodeString(config.Key)
+	if err != nil {
+		return nil, newError("parse config").Base(err)
+	}
+	service, err := shadowaead_2022.NewService(config.Method, psk, "", random.Default, 500, inbound)
+	if err != nil {
+		return nil, newError("create service").Base(err)
+	}
+	inbound.service = service
+	return inbound, nil
+}
+
+func (i *Inbound) Network() []net.Network {
+	return i.networks
+}
+
+func (i *Inbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {
+	inbound := session.InboundFromContext(ctx)
+	if inbound == nil {
+		panic("no inbound metadata")
+	}
+
+	var metadata M.Metadata
+	if inbound.Source.IsValid() {
+		metadata.Source = M.ParseSocksaddr(inbound.Source.NetAddr())
+	}
+
+	ctx = session.ContextWithDispatcher(ctx, dispatcher)
+
+	if network == net.Network_TCP {
+		return i.service.NewConnection(ctx, connection, metadata)
+	} else {
+		reader := buf.NewReader(connection)
+		pc := &natPacketConn{connection}
+		for {
+			mb, err := reader.ReadMultiBuffer()
+			if err != nil {
+				return err
+			}
+			for _, buffer := range mb {
+				err = i.service.NewPacket(ctx, pc, B.As(buffer.Bytes()), metadata)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+}
+
+func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
+	ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
+		From:   metadata.Source,
+		To:     metadata.Destination,
+		Status: log.AccessAccepted,
+	})
+	newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx))
+	dispatcher := session.DispatcherFromContext(ctx)
+	link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP))
+	if err != nil {
+		return err
+	}
+	outConn := &pipeConnWrapper{
+		&buf.BufferedReader{Reader: link.Reader},
+		link.Writer,
+		conn,
+	}
+	return bufio.CopyConn(ctx, conn, outConn)
+}
+
+func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
+	ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
+		From:   metadata.Source,
+		To:     metadata.Destination,
+		Status: log.AccessAccepted,
+	})
+	newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx))
+	dispatcher := session.DispatcherFromContext(ctx)
+	destination := toDestination(metadata.Destination, net.Network_UDP)
+	link, err := dispatcher.Dispatch(ctx, destination)
+	if err != nil {
+		return err
+	}
+	outConn := &packetConnWrapper{
+		Reader: link.Reader,
+		Writer: link.Writer,
+		Dest:   destination,
+	}
+	return bufio.CopyPacketConn(ctx, conn, outConn)
+}
+
+func (i *Inbound) HandleError(err error) {
+	newError(err).AtWarning().WriteToLog()
+}
+
+type natPacketConn struct {
+	net.Conn
+}
+
+func (c *natPacketConn) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
+	_, err = buffer.ReadFrom(c)
+	return
+}
+
+func (c *natPacketConn) WritePacket(buffer *B.Buffer, addr M.Socksaddr) error {
+	_, err := buffer.WriteTo(c)
+	return err
+}

+ 166 - 0
proxy/shadowsocks_2022/outbound.go

@@ -0,0 +1,166 @@
+package shadowsocks_2022
+
+import (
+	"context"
+	"encoding/base64"
+	"io"
+	"runtime"
+	"strings"
+	"time"
+
+	C "github.com/sagernet/sing/common"
+	B "github.com/sagernet/sing/common/buf"
+	"github.com/sagernet/sing/common/bufio"
+	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/random"
+	"github.com/sagernet/sing/protocol/shadowsocks"
+	"github.com/sagernet/sing/protocol/shadowsocks/shadowaead_2022"
+	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/buf"
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/common/session"
+	"github.com/xtls/xray-core/transport"
+	"github.com/xtls/xray-core/transport/internet"
+)
+
+func init() {
+	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewClient(ctx, config.(*ClientConfig))
+	}))
+}
+
+type Outbound struct {
+	ctx    context.Context
+	server net.Destination
+	method shadowsocks.Method
+}
+
+func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
+	o := &Outbound{
+		ctx: ctx,
+		server: net.Destination{
+			Address: config.Address.AsAddress(),
+			Port:    net.Port(config.Port),
+			Network: net.Network_TCP,
+		},
+	}
+	if C.Contains(shadowaead_2022.List, config.Method) {
+		if config.Key == "" {
+			return nil, newError("missing psk")
+		}
+		var pskList [][]byte
+		for _, ks := range strings.Split(config.Key, ":") {
+			psk, err := base64.StdEncoding.DecodeString(ks)
+			if err != nil {
+				return nil, newError("decode key ", ks).Base(err)
+			}
+			pskList = append(pskList, psk)
+		}
+		var rng io.Reader = random.Default
+		if config.ReducedIvHeadEntropy {
+			rng = &shadowsocks.ReducedEntropyReader{
+				Reader: rng,
+			}
+		}
+		method, err := shadowaead_2022.New(config.Method, pskList, "", rng)
+		if err != nil {
+			return nil, newError("create method").Base(err)
+		}
+		o.method = method
+	} else {
+		return nil, newError("unknown method ", config.Method)
+	}
+	return o, nil
+}
+
+func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
+	var inboundConn net.Conn
+	inbound := session.InboundFromContext(ctx)
+	if inbound != nil {
+		inboundConn = inbound.Conn
+	}
+
+	outbound := session.OutboundFromContext(ctx)
+	if outbound == nil || !outbound.Target.IsValid() {
+		return newError("target not specified")
+	}
+	/*if statConn, ok := inboundConn.(*internet.StatCounterConn); ok {
+		inboundConn = statConn.Connection
+	}*/
+	destination := outbound.Target
+	network := destination.Network
+
+	newError("tunneling request to ", destination, " via ", o.server.NetAddr()).WriteToLog(session.ExportIDToError(ctx))
+
+	serverDestination := o.server
+	serverDestination.Network = network
+	connection, err := dialer.Dial(ctx, serverDestination)
+	if err != nil {
+		return newError("failed to connect to server").Base(err)
+	}
+
+	if network == net.Network_TCP {
+		serverConn := o.method.DialEarlyConn(connection, toSocksaddr(destination))
+		var handshake bool
+		if timeoutReader, isTimeoutReader := link.Reader.(buf.TimeoutReader); isTimeoutReader {
+			mb, err := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 100)
+			if err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
+				return newError("read payload").Base(err)
+			}
+			_payload := B.StackNew()
+			payload := C.Dup(_payload)
+			for {
+				payload.FullReset()
+				nb, n := buf.SplitBytes(mb, payload.FreeBytes())
+				if n > 0 {
+					payload.Truncate(n)
+					_, err = serverConn.Write(payload.Bytes())
+					if err != nil {
+						return newError("write payload").Base(err)
+					}
+					handshake = true
+				}
+				if nb.IsEmpty() {
+					break
+				} else {
+					mb = nb
+				}
+			}
+			runtime.KeepAlive(_payload)
+		}
+		if !handshake {
+			_, err = serverConn.Write(nil)
+			if err != nil {
+				return newError("client handshake").Base(err)
+			}
+		}
+		conn := &pipeConnWrapper{
+			W:    link.Writer,
+			Conn: inboundConn,
+		}
+		if ir, ok := link.Reader.(io.Reader); ok {
+			conn.R = ir
+		} else {
+			conn.R = &buf.BufferedReader{Reader: link.Reader}
+		}
+
+		return bufio.CopyConn(ctx, conn, serverConn)
+	} else {
+		var packetConn N.PacketConn
+		if pc, isPacketConn := inboundConn.(N.PacketConn); isPacketConn {
+			packetConn = pc
+		} else if nc, isNetPacket := inboundConn.(net.PacketConn); isNetPacket {
+			packetConn = &bufio.PacketConnWrapper{PacketConn: nc}
+		} else {
+			packetConn = &packetConnWrapper{
+				Reader: link.Reader,
+				Writer: link.Writer,
+				Conn:   inboundConn,
+				Dest:   destination,
+			}
+		}
+
+		serverConn := o.method.DialPacketConn(connection)
+		return bufio.CopyPacketConn(ctx, packetConn, serverConn)
+	}
+}

+ 143 - 0
proxy/shadowsocks_2022/shadowsocks_2022.go

@@ -0,0 +1,143 @@
+package shadowsocks_2022
+
+import (
+	"io"
+
+	B "github.com/sagernet/sing/common/buf"
+	M "github.com/sagernet/sing/common/metadata"
+	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/buf"
+	"github.com/xtls/xray-core/common/net"
+)
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
+
+func toDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
+	if socksaddr.Family().IsFqdn() {
+		return net.Destination{
+			Network: network,
+			Address: net.DomainAddress(socksaddr.Fqdn),
+			Port:    net.Port(socksaddr.Port),
+		}
+	} else {
+		return net.Destination{
+			Network: network,
+			Address: net.IPAddress(socksaddr.Addr.AsSlice()),
+			Port:    net.Port(socksaddr.Port),
+		}
+	}
+}
+
+func toSocksaddr(destination net.Destination) M.Socksaddr {
+	var addr M.Socksaddr
+	switch destination.Address.Family() {
+	case net.AddressFamilyDomain:
+		addr.Fqdn = destination.Address.Domain()
+	default:
+		addr.Addr = M.AddrFromIP(destination.Address.IP())
+	}
+	addr.Port = uint16(destination.Port)
+	return addr
+}
+
+type pipeConnWrapper struct {
+	R io.Reader
+	W buf.Writer
+	net.Conn
+}
+
+func (w *pipeConnWrapper) Close() error {
+	common.Interrupt(w.R)
+	common.Interrupt(w.W)
+	common.Close(w.Conn)
+	return nil
+}
+
+func (w *pipeConnWrapper) Read(b []byte) (n int, err error) {
+	return w.R.Read(b)
+}
+
+func (w *pipeConnWrapper) Write(p []byte) (n int, err error) {
+	n = len(p)
+	var mb buf.MultiBuffer
+	pLen := len(p)
+	for pLen > 0 {
+		buffer := buf.New()
+		if pLen > buf.Size {
+			_, err = buffer.Write(p[:buf.Size])
+			p = p[buf.Size:]
+		} else {
+			buffer.Write(p)
+		}
+		pLen -= int(buffer.Len())
+		mb = append(mb, buffer)
+	}
+	err = w.W.WriteMultiBuffer(mb)
+	if err != nil {
+		n = 0
+		buf.ReleaseMulti(mb)
+	}
+	return
+}
+
+type packetConnWrapper struct {
+	buf.Reader
+	buf.Writer
+	net.Conn
+	Dest   net.Destination
+	cached buf.MultiBuffer
+}
+
+func (w *packetConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
+	if w.cached != nil {
+		mb, bb := buf.SplitFirst(w.cached)
+		if bb == nil {
+			w.cached = nil
+		} else {
+			buffer.Write(bb.Bytes())
+			w.cached = mb
+			var destination net.Destination
+			if bb.UDP != nil {
+				destination = *bb.UDP
+			} else {
+				destination = w.Dest
+			}
+			bb.Release()
+			return toSocksaddr(destination), nil
+		}
+	}
+	mb, err := w.ReadMultiBuffer()
+	if err != nil {
+		return M.Socksaddr{}, err
+	}
+	nb, bb := buf.SplitFirst(mb)
+	if bb == nil {
+		return M.Socksaddr{}, nil
+	} else {
+		buffer.Write(bb.Bytes())
+		w.cached = nb
+		var destination net.Destination
+		if bb.UDP != nil {
+			destination = *bb.UDP
+		} else {
+			destination = w.Dest
+		}
+		bb.Release()
+		return toSocksaddr(destination), nil
+	}
+}
+
+func (w *packetConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {
+	vBuf := buf.New()
+	vBuf.Write(buffer.Bytes())
+	endpoint := toDestination(destination, net.Network_UDP)
+	vBuf.UDP = &endpoint
+	return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})
+}
+
+func (w *packetConnWrapper) Close() error {
+	common.Interrupt(w.Reader)
+	common.Close(w.Conn)
+	buf.ReleaseMulti(w.cached)
+	return nil
+}