Browse Source

add shadowsocks2022 tcp client support

Shelikhoo 2 years ago
parent
commit
e575a525bb

+ 1 - 0
common/buf/io.go

@@ -25,6 +25,7 @@ type TimeoutReader interface {
 // Writer extends io.Writer with MultiBuffer.
 // Writer extends io.Writer with MultiBuffer.
 type Writer interface {
 type Writer interface {
 	// WriteMultiBuffer writes a MultiBuffer into underlying writer.
 	// WriteMultiBuffer writes a MultiBuffer into underlying writer.
+	// Caller relinquish the ownership of MultiBuffer after calling this method.
 	WriteMultiBuffer(MultiBuffer) error
 	WriteMultiBuffer(MultiBuffer) error
 }
 }
 
 

+ 1 - 0
common/crypto/chunk.go

@@ -10,6 +10,7 @@ import (
 
 
 // ChunkSizeDecoder is a utility class to decode size value from bytes.
 // ChunkSizeDecoder is a utility class to decode size value from bytes.
 type ChunkSizeDecoder interface {
 type ChunkSizeDecoder interface {
+	// SizeBytes must be stable, return the same value across all calls
 	SizeBytes() int32
 	SizeBytes() int32
 	Decode([]byte) (uint16, error)
 	Decode([]byte) (uint16, error)
 }
 }

+ 4 - 1
go.mod

@@ -13,6 +13,7 @@ require (
 	github.com/google/gopacket v1.1.19
 	github.com/google/gopacket v1.1.19
 	github.com/gorilla/websocket v1.5.1
 	github.com/gorilla/websocket v1.5.1
 	github.com/jhump/protoreflect v1.15.3
 	github.com/jhump/protoreflect v1.15.3
+	github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
 	github.com/miekg/dns v1.1.57
 	github.com/miekg/dns v1.1.57
 	github.com/mustafaturan/bus v1.0.2
 	github.com/mustafaturan/bus v1.0.2
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pelletier/go-toml v1.9.5
@@ -36,6 +37,7 @@ require (
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1
 	gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
 	gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
 	h12.io/socks v1.0.3
 	h12.io/socks v1.0.3
+	lukechampine.com/blake3 v1.2.1
 )
 )
 
 
 require (
 require (
@@ -59,7 +61,6 @@ require (
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/klauspost/reedsolomon v1.11.7 // indirect
 	github.com/klauspost/reedsolomon v1.11.7 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
-	github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
 	github.com/mustafaturan/monoton v1.0.0 // indirect
 	github.com/mustafaturan/monoton v1.0.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.10.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.10.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
@@ -81,3 +82,5 @@ require (
 	golang.org/x/tools v0.14.0 // indirect
 	golang.org/x/tools v0.14.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 )
 )
+
+replace github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 => github.com/xiaokangwang/struc v0.0.0-20231031203518-0e381172f248

+ 4 - 2
go.sum

@@ -195,8 +195,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -330,6 +328,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
+github.com/xiaokangwang/struc v0.0.0-20231031203518-0e381172f248 h1:C1JR/QGoOd2e00yquW+C3M8R88ZZ+9oPh+ehS+mPJjQ=
+github.com/xiaokangwang/struc v0.0.0-20231031203518-0e381172f248/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/xtaci/smux v1.5.12/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
 github.com/xtaci/smux v1.5.12/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
 github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
 github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
 github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
 github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
@@ -578,4 +578,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
+lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

+ 2 - 0
main/distro/all/all.go

@@ -54,6 +54,8 @@ import (
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound"
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound"
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound"
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound"
 
 
+	_ "github.com/v2fly/v2ray-core/v5/proxy/shadowsocks2022"
+
 	// Transports
 	// Transports
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/grpc"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/grpc"

+ 129 - 0
proxy/shadowsocks2022/client.go

@@ -0,0 +1,129 @@
+package shadowsocks2022
+
+import (
+	"context"
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/retry"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/common/signal"
+	"github.com/v2fly/v2ray-core/v5/common/task"
+	"github.com/v2fly/v2ray-core/v5/transport"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"time"
+)
+
+type Client struct {
+	config *ClientConfig
+	ctx    context.Context
+}
+
+func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
+	outbound := session.OutboundFromContext(ctx)
+	if outbound == nil || !outbound.Target.IsValid() {
+		return newError("target not specified")
+	}
+	destination := outbound.Target
+	network := destination.Network
+
+	var conn internet.Connection
+
+	err := retry.ExponentialBackoff(5, 100).On(func() error {
+		dest := net.TCPDestination(c.config.Address.AsAddress(), net.Port(c.config.Port))
+		dest.Network = network
+		rawConn, err := dialer.Dial(ctx, dest)
+		if err != nil {
+			return err
+		}
+		conn = rawConn
+
+		return nil
+	})
+	if err != nil {
+		return newError("failed to find an available destination").AtWarning().Base(err)
+	}
+	newError("tunneling request to ", destination, " via ", network, ":", c.config.Address).WriteToLog(session.ExportIDToError(ctx))
+	defer conn.Close()
+
+	var keyDerivation = newBLAKE3KeyDerivation()
+	var method Method
+	switch c.config.Method {
+	case "2022-blake3-aes-128-gcm":
+		method = newAES128GCMMethod()
+	case "2022-blake3-aes-256-gcm":
+		method = newAES256GCMMethod()
+	default:
+		return newError("unknown method: ", c.config.Method)
+	}
+
+	effectivePsk := c.config.Psk
+
+	ctx, cancel := context.WithCancel(ctx)
+	timer := signal.CancelAfterInactivity(ctx, cancel, time.Minute)
+
+	if network == net.Network_TCP {
+		request := &TCPRequest{
+			keyDerivation: keyDerivation,
+			method:        method,
+		}
+		TCPRequestBuffer := buf.New()
+		defer TCPRequestBuffer.Release()
+		err = request.EncodeTCPRequestHeader(effectivePsk, c.config.Ipsk, destination.Address,
+			int(destination.Port), nil, TCPRequestBuffer)
+		if err != nil {
+			return newError("failed to encode TCP request header").Base(err)
+		}
+		_, err = conn.Write(TCPRequestBuffer.Bytes())
+		if err != nil {
+			return newError("failed to write TCP request header").Base(err)
+		}
+		requestDone := func() error {
+			encodedWriter := request.CreateClientC2SWriter(conn)
+			return buf.Copy(link.Reader, encodedWriter, buf.UpdateActivity(timer))
+		}
+		responseDone := func() error {
+			err = request.DecodeTCPResponseHeader(effectivePsk, conn)
+			if err != nil {
+				return newError("failed to decode TCP response header").Base(err)
+			}
+			if err = request.CheckC2SConnectionConstraint(); err != nil {
+				return newError("C2S connection constraint violation").Base(err)
+			}
+			initialPayload := buf.NewWithSize(65535)
+			encodedReader, err := request.CreateClientS2CReader(conn, initialPayload)
+			if err != nil {
+				return newError("failed to create client S2C reader").Base(err)
+			}
+			err = link.Writer.WriteMultiBuffer(buf.MultiBuffer{initialPayload})
+			if err != nil {
+				return newError("failed to write initial payload").Base(err)
+			}
+			return buf.Copy(encodedReader, link.Writer, buf.UpdateActivity(timer))
+		}
+		responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
+		if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
+			return newError("connection ends").Base(err)
+		}
+		return nil
+	} else {
+		return newError("not implemented")
+	}
+}
+
+func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
+	return &Client{
+		config: config,
+		ctx:    ctx,
+	}, nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		clientConfig, ok := config.(*ClientConfig)
+		if !ok {
+			return nil, newError("not a ClientConfig")
+		}
+		return NewClient(ctx, clientConfig)
+	}))
+}

