1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2025-02-20 23:47:21 -05:00

add shadowsocks2022 tcp client support

This commit is contained in:
Shelikhoo 2023-11-03 20:10:11 +00:00 committed by Xiaokang Wang (Shelikhoo)
parent b583ef45b1
commit e575a525bb
16 changed files with 979 additions and 3 deletions

View File

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

View File

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

5
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/google/gopacket v1.1.19
github.com/gorilla/websocket v1.5.1
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/mustafaturan/bus v1.0.2
github.com/pelletier/go-toml v1.9.5
@ -36,6 +37,7 @@ require (
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
h12.io/socks v1.0.3
lukechampine.com/blake3 v1.2.1
)
require (
@ -59,7 +61,6 @@ require (
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/reedsolomon v1.11.7 // 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/onsi/ginkgo/v2 v2.10.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
@ -81,3 +82,5 @@ require (
golang.org/x/tools v0.14.0 // 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

6
go.sum
View File

@ -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/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-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/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=
@ -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/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/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.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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=

View File

@ -54,6 +54,8 @@ import (
_ "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/shadowsocks2022"
// Transports
_ "github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket"
_ "github.com/v2fly/v2ray-core/v5/transport/internet/grpc"

View File

@ -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)
}))
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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{})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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),
)