+ 195 - 0
proxy/shadowsocks2022/config.pb.go

@@ -0,0 +1,195 @@
+package shadowsocks2022
+
+import (
+	net "github.com/v2fly/v2ray-core/v5/common/net"
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	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 ClientConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Method  string          `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+	Psk     []byte          `protobuf:"bytes,2,opt,name=psk,proto3" json:"psk,omitempty"`
+	Ipsk    [][]byte        `protobuf:"bytes,4,rep,name=ipsk,proto3" json:"ipsk,omitempty"`
+	Address *net.IPOrDomain `protobuf:"bytes,5,opt,name=address,proto3" json:"address,omitempty"`
+	Port    uint32          `protobuf:"varint,6,opt,name=port,proto3" json:"port,omitempty"`
+}
+
+func (x *ClientConfig) Reset() {
+	*x = ClientConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_shadowsocks2022_config_proto_msgTypes[0]
+		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_shadowsocks2022_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 ClientConfig.ProtoReflect.Descriptor instead.
+func (*ClientConfig) Descriptor() ([]byte, []int) {
+	return file_proxy_shadowsocks2022_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ClientConfig) GetMethod() string {
+	if x != nil {
+		return x.Method
+	}
+	return ""
+}
+
+func (x *ClientConfig) GetPsk() []byte {
+	if x != nil {
+		return x.Psk
+	}
+	return nil
+}
+
+func (x *ClientConfig) GetIpsk() [][]byte {
+	if x != nil {
+		return x.Ipsk
+	}
+	return nil
+}
+
+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
+}
+
+var File_proxy_shadowsocks2022_config_proto protoreflect.FileDescriptor
+
+var file_proxy_shadowsocks2022_config_proto_rawDesc = []byte{
+	0x0a, 0x22, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f,
+	0x63, 0x6b, 0x73, 0x32, 0x30, 0x32, 0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63,
+	0x6b, 0x73, 0x32, 0x30, 0x32, 0x32, 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,
+	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78,
+	0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x22, 0xbe, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 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, 0x70,
+	0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x70, 0x73, 0x6b, 0x12, 0x12, 0x0a,
+	0x04, 0x69, 0x70, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x73,
+	0x6b, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
+	0x72, 0x74, 0x3a, 0x1f, 0x82, 0xb5, 0x18, 0x1b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75,
+	0x6e, 0x64, 0x12, 0x0f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x32,
+	0x30, 0x32, 0x32, 0x42, 0x81, 0x01, 0x0a, 0x24, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61,
+	0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x32, 0x30, 0x32, 0x32, 0x50, 0x01, 0x5a, 0x34,
+	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79,
+	0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
+	0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x20, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
+	0x65, 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_shadowsocks2022_config_proto_rawDescOnce sync.Once
+	file_proxy_shadowsocks2022_config_proto_rawDescData = file_proxy_shadowsocks2022_config_proto_rawDesc
+)
+
+func file_proxy_shadowsocks2022_config_proto_rawDescGZIP() []byte {
+	file_proxy_shadowsocks2022_config_proto_rawDescOnce.Do(func() {
+		file_proxy_shadowsocks2022_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_shadowsocks2022_config_proto_rawDescData)
+	})
+	return file_proxy_shadowsocks2022_config_proto_rawDescData
+}
+
+var file_proxy_shadowsocks2022_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_proxy_shadowsocks2022_config_proto_goTypes = []interface{}{
+	(*ClientConfig)(nil),   // 0: v2ray.core.proxy.shadowsocks2022.ClientConfig
+	(*net.IPOrDomain)(nil), // 1: v2ray.core.common.net.IPOrDomain
+}
+var file_proxy_shadowsocks2022_config_proto_depIdxs = []int32{
+	1, // 0: v2ray.core.proxy.shadowsocks2022.ClientConfig.address:type_name -> v2ray.core.common.net.IPOrDomain
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_proxy_shadowsocks2022_config_proto_init() }
+func file_proxy_shadowsocks2022_config_proto_init() {
+	if File_proxy_shadowsocks2022_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proxy_shadowsocks2022_config_proto_msgTypes[0].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_shadowsocks2022_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_proxy_shadowsocks2022_config_proto_goTypes,
+		DependencyIndexes: file_proxy_shadowsocks2022_config_proto_depIdxs,
+		MessageInfos:      file_proxy_shadowsocks2022_config_proto_msgTypes,
+	}.Build()
+	File_proxy_shadowsocks2022_config_proto = out.File
+	file_proxy_shadowsocks2022_config_proto_rawDesc = nil
+	file_proxy_shadowsocks2022_config_proto_goTypes = nil
+	file_proxy_shadowsocks2022_config_proto_depIdxs = nil
+}

+ 22 - 0
proxy/shadowsocks2022/config.proto

@@ -0,0 +1,22 @@
+syntax = "proto3";
+
+package v2ray.core.proxy.shadowsocks2022;
+option csharp_namespace = "V2Ray.Core.Proxy.Shadowsocks2022";
+option go_package = "github.com/v2fly/v2ray-core/v5/proxy/shadowsocks2022";
+option java_package = "com.v2ray.core.proxy.shadowsocks2022";
+option java_multiple_files = true;
+
+import "common/net/address.proto";
+import "common/protoext/extensions.proto";
+
+message ClientConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "outbound";
+  option (v2ray.core.common.protoext.message_opt).short_name = "shadowsocks2022";
+
+  string method = 1;
+  bytes psk = 2;
+  repeated bytes ipsk = 4;
+
+  v2ray.core.common.net.IPOrDomain address = 5;
+  uint32 port = 6;
+}

+ 104 - 0
proxy/shadowsocks2022/eih_aes.go

@@ -0,0 +1,104 @@
+package shadowsocks2022
+
+import (
+	"github.com/lunixbochs/struc"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"io"
+	"lukechampine.com/blake3"
+)
+
+func newAESEIH(size int) *aesEIH {
+	return &aesEIH{length: size}
+}
+
+func newAESEIHWithData(size int, eih [][aesEIHSize]byte) *aesEIH {
+	return &aesEIH{length: size, eih: eih}
+}
+
+const aesEIHSize = 16
+
+type aesEIH struct {
+	eih    [][aesEIHSize]byte
+	length int
+}
+
+func (a *aesEIH) Pack(p []byte, opt *struc.Options) (int, error) {
+	var totalCopy int
+	for i := 0; i < a.length; i++ {
+		n := copy(p[aesEIHSize*i:aesEIHSize*(i+1)], a.eih[i][:])
+		if n != 16 {
+			return 0, newError("failed to pack aesEIH")
+		}
+		totalCopy += n
+	}
+	return totalCopy, nil
+}
+
+func (a *aesEIH) Unpack(r io.Reader, length int, opt *struc.Options) error {
+	a.eih = make([][aesEIHSize]byte, a.length)
+	for i := 0; i < a.length; i++ {
+		n, err := r.Read(a.eih[i][:])
+		if err != nil {
+			return newError("failed to unpack aesEIH").Base(err)
+		}
+		if n != aesEIHSize {
+			return newError("failed to unpack aesEIH")
+		}
+	}
+	return nil
+}
+
+func (a *aesEIH) Size(opt *struc.Options) int {
+	return a.length * aesEIHSize
+}
+
+func (a *aesEIH) String() string {
+	return ""
+}
+
+const aesEIHPskHashSize = 16
+
+type aesEIHGenerator struct {
+	ipsk     [][]byte
+	ipskHash [][aesEIHPskHashSize]byte
+	psk      []byte
+	pskHash  [aesEIHPskHashSize]byte
+	length   int
+}
+
+func newAESEIHGeneratorContainer(size int, effectivePsk []byte, ipsk [][]byte) *aesEIHGenerator {
+	var ipskHash [][aesEIHPskHashSize]byte
+	for _, v := range ipsk {
+		hash := blake3.Sum512(v)
+		ipskHash = append(ipskHash, [aesEIHPskHashSize]byte(hash[:16]))
+	}
+	pskHashFull := blake3.Sum512(effectivePsk)
+	pskHash := [aesEIHPskHashSize]byte(pskHashFull[:16])
+	return &aesEIHGenerator{length: size, ipsk: ipsk, ipskHash: ipskHash, psk: effectivePsk, pskHash: pskHash}
+}
+
+func (a *aesEIHGenerator) GenerateEIH(derivation KeyDerivation, method Method, salt []byte) (ExtensibleIdentityHeaders, error) {
+	eih := make([][16]byte, a.length)
+	current := a.length - 1
+	currentPskHash := a.pskHash
+	for {
+		identityKeyBuf := buf.New()
+		identityKey := identityKeyBuf.Extend(int32(method.GetSessionSubKeyAndSaltLength()))
+		err := derivation.GetIdentitySubKey(a.ipsk[current], salt, identityKey)
+		if err != nil {
+			return nil, newError("failed to get identity sub key").Base(err)
+		}
+		eih[current] = [16]byte{}
+		err = method.GenerateEIH(identityKey, currentPskHash[:], eih[current][:])
+		if err != nil {
+			return nil, newError("failed to generate EIH").Base(err)
+		}
+		current--
+		if current < 0 {
+			break
+		}
+		currentPskHash = a.ipskHash[current]
+		identityKeyBuf.Release()
+	}
+	return newAESEIHWithData(a.length, eih), nil
+}

+ 255 - 0
proxy/shadowsocks2022/encoding.go

@@ -0,0 +1,255 @@
+package shadowsocks2022
+
+import (
+	"bytes"
+	"crypto/cipher"
+	cryptoRand "crypto/rand"
+	"io"
+	"math/rand"
+	"time"
+
+	"github.com/lunixbochs/struc"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/crypto"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+)
+
+type TCPRequest struct {
+	keyDerivation KeyDerivation
+	method        Method
+
+	c2sSalt  RequestSalt
+	c2sNonce crypto.BytesGenerator
+	c2sAEAD  cipher.AEAD
+
+	s2cSalt  RequestSalt
+	s2cNonce crypto.BytesGenerator
+	s2cAEAD  cipher.AEAD
+
+	s2cSaltAssert         RequestSalt
+	s2cInitialPayloadSize int
+}
+
+func (t *TCPRequest) EncodeTCPRequestHeader(effectivePsk []byte,
+	eih [][]byte, address DestinationAddress, destPort int, initialPayload []byte, Out *buf.Buffer) error {
+	requestSalt := newRequestSaltWithLength(t.method.GetSessionSubKeyAndSaltLength())
+	{
+		err := requestSalt.FillAllFrom(cryptoRand.Reader)
+		if err != nil {
+			return newError("failed to fill salt").Base(err)
+		}
+	}
+	t.c2sSalt = requestSalt
+	var sessionKey = make([]byte, t.method.GetSessionSubKeyAndSaltLength())
+	{
+		err := t.keyDerivation.GetSessionSubKey(effectivePsk, requestSalt.Bytes(), sessionKey)
+		if err != nil {
+			return newError("failed to get session sub key").Base(err)
+		}
+	}
+
+	aead, err := t.method.GetStreamAEAD(sessionKey)
+	if err != nil {
+		return newError("failed to get stream AEAD").Base(err)
+	}
+	t.c2sAEAD = aead
+	var paddingLength = TCPMinPaddingLength
+	if initialPayload == nil {
+		initialPayload = []byte{}
+		paddingLength += rand.Intn(TCPMaxPaddingLength) // TODO INSECURE RANDOM USED
+	}
+
+	variableLengthHeader := &TCPRequestHeader3VariableLength{
+		DestinationAddress: address,
+		Contents: struct {
+			PaddingLength uint16 `struc:"sizeof=Padding"`
+			Padding       []byte
+		}(struct {
+			PaddingLength uint16
+			Padding       []byte
+		}{
+			PaddingLength: uint16(paddingLength),
+			Padding:       make([]byte, paddingLength),
+		}),
+	}
+	variableLengthHeaderBuffer := buf.New()
+	defer variableLengthHeaderBuffer.Release()
+	{
+		err := addrParser.WriteAddressPort(variableLengthHeaderBuffer, address, net.Port(destPort))
+		if err != nil {
+			return newError("failed to write address port").Base(err)
+		}
+	}
+	{
+		err := struc.Pack(variableLengthHeaderBuffer, &variableLengthHeader.Contents)
+		if err != nil {
+			return newError("failed to pack variable length header").Base(err)
+		}
+	}
+	{
+		_, err := variableLengthHeaderBuffer.Write(initialPayload)
+		if err != nil {
+			return newError("failed to write initial payload").Base(err)
+		}
+	}
+
+	fixedLengthHeader := &TCPRequestHeader2FixedLength{
+		Type:         TCPHeaderTypeClientToServerStream,
+		Timestamp:    uint64(time.Now().Unix()),
+		HeaderLength: uint16(variableLengthHeaderBuffer.Len()),
+	}
+
+	fixedLengthHeaderBuffer := buf.New()
+	defer fixedLengthHeaderBuffer.Release()
+	{
+		err := struc.Pack(fixedLengthHeaderBuffer, fixedLengthHeader)
+		if err != nil {
+			return newError("failed to pack fixed length header").Base(err)
+		}
+	}
+	eihGenerator := newAESEIHGeneratorContainer(len(eih), effectivePsk, eih)
+	eihHeader, err := eihGenerator.GenerateEIH(t.keyDerivation, t.method, requestSalt.Bytes())
+	if err != nil {
+		return newError("failed to construct EIH").Base(err)
+	}
+	preSessionKeyHeader := &TCPRequestHeader1PreSessionKey{
+		Salt: requestSalt,
+		EIH:  eihHeader,
+	}
+	preSessionKeyHeaderBuffer := buf.New()
+	defer preSessionKeyHeaderBuffer.Release()
+	{
+		err := struc.Pack(preSessionKeyHeaderBuffer, preSessionKeyHeader)
+		if err != nil {
+			return newError("failed to pack pre session key header").Base(err)
+		}
+	}
+	requestNonce := crypto.GenerateInitialAEADNonce()
+	t.c2sNonce = requestNonce
+	{
+		n, err := Out.Write(preSessionKeyHeaderBuffer.BytesFrom(0))
+		if err != nil {
+			return newError("failed to write pre session key header").Base(err)
+		}
+		if int32(n) != preSessionKeyHeaderBuffer.Len() {
+			return newError("failed to write pre session key header")
+		}
+	}
+	{
+		fixedLengthEncrypted := Out.Extend(fixedLengthHeaderBuffer.Len() + int32(aead.Overhead()))
+		aead.Seal(fixedLengthEncrypted[:0], requestNonce(), fixedLengthHeaderBuffer.Bytes(), nil)
+	}
+	{
+		variableLengthEncrypted := Out.Extend(variableLengthHeaderBuffer.Len() + int32(aead.Overhead()))
+		aead.Seal(variableLengthEncrypted[:0], requestNonce(), variableLengthHeaderBuffer.Bytes(), nil)
+	}
+	return nil
+}
+
+func (t *TCPRequest) DecodeTCPResponseHeader(effectivePsk []byte, In io.Reader) error {
+	var preSessionKeyHeader TCPResponseHeader1PreSessionKey
+	preSessionKeyHeader.Salt = newRequestSaltWithLength(t.method.GetSessionSubKeyAndSaltLength())
+	{
+		err := struc.Unpack(In, &preSessionKeyHeader)
+		if err != nil {
+			return newError("failed to unpack pre session key header").Base(err)
+		}
+	}
+	var s2cSalt = preSessionKeyHeader.Salt.Bytes()
+	t.s2cSalt = preSessionKeyHeader.Salt
+	var sessionKey = make([]byte, t.method.GetSessionSubKeyAndSaltLength())
+	{
+		err := t.keyDerivation.GetSessionSubKey(effectivePsk, s2cSalt, sessionKey)
+		if err != nil {
+			return newError("failed to get session sub key").Base(err)
+		}
+	}
+	aead, err := t.method.GetStreamAEAD(sessionKey)
+	if err != nil {
+		return newError("failed to get stream AEAD").Base(err)
+	}
+	t.s2cAEAD = aead
+
+	var fixedLengthHeaderEncryptedBuffer = buf.New()
+	defer fixedLengthHeaderEncryptedBuffer.Release()
+	{
+		_, err := fixedLengthHeaderEncryptedBuffer.ReadFullFrom(In, 11+int32(t.method.GetSessionSubKeyAndSaltLength())+int32(aead.Overhead()))
+		if err != nil {
+			return newError("failed to read fixed length header encrypted").Base(err)
+		}
+	}
+	s2cNonce := crypto.GenerateInitialAEADNonce()
+	t.s2cNonce = s2cNonce
+	var fixedLengthHeaderDecryptedBuffer = buf.New()
+	defer fixedLengthHeaderDecryptedBuffer.Release()
+	{
+		decryptionBuffer := fixedLengthHeaderDecryptedBuffer.Extend(11 + int32(t.method.GetSessionSubKeyAndSaltLength()))
+		_, err = aead.Open(decryptionBuffer[:0], s2cNonce(), fixedLengthHeaderEncryptedBuffer.Bytes(), nil)
+		if err != nil {
+			return newError("failed to decrypt fixed length header").Base(err)
+		}
+	}
+	var fixedLengthHeader TCPResponseHeader2FixedLength
+	fixedLengthHeader.RequestSalt = newRequestSaltWithLength(t.method.GetSessionSubKeyAndSaltLength())
+	{
+		err := struc.Unpack(bytes.NewReader(fixedLengthHeaderDecryptedBuffer.Bytes()), &fixedLengthHeader)
+		if err != nil {
+			return newError("failed to unpack fixed length header").Base(err)
+		}
+	}
+
+	if fixedLengthHeader.Type != TCPHeaderTypeServerToClientStream {
+		return newError("unexpected TCP header type")
+	}
+	timeDifference := int64(fixedLengthHeader.Timestamp) - time.Now().Unix()
+	if timeDifference < -60 || timeDifference > 60 {
+		return newError("timestamp is too far away")
+	}
+
+	t.s2cSaltAssert = fixedLengthHeader.RequestSalt
+	t.s2cInitialPayloadSize = int(fixedLengthHeader.InitialPayloadLength)
+	return nil
+}
+
+func (t *TCPRequest) CheckC2SConnectionConstraint() error {
+	if bytes.Compare(t.c2sSalt.Bytes(), t.s2cSaltAssert.Bytes()) != 0 {
+		return newError("c2s salt not equal to s2c salt assert")
+	}
+	return nil
+}
+
+func (t *TCPRequest) CreateClientS2CReader(In io.Reader, initialPayload *buf.Buffer) (buf.Reader, error) {
+	AEADAuthenticator := &crypto.AEADAuthenticator{
+		AEAD:                    t.s2cAEAD,
+		NonceGenerator:          t.s2cNonce,
+		AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
+	}
+	initialPayloadEncrypted := buf.NewWithSize(65535)
+	defer initialPayloadEncrypted.Release()
+	initialPayloadEncryptedBytes := initialPayloadEncrypted.Extend(int32(t.s2cAEAD.Overhead()) + int32(t.s2cInitialPayloadSize))
+	_, err := io.ReadFull(In, initialPayloadEncryptedBytes)
+	if err != nil {
+		return nil, newError("failed to read initial payload").Base(err)
+	}
+	initialPayloadBytes := initialPayload.Extend(int32(t.s2cInitialPayloadSize))
+	_, err = t.s2cAEAD.Open(initialPayloadBytes[:0], t.s2cNonce(), initialPayloadEncryptedBytes, nil)
+	if err != nil {
+		return nil, newError("failed to decrypt initial payload").Base(err)
+	}
+	return crypto.NewAuthenticationReader(AEADAuthenticator, &crypto.AEADChunkSizeParser{
+		Auth: AEADAuthenticator,
+	}, In, protocol.TransferTypeStream, nil), nil
+}
+
+func (t *TCPRequest) CreateClientC2SWriter(writer io.Writer) buf.Writer {
+	AEADAuthenticator := &crypto.AEADAuthenticator{
+		AEAD:                    t.c2sAEAD,
+		NonceGenerator:          t.c2sNonce,
+		AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
+	}
+	sizeParser := &crypto.AEADChunkSizeParser{
+		Auth: AEADAuthenticator,
+	}
+	return crypto.NewAuthenticationWriter(AEADAuthenticator, sizeParser, writer, protocol.TransferTypeStream, nil)
+}

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

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

+ 31 - 0
proxy/shadowsocks2022/kdf_blake3.go

@@ -0,0 +1,31 @@
+package shadowsocks2022
+
+import (
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"lukechampine.com/blake3"
+)
+
+func newBLAKE3KeyDerivation() *BLAKE3KeyDerivation {
+	return &BLAKE3KeyDerivation{}
+}
+
+type BLAKE3KeyDerivation struct {
+}
+
+func (B BLAKE3KeyDerivation) GetSessionSubKey(effectivePsk, Salt []byte, OutKey []byte) error {
+	keyingMaterialBuffer := buf.New()
+	keyingMaterialBuffer.Write(effectivePsk)
+	keyingMaterialBuffer.Write(Salt)
+	blake3.DeriveKey(OutKey, "shadowsocks 2022 session subkey", keyingMaterialBuffer.Bytes())
+	keyingMaterialBuffer.Release()
+	return nil
+}
+
+func (B BLAKE3KeyDerivation) GetIdentitySubKey(effectivePsk, Salt []byte, OutKey []byte) error {
+	keyingMaterialBuffer := buf.New()
+	keyingMaterialBuffer.Write(effectivePsk)
+	keyingMaterialBuffer.Write(Salt)
+	blake3.DeriveKey(OutKey, "shadowsocks 2022 identity subkey", keyingMaterialBuffer.Bytes())
+	keyingMaterialBuffer.Release()
+	return nil
+}

+ 38 - 0
proxy/shadowsocks2022/method_aes128gcm.go

@@ -0,0 +1,38 @@
+package shadowsocks2022
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+)
+
+func newAES128GCMMethod() *AES128GCMMethod {
+	return &AES128GCMMethod{}
+}
+
+type AES128GCMMethod struct {
+}
+
+func (A AES128GCMMethod) GetSessionSubKeyAndSaltLength() int {
+	return 16
+}
+
+func (A AES128GCMMethod) GetStreamAEAD(SessionSubKey []byte) (cipher.AEAD, error) {
+	aesCipher, err := aes.NewCipher(SessionSubKey)
+	if err != nil {
+		return nil, newError("failed to create AES cipher").Base(err)
+	}
+	aead, err := cipher.NewGCM(aesCipher)
+	if err != nil {
+		return nil, newError("failed to create AES-GCM AEAD").Base(err)
+	}
+	return aead, nil
+}
+
+func (A AES128GCMMethod) GenerateEIH(CurrentIdentitySubKey []byte, nextPskHash []byte, out []byte) error {
+	aesCipher, err := aes.NewCipher(CurrentIdentitySubKey)
+	if err != nil {
+		return newError("failed to create AES cipher").Base(err)
+	}
+	aesCipher.Encrypt(out, nextPskHash)
+	return nil
+}

+ 38 - 0
proxy/shadowsocks2022/method_aes256gcm.go

@@ -0,0 +1,38 @@
+package shadowsocks2022
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+)
+
+func newAES256GCMMethod() *AES256GCMMethod {
+	return &AES256GCMMethod{}
+}
+
+type AES256GCMMethod struct {
+}
+
+func (A AES256GCMMethod) GetSessionSubKeyAndSaltLength() int {
+	return 32
+}
+
+func (A AES256GCMMethod) GetStreamAEAD(SessionSubKey []byte) (cipher.AEAD, error) {
+	aesCipher, err := aes.NewCipher(SessionSubKey)
+	if err != nil {
+		return nil, newError("failed to create AES cipher").Base(err)
+	}
+	aead, err := cipher.NewGCM(aesCipher)
+	if err != nil {
+		return nil, newError("failed to create AES-GCM AEAD").Base(err)
+	}
+	return aead, nil
+}
+
+func (A AES256GCMMethod) GenerateEIH(CurrentIdentitySubKey []byte, nextPskHash []byte, out []byte) error {
+	aesCipher, err := aes.NewCipher(CurrentIdentitySubKey)
+	if err != nil {
+		return newError("failed to create AES cipher").Base(err)
+	}
+	aesCipher.Encrypt(out, nextPskHash)
+	return nil
+}

+ 58 - 0
proxy/shadowsocks2022/requestsalt.go

@@ -0,0 +1,58 @@
+package shadowsocks2022
+
+import (
+	"encoding/hex"
+	"github.com/lunixbochs/struc"
+	"io"
+)
+
+func newRequestSaltWithLength(length int) RequestSalt {
+	return &requestSaltWithLength{length: length}
+}
+
+type requestSaltWithLength struct {
+	length  int
+	content []byte
+}
+
+func (r *requestSaltWithLength) isRequestSalt() {}
+func (r *requestSaltWithLength) Pack(p []byte, opt *struc.Options) (int, error) {
+	n := copy(p, r.content)
+	if n != r.length {
+		return 0, newError("failed to pack request salt with length")
+	}
+	return n, nil
+}
+
+func (r *requestSaltWithLength) Unpack(reader io.Reader, length int, opt *struc.Options) error {
+	r.content = make([]byte, r.length)
+	n, err := io.ReadFull(reader, r.content)
+	if err != nil {
+		return newError("failed to unpack request salt with length").Base(err)
+	}
+	if n != r.length {
+		return newError("failed to unpack request salt with length")
+	}
+	return nil
+}
+
+func (r *requestSaltWithLength) Size(opt *struc.Options) int {
+	return r.length
+}
+
+func (r *requestSaltWithLength) String() string {
+	return hex.Dump(r.content)
+}
+
+func (r *requestSaltWithLength) Bytes() []byte {
+	return r.content
+}
+
+func (r *requestSaltWithLength) FillAllFrom(reader io.Reader) error {
+	r.content = make([]byte, r.length)
+	_, err := io.ReadFull(reader, r.content)
+	if err != nil {
+		return newError("failed to fill salt from reader").Base(err)
+	}
+	return nil
+}

+ 88 - 0
proxy/shadowsocks2022/ss2022.go

@@ -0,0 +1,88 @@
+package shadowsocks2022
+
+import (
+	"crypto/cipher"
+	"github.com/lunixbochs/struc"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+	"io"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+type KeyDerivation interface {
+	GetSessionSubKey(effectivePsk, Salt []byte, OutKey []byte) error
+	GetIdentitySubKey(effectivePsk, Salt []byte, OutKey []byte) error
+}
+
+type Method interface {
+	GetSessionSubKeyAndSaltLength() int
+	GetStreamAEAD(SessionSubKey []byte) (cipher.AEAD, error)
+	GenerateEIH(CurrentIdentitySubKey []byte, nextPskHash []byte, out []byte) error
+}
+
+type ExtensibleIdentityHeaders interface {
+	struc.Custom
+}
+
+type DestinationAddress interface {
+	net.Address
+}
+
+type RequestSalt interface {
+	struc.Custom
+	isRequestSalt()
+	Bytes() []byte
+	FillAllFrom(reader io.Reader) error
+}
+
+type TCPRequestHeader1PreSessionKey struct {
+	Salt RequestSalt
+	EIH  ExtensibleIdentityHeaders
+}
+
+type TCPRequestHeader2FixedLength struct {
+	Type         byte
+	Timestamp    uint64
+	HeaderLength uint16
+}
+
+type TCPRequestHeader3VariableLength struct {
+	DestinationAddress DestinationAddress
+	Contents           struct {
+		PaddingLength uint16 `struc:"sizeof=Padding"`
+		Padding       []byte
+	}
+}
+
+type TCPRequestHeader struct {
+	PreSessionKeyHeader TCPRequestHeader1PreSessionKey
+	FixedLengthHeader   TCPRequestHeader2FixedLength
+	Header              TCPRequestHeader3VariableLength
+}
+
+type TCPResponseHeader1PreSessionKey struct {
+	Salt RequestSalt
+}
+
+type TCPResponseHeader2FixedLength struct {
+	Type                 byte
+	Timestamp            uint64
+	RequestSalt          RequestSalt
+	InitialPayloadLength uint16
+}
+type TCPResponseHeader struct {
+	PreSessionKeyHeader TCPResponseHeader1PreSessionKey
+	Header              TCPResponseHeader2FixedLength
+}
+
+const TCPHeaderTypeClientToServerStream = byte(0x00)
+const TCPHeaderTypeServerToClientStream = byte(0x01)
+const TCPMinPaddingLength = 0
+const TCPMaxPaddingLength = 900
+
+var addrParser = protocol.NewAddressParser(
+	protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),
+	protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),
+	protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),
+)