From a0a32ee00de1aaf9d13023d91bfdb9fd8daebe6c Mon Sep 17 00:00:00 2001 From: RPRX <63339210+rprx@users.noreply.github.com> Date: Sun, 14 Mar 2021 15:02:07 +0000 Subject: [PATCH] Add gRPC Transport support (#356) Co-authored-by: JimhHan <50871214+JimhHan@users.noreply.github.com> --- common/buf/buffer.go | 16 ++ infra/conf/grpc.go | 16 ++ infra/conf/transport.go | 16 ++ infra/conf/transport_internet.go | 17 ++ infra/conf/transport_test.go | 30 +++ main/distro/all/all.go | 1 + testing/scenarios/tls_test.go | 237 ++++++++++++++++++ transport/internet/grpc/config.go | 14 ++ transport/internet/grpc/config.pb.go | 172 +++++++++++++ transport/internet/grpc/config.proto | 10 + transport/internet/grpc/dial.go | 130 ++++++++++ .../grpc/encoding/customSeviceName.go | 60 +++++ transport/internet/grpc/encoding/encoding.go | 3 + .../grpc/encoding/errors.generated.go | 9 + transport/internet/grpc/encoding/hunkconn.go | 123 +++++++++ transport/internet/grpc/encoding/multiconn.go | 108 ++++++++ transport/internet/grpc/encoding/stream.pb.go | 234 +++++++++++++++++ transport/internet/grpc/encoding/stream.proto | 17 ++ .../internet/grpc/encoding/stream_grpc.pb.go | 201 +++++++++++++++ transport/internet/grpc/errors.generated.go | 9 + transport/internet/grpc/grpc.go | 3 + transport/internet/grpc/hub.go | 129 ++++++++++ transport/internet/http/dialer.go | 13 +- 23 files changed, 1564 insertions(+), 4 deletions(-) create mode 100644 infra/conf/grpc.go create mode 100644 transport/internet/grpc/config.go create mode 100644 transport/internet/grpc/config.pb.go create mode 100644 transport/internet/grpc/config.proto create mode 100644 transport/internet/grpc/dial.go create mode 100644 transport/internet/grpc/encoding/customSeviceName.go create mode 100644 transport/internet/grpc/encoding/encoding.go create mode 100644 transport/internet/grpc/encoding/errors.generated.go create mode 100644 transport/internet/grpc/encoding/hunkconn.go create mode 100644 transport/internet/grpc/encoding/multiconn.go create mode 100644 transport/internet/grpc/encoding/stream.pb.go create mode 100644 transport/internet/grpc/encoding/stream.proto create mode 100644 transport/internet/grpc/encoding/stream_grpc.pb.go create mode 100644 transport/internet/grpc/errors.generated.go create mode 100644 transport/internet/grpc/grpc.go create mode 100644 transport/internet/grpc/hub.go diff --git a/common/buf/buffer.go b/common/buf/buffer.go index 9b32c6c7..ec0c32bd 100644 --- a/common/buf/buffer.go +++ b/common/buf/buffer.go @@ -31,6 +31,22 @@ func New() *Buffer { } } +func NewExisted(b []byte) *Buffer { + if cap(b) < Size { + panic("Invalid buffer") + } + + oLen := len(b) + if oLen < Size { + b = append(b, make([]byte, Size-oLen)...) + } + + return &Buffer{ + v: b, + end: int32(oLen), + } +} + // StackNew creates a new Buffer object on stack. // This method is for buffers that is released in the same function. func StackNew() Buffer { diff --git a/infra/conf/grpc.go b/infra/conf/grpc.go new file mode 100644 index 00000000..e39d314d --- /dev/null +++ b/infra/conf/grpc.go @@ -0,0 +1,16 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + + "github.com/xtls/xray-core/transport/internet/grpc" +) + +type GRPCConfig struct { + ServiceName string `json:"serviceName"` + MultiMode bool `json:"multiMode"` +} + +func (g GRPCConfig) Build() (proto.Message, error) { + return &grpc.Config{ServiceName: g.ServiceName, MultiMode: g.MultiMode}, nil +} diff --git a/infra/conf/transport.go b/infra/conf/transport.go index 1108167b..fa8a3886 100644 --- a/infra/conf/transport.go +++ b/infra/conf/transport.go @@ -13,6 +13,8 @@ type TransportConfig struct { HTTPConfig *HTTPConfig `json:"httpSettings"` DSConfig *DomainSocketConfig `json:"dsSettings"` QUICConfig *QUICConfig `json:"quicSettings"` + GRPCConfig *GRPCConfig `json:"grpcSettings"` + GUNConfig *GRPCConfig `json:"gunSettings"` } // Build implements Buildable. @@ -85,5 +87,19 @@ func (c *TransportConfig) Build() (*global.Config, error) { }) } + if c.GRPCConfig == nil { + c.GRPCConfig = c.GUNConfig + } + if c.GRPCConfig != nil { + gs, err := c.GRPCConfig.Build() + if err != nil { + return nil, newError("Failed to build gRPC config.").Base(err) + } + config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(gs), + }) + } + return config, nil } diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index c8f5c18f..15d5dd32 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -458,6 +458,8 @@ func (p TransportProtocol) Build() (string, error) { return "domainsocket", nil case "quic": return "quic", nil + case "grpc", "gun": + return "grpc", nil default: return "", newError("Config: unknown transport protocol: ", p) } @@ -534,6 +536,8 @@ type StreamConfig struct { DSSettings *DomainSocketConfig `json:"dsSettings"` QUICSettings *QUICConfig `json:"quicSettings"` SocketSettings *SocketConfig `json:"sockopt"` + GRPCConfig *GRPCConfig `json:"grpcSettings"` + GUNConfig *GRPCConfig `json:"gunSettings"` } // Build implements Buildable. @@ -643,6 +647,19 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { Settings: serial.ToTypedMessage(qs), }) } + if c.GRPCConfig == nil { + c.GRPCConfig = c.GUNConfig + } + if c.GRPCConfig != nil { + gs, err := c.GRPCConfig.Build() + if err != nil { + return nil, newError("Failed to build gRPC config.").Base(err) + } + config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(gs), + }) + } if c.SocketSettings != nil { ss, err := c.SocketSettings.Build() if err != nil { diff --git a/infra/conf/transport_test.go b/infra/conf/transport_test.go index acc15dd0..a3e9cc74 100644 --- a/infra/conf/transport_test.go +++ b/infra/conf/transport_test.go @@ -10,6 +10,7 @@ import ( . "github.com/xtls/xray-core/infra/conf" "github.com/xtls/xray-core/transport/global" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/grpc" "github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/noop" "github.com/xtls/xray-core/transport/internet/headers/tls" @@ -120,6 +121,10 @@ func TestTransportConfig(t *testing.T) { "header": { "type": "dtls" } + }, + "grpcSettings": { + "serviceName": "name", + "multiMode": true } }`, Parser: createParser(), @@ -190,6 +195,31 @@ func TestTransportConfig(t *testing.T) { Header: serial.ToTypedMessage(&tls.PacketConfig{}), }), }, + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ + ServiceName: "name", + MultiMode: true, + }), + }, + }, + }, + }, + { + Input: `{ + "gunSettings": { + "serviceName": "name" + } + }`, + Parser: createParser(), + Output: &global.Config{ + TransportSettings: []*internet.TransportConfig{ + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ + ServiceName: "name", + }), + }, }, }, }, diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 0284b33a..301987ce 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -40,6 +40,7 @@ import ( // Transports _ "github.com/xtls/xray-core/transport/internet/domainsocket" + _ "github.com/xtls/xray-core/transport/internet/grpc" _ "github.com/xtls/xray-core/transport/internet/http" _ "github.com/xtls/xray-core/transport/internet/kcp" _ "github.com/xtls/xray-core/transport/internet/quic" diff --git a/testing/scenarios/tls_test.go b/testing/scenarios/tls_test.go index cb420821..83699f2d 100644 --- a/testing/scenarios/tls_test.go +++ b/testing/scenarios/tls_test.go @@ -24,6 +24,7 @@ import ( "github.com/xtls/xray-core/testing/servers/tcp" "github.com/xtls/xray-core/testing/servers/udp" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/grpc" "github.com/xtls/xray-core/transport/internet/http" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/websocket" @@ -589,3 +590,239 @@ func TestHTTP2(t *testing.T) { t.Error(err) } } + +func TestGRPC(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + ProtocolName: "grpc", + TransportSettings: []*internet.TransportConfig{ + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ServiceName: "🍉"}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + ProtocolName: "grpc", + TransportSettings: []*internet.TransportConfig{ + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ServiceName: "🍉"}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errg errgroup.Group + for i := 0; i < 10; i++ { + errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40)) + } + if err := errg.Wait(); err != nil { + t.Error(err) + } +} + +func TestGRPCMultiMode(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + ProtocolName: "grpc", + TransportSettings: []*internet.TransportConfig{ + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ServiceName: "🍉"}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + ProtocolName: "grpc", + TransportSettings: []*internet.TransportConfig{ + { + ProtocolName: "grpc", + Settings: serial.ToTypedMessage(&grpc.Config{ServiceName: "🍉", MultiMode: true}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errg errgroup.Group + for i := 0; i < 10; i++ { + errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40)) + } + if err := errg.Wait(); err != nil { + t.Error(err) + } +} diff --git a/transport/internet/grpc/config.go b/transport/internet/grpc/config.go new file mode 100644 index 00000000..4be47ec2 --- /dev/null +++ b/transport/internet/grpc/config.go @@ -0,0 +1,14 @@ +package grpc + +import ( + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/transport/internet" +) + +const protocolName = "grpc" + +func init() { + common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { + return new(Config) + })) +} diff --git a/transport/internet/grpc/config.pb.go b/transport/internet/grpc/config.pb.go new file mode 100644 index 00000000..4f8fcb81 --- /dev/null +++ b/transport/internet/grpc/config.pb.go @@ -0,0 +1,172 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.15.6 +// source: transport/internet/grpc/config.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + 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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + MultiMode bool `protobuf:"varint,3,opt,name=multi_mode,json=multiMode,proto3" json:"multi_mode,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_grpc_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_grpc_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 Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_grpc_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *Config) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *Config) GetMultiMode() bool { + if x != nil { + return x.MultiMode + } + return false +} + +var File_transport_internet_grpc_config_proto protoreflect.FileDescriptor + +var file_transport_internet_grpc_config_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x25, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x5e, 0x0a, + 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x33, 0x5a, + 0x31, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_transport_internet_grpc_config_proto_rawDescOnce sync.Once + file_transport_internet_grpc_config_proto_rawDescData = file_transport_internet_grpc_config_proto_rawDesc +) + +func file_transport_internet_grpc_config_proto_rawDescGZIP() []byte { + file_transport_internet_grpc_config_proto_rawDescOnce.Do(func() { + file_transport_internet_grpc_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_grpc_config_proto_rawDescData) + }) + return file_transport_internet_grpc_config_proto_rawDescData +} + +var file_transport_internet_grpc_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_grpc_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: xray.transport.internet.grpc.encoding.Config +} +var file_transport_internet_grpc_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_grpc_config_proto_init() } +func file_transport_internet_grpc_config_proto_init() { + if File_transport_internet_grpc_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_grpc_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); 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_transport_internet_grpc_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_grpc_config_proto_goTypes, + DependencyIndexes: file_transport_internet_grpc_config_proto_depIdxs, + MessageInfos: file_transport_internet_grpc_config_proto_msgTypes, + }.Build() + File_transport_internet_grpc_config_proto = out.File + file_transport_internet_grpc_config_proto_rawDesc = nil + file_transport_internet_grpc_config_proto_goTypes = nil + file_transport_internet_grpc_config_proto_depIdxs = nil +} diff --git a/transport/internet/grpc/config.proto b/transport/internet/grpc/config.proto new file mode 100644 index 00000000..9c4330b2 --- /dev/null +++ b/transport/internet/grpc/config.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package xray.transport.internet.grpc.encoding; +option go_package = "github.com/xtls/xray-core/transport/internet/grpc"; + +message Config { + string host = 1; + string service_name = 2; + bool multi_mode = 3; +} diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go new file mode 100644 index 00000000..457f67df --- /dev/null +++ b/transport/internet/grpc/dial.go @@ -0,0 +1,130 @@ +package grpc + +import ( + "context" + gonet "net" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/grpc/encoding" + "github.com/xtls/xray-core/transport/internet/tls" +) + +func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { + newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx)) + + conn, err := dialgRPC(ctx, dest, streamSettings) + if err != nil { + return nil, newError("failed to dial gRPC").Base(err) + } + return internet.Connection(conn), nil +} + +func init() { + common.Must(internet.RegisterTransportDialer(protocolName, Dial)) +} + +type dialerConf struct { + net.Destination + *internet.SocketConfig +} + +var ( + globalDialerMap map[dialerConf]*grpc.ClientConn + globalDialerAccess sync.Mutex +) + +func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { + grpcSettings := streamSettings.ProtocolSettings.(*Config) + + config := tls.ConfigFromStreamSettings(streamSettings) + var dialOption = grpc.WithInsecure() + + if config != nil { + dialOption = grpc.WithTransportCredentials(credentials.NewTLS(config.GetTLSConfig())) + } + + conn, err := getGrpcClient(ctx, dest, dialOption, streamSettings.SocketSettings) + + if err != nil { + return nil, newError("Cannot dial gRPC").Base(err) + } + client := encoding.NewGRPCServiceClient(conn) + if grpcSettings.MultiMode { + newError("using gRPC multi mode").AtDebug().WriteToLog() + grpcService, err := client.(encoding.GRPCServiceClientX).TunMultiCustomName(ctx, grpcSettings.ServiceName) + if err != nil { + return nil, newError("Cannot dial gRPC").Base(err) + } + return encoding.NewMultiHunkConn(grpcService, nil), nil + } + + grpcService, err := client.(encoding.GRPCServiceClientX).TunCustomName(ctx, grpcSettings.ServiceName) + if err != nil { + return nil, newError("Cannot dial gRPC").Base(err) + } + + return encoding.NewHunkConn(grpcService, nil), nil +} + +func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption, sockopt *internet.SocketConfig) (*grpc.ClientConn, error) { + globalDialerAccess.Lock() + defer globalDialerAccess.Unlock() + + if globalDialerMap == nil { + globalDialerMap = make(map[dialerConf]*grpc.ClientConn) + } + + if client, found := globalDialerMap[dialerConf{dest, sockopt}]; found && client.GetState() != connectivity.Shutdown { + return client, nil + } + + conn, err := grpc.Dial( + gonet.JoinHostPort(dest.Address.String(), dest.Port.String()), + dialOption, + grpc.WithConnectParams(grpc.ConnectParams{ + Backoff: backoff.Config{ + BaseDelay: 500 * time.Millisecond, + Multiplier: 1.5, + Jitter: 0.2, + MaxDelay: 19 * time.Second, + }, + MinConnectTimeout: 5 * time.Second, + }), + grpc.WithContextDialer(func(gctx context.Context, s string) (gonet.Conn, error) { + gctx = session.ContextWithID(gctx, session.IDFromContext(ctx)) + gctx = session.ContextWithOutbound(gctx, session.OutboundFromContext(ctx)) + + rawHost, rawPort, err := net.SplitHostPort(s) + select { + case <-gctx.Done(): + return nil, gctx.Err() + default: + } + + if err != nil { + return nil, err + } + if len(rawPort) == 0 { + rawPort = "443" + } + port, err := net.PortFromString(rawPort) + if err != nil { + return nil, err + } + address := net.ParseAddress(rawHost) + return internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) + }), + ) + globalDialerMap[dialerConf{dest, sockopt}] = conn + return conn, err +} diff --git a/transport/internet/grpc/encoding/customSeviceName.go b/transport/internet/grpc/encoding/customSeviceName.go new file mode 100644 index 00000000..d37d61f8 --- /dev/null +++ b/transport/internet/grpc/encoding/customSeviceName.go @@ -0,0 +1,60 @@ +package encoding + +import ( + "context" + + "google.golang.org/grpc" +) + +func ServerDesc(name string) grpc.ServiceDesc { + return grpc.ServiceDesc{ + ServiceName: name, + HandlerType: (*GRPCServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Tun", + Handler: _GRPCService_Tun_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "TunMulti", + Handler: _GRPCService_TunMulti_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpc.proto", + } +} + +func (c *gRPCServiceClient) TunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunClient, error) { + stream, err := c.cc.NewStream(ctx, &ServerDesc(name).Streams[0], "/"+name+"/Tun", opts...) + if err != nil { + return nil, err + } + x := &gRPCServiceTunClient{stream} + return x, nil +} + +func (c *gRPCServiceClient) TunMultiCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) { + stream, err := c.cc.NewStream(ctx, &ServerDesc(name).Streams[0], "/"+name+"/TunMulti", opts...) + if err != nil { + return nil, err + } + x := &gRPCServiceTunMultiClient{stream} + return x, nil +} + +type GRPCServiceClientX interface { + TunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunClient, error) + TunMultiCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) + Tun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error) + TunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) +} + +func RegisterGRPCServiceServerX(s *grpc.Server, srv GRPCServiceServer, name string) { + desc := ServerDesc(name) + s.RegisterService(&desc, srv) +} diff --git a/transport/internet/grpc/encoding/encoding.go b/transport/internet/grpc/encoding/encoding.go new file mode 100644 index 00000000..f8e3e368 --- /dev/null +++ b/transport/internet/grpc/encoding/encoding.go @@ -0,0 +1,3 @@ +package encoding + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/transport/internet/grpc/encoding/errors.generated.go b/transport/internet/grpc/encoding/errors.generated.go new file mode 100644 index 00000000..267711d9 --- /dev/null +++ b/transport/internet/grpc/encoding/errors.generated.go @@ -0,0 +1,9 @@ +package encoding + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/transport/internet/grpc/encoding/hunkconn.go b/transport/internet/grpc/encoding/hunkconn.go new file mode 100644 index 00000000..50815144 --- /dev/null +++ b/transport/internet/grpc/encoding/hunkconn.go @@ -0,0 +1,123 @@ +package encoding + +import ( + "context" + "io" + "net" + + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net/cnc" + "github.com/xtls/xray-core/common/signal/done" +) + +type HunkConn interface { + Send(*Hunk) error + Recv() (*Hunk, error) + SendMsg(m interface{}) error + RecvMsg(m interface{}) error +} + +type StreamCloser interface { + CloseSend() error +} + +type HunkReaderWriter struct { + hc HunkConn + cancel context.CancelFunc + done *done.Instance + + buf []byte + index int +} + +func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter { + return &HunkReaderWriter{hc, cancel, done.New(), nil, 0} +} + +func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn { + wrc := NewHunkReadWriter(hc, cancel) + return cnc.NewConnection( + cnc.ConnectionInput(wrc), + cnc.ConnectionOutput(wrc), + cnc.ConnectionOnClose(wrc), + ) +} + +func (h *HunkReaderWriter) forceFetch() error { + hunk, err := h.hc.Recv() + if err != nil { + if err == io.EOF { + return err + } + + return newError("failed to fetch hunk from gRPC tunnel").Base(err) + } + + h.buf = hunk.Data + h.index = 0 + + return nil +} + +func (h *HunkReaderWriter) Read(buf []byte) (int, error) { + if h.done.Done() { + return 0, io.EOF + } + + if h.index >= len(h.buf) { + if err := h.forceFetch(); err != nil { + return 0, err + } + } + n := copy(buf, h.buf[h.index:]) + h.index += n + + return n, nil +} + +func (h *HunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) { + if h.done.Done() { + return nil, io.EOF + } + if h.index >= len(h.buf) { + if err := h.forceFetch(); err != nil { + return nil, err + } + } + + if cap(h.buf) == buf.Size { + b := h.buf + h.index = len(h.buf) + return buf.MultiBuffer{buf.NewExisted(b)}, nil + } + + b := buf.New() + _, err := b.ReadFrom(h) + if err != nil { + return nil, err + } + return buf.MultiBuffer{b}, nil +} + +func (h *HunkReaderWriter) Write(buf []byte) (int, error) { + if h.done.Done() { + return 0, io.ErrClosedPipe + } + + err := h.hc.Send(&Hunk{Data: buf[:]}) + if err != nil { + return 0, newError("failed to send data over gRPC tunnel").Base(err) + } + return len(buf), nil +} + +func (h *HunkReaderWriter) Close() error { + if h.cancel != nil { + h.cancel() + } + if sc, match := h.hc.(StreamCloser); match { + return sc.CloseSend() + } + + return h.done.Close() +} diff --git a/transport/internet/grpc/encoding/multiconn.go b/transport/internet/grpc/encoding/multiconn.go new file mode 100644 index 00000000..b2b865ab --- /dev/null +++ b/transport/internet/grpc/encoding/multiconn.go @@ -0,0 +1,108 @@ +package encoding + +import ( + "context" + "io" + "net" + + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net/cnc" + "github.com/xtls/xray-core/common/signal/done" +) + +type MultiHunkConn interface { + Send(*MultiHunk) error + Recv() (*MultiHunk, error) + SendMsg(m interface{}) error + RecvMsg(m interface{}) error +} + +type MultiHunkReaderWriter struct { + hc MultiHunkConn + cancel context.CancelFunc + done *done.Instance + + buf [][]byte +} + +func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiHunkReaderWriter { + return &MultiHunkReaderWriter{hc, cancel, done.New(), nil} +} + +func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn { + wrc := NewMultiHunkReadWriter(hc, cancel) + return cnc.NewConnection( + cnc.ConnectionInputMulti(wrc), + cnc.ConnectionOutputMulti(wrc), + cnc.ConnectionOnClose(wrc), + ) +} + +func (h *MultiHunkReaderWriter) forceFetch() error { + hunk, err := h.hc.Recv() + if err != nil { + if err == io.EOF { + return err + } + + return newError("failed to fetch hunk from gRPC tunnel").Base(err) + } + + h.buf = hunk.Data + + return nil +} + +func (h *MultiHunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) { + if h.done.Done() { + return nil, io.EOF + } + + if err := h.forceFetch(); err != nil { + return nil, err + } + + var mb = make(buf.MultiBuffer, 0, len(h.buf)) + for _, b := range h.buf { + if cap(b) >= buf.Size { + mb = append(mb, buf.NewExisted(b)) + continue + } + + nb := buf.New() + nb.Extend(int32(len(b))) + copy(nb.Bytes(), b) + + mb = append(mb, nb) + } + return mb, nil +} + +func (h *MultiHunkReaderWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + defer buf.ReleaseMulti(mb) + if h.done.Done() { + return io.ErrClosedPipe + } + + hunk := &MultiHunk{Data: make([][]byte, len(mb))} + for _, b := range mb { + hunk.Data = append(hunk.Data, b.Bytes()) + } + + err := h.hc.Send(hunk) + if err != nil { + return err + } + return nil +} + +func (h *MultiHunkReaderWriter) Close() error { + if h.cancel != nil { + h.cancel() + } + if sc, match := h.hc.(StreamCloser); match { + return sc.CloseSend() + } + + return h.done.Close() +} diff --git a/transport/internet/grpc/encoding/stream.pb.go b/transport/internet/grpc/encoding/stream.pb.go new file mode 100644 index 00000000..122ab50e --- /dev/null +++ b/transport/internet/grpc/encoding/stream.pb.go @@ -0,0 +1,234 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.15.6 +// source: transport/internet/grpc/encoding/stream.proto + +package encoding + +import ( + proto "github.com/golang/protobuf/proto" + 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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Hunk struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Hunk) Reset() { + *x = Hunk{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Hunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Hunk) ProtoMessage() {} + +func (x *Hunk) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_grpc_encoding_stream_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 Hunk.ProtoReflect.Descriptor instead. +func (*Hunk) Descriptor() ([]byte, []int) { + return file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP(), []int{0} +} + +func (x *Hunk) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type MultiHunk struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data [][]byte `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` +} + +func (x *MultiHunk) Reset() { + *x = MultiHunk{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MultiHunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MultiHunk) ProtoMessage() {} + +func (x *MultiHunk) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_grpc_encoding_stream_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 MultiHunk.ProtoReflect.Descriptor instead. +func (*MultiHunk) Descriptor() ([]byte, []int) { + return file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP(), []int{1} +} + +func (x *MultiHunk) GetData() [][]byte { + if x != nil { + return x.Data + } + return nil +} + +var File_transport_internet_grpc_encoding_stream_proto protoreflect.FileDescriptor + +var file_transport_internet_grpc_encoding_stream_proto_rawDesc = []byte{ + 0x0a, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, + 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x25, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, + 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x1a, 0x0a, 0x04, 0x48, 0x75, 0x6e, 0x6b, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x22, 0x1f, 0x0a, 0x09, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x48, 0x75, 0x6e, 0x6b, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x32, 0xe6, 0x01, 0x0a, 0x0b, 0x47, 0x52, 0x50, 0x43, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x03, 0x54, 0x75, 0x6e, 0x12, 0x2b, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, + 0x6e, 0x67, 0x2e, 0x48, 0x75, 0x6e, 0x6b, 0x1a, 0x2b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, + 0x74, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2e, + 0x48, 0x75, 0x6e, 0x6b, 0x28, 0x01, 0x30, 0x01, 0x12, 0x72, 0x0a, 0x08, 0x54, 0x75, 0x6e, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x12, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x48, 0x75, 0x6e, 0x6b, 0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x48, 0x75, 0x6e, 0x6b, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3c, 0x5a, 0x3a, + 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, + 0x63, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_transport_internet_grpc_encoding_stream_proto_rawDescOnce sync.Once + file_transport_internet_grpc_encoding_stream_proto_rawDescData = file_transport_internet_grpc_encoding_stream_proto_rawDesc +) + +func file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP() []byte { + file_transport_internet_grpc_encoding_stream_proto_rawDescOnce.Do(func() { + file_transport_internet_grpc_encoding_stream_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_grpc_encoding_stream_proto_rawDescData) + }) + return file_transport_internet_grpc_encoding_stream_proto_rawDescData +} + +var file_transport_internet_grpc_encoding_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_transport_internet_grpc_encoding_stream_proto_goTypes = []interface{}{ + (*Hunk)(nil), // 0: xray.transport.internet.grpc.encoding.Hunk + (*MultiHunk)(nil), // 1: xray.transport.internet.grpc.encoding.MultiHunk +} +var file_transport_internet_grpc_encoding_stream_proto_depIdxs = []int32{ + 0, // 0: xray.transport.internet.grpc.encoding.GRPCService.Tun:input_type -> xray.transport.internet.grpc.encoding.Hunk + 1, // 1: xray.transport.internet.grpc.encoding.GRPCService.TunMulti:input_type -> xray.transport.internet.grpc.encoding.MultiHunk + 0, // 2: xray.transport.internet.grpc.encoding.GRPCService.Tun:output_type -> xray.transport.internet.grpc.encoding.Hunk + 1, // 3: xray.transport.internet.grpc.encoding.GRPCService.TunMulti:output_type -> xray.transport.internet.grpc.encoding.MultiHunk + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_grpc_encoding_stream_proto_init() } +func file_transport_internet_grpc_encoding_stream_proto_init() { + if File_transport_internet_grpc_encoding_stream_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_grpc_encoding_stream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Hunk); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_transport_internet_grpc_encoding_stream_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MultiHunk); 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_transport_internet_grpc_encoding_stream_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_transport_internet_grpc_encoding_stream_proto_goTypes, + DependencyIndexes: file_transport_internet_grpc_encoding_stream_proto_depIdxs, + MessageInfos: file_transport_internet_grpc_encoding_stream_proto_msgTypes, + }.Build() + File_transport_internet_grpc_encoding_stream_proto = out.File + file_transport_internet_grpc_encoding_stream_proto_rawDesc = nil + file_transport_internet_grpc_encoding_stream_proto_goTypes = nil + file_transport_internet_grpc_encoding_stream_proto_depIdxs = nil +} diff --git a/transport/internet/grpc/encoding/stream.proto b/transport/internet/grpc/encoding/stream.proto new file mode 100644 index 00000000..63898a77 --- /dev/null +++ b/transport/internet/grpc/encoding/stream.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package xray.transport.internet.grpc.encoding; +option go_package = "github.com/xtls/xray-core/transport/internet/grpc/encoding"; + +message Hunk { + bytes data = 1; +} + +message MultiHunk { + repeated bytes data = 1; +} + +service GRPCService { + rpc Tun (stream Hunk) returns (stream Hunk); + rpc TunMulti (stream MultiHunk) returns (stream MultiHunk); +} diff --git a/transport/internet/grpc/encoding/stream_grpc.pb.go b/transport/internet/grpc/encoding/stream_grpc.pb.go new file mode 100644 index 00000000..42e440a6 --- /dev/null +++ b/transport/internet/grpc/encoding/stream_grpc.pb.go @@ -0,0 +1,201 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package encoding + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GRPCServiceClient is the client API for GRPCService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GRPCServiceClient interface { + Tun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error) + TunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) +} + +type gRPCServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGRPCServiceClient(cc grpc.ClientConnInterface) GRPCServiceClient { + return &gRPCServiceClient{cc} +} + +func (c *gRPCServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error) { + stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[0], "/xray.transport.internet.grpc.encoding.GRPCService/Tun", opts...) + if err != nil { + return nil, err + } + x := &gRPCServiceTunClient{stream} + return x, nil +} + +type GRPCService_TunClient interface { + Send(*Hunk) error + Recv() (*Hunk, error) + grpc.ClientStream +} + +type gRPCServiceTunClient struct { + grpc.ClientStream +} + +func (x *gRPCServiceTunClient) Send(m *Hunk) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCServiceTunClient) Recv() (*Hunk, error) { + m := new(Hunk) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *gRPCServiceClient) TunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) { + stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[1], "/xray.transport.internet.grpc.encoding.GRPCService/TunMulti", opts...) + if err != nil { + return nil, err + } + x := &gRPCServiceTunMultiClient{stream} + return x, nil +} + +type GRPCService_TunMultiClient interface { + Send(*MultiHunk) error + Recv() (*MultiHunk, error) + grpc.ClientStream +} + +type gRPCServiceTunMultiClient struct { + grpc.ClientStream +} + +func (x *gRPCServiceTunMultiClient) Send(m *MultiHunk) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCServiceTunMultiClient) Recv() (*MultiHunk, error) { + m := new(MultiHunk) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCServiceServer is the server API for GRPCService service. +// All implementations must embed UnimplementedGRPCServiceServer +// for forward compatibility +type GRPCServiceServer interface { + Tun(GRPCService_TunServer) error + TunMulti(GRPCService_TunMultiServer) error + mustEmbedUnimplementedGRPCServiceServer() +} + +// UnimplementedGRPCServiceServer must be embedded to have forward compatible implementations. +type UnimplementedGRPCServiceServer struct { +} + +func (UnimplementedGRPCServiceServer) Tun(GRPCService_TunServer) error { + return status.Errorf(codes.Unimplemented, "method Tun not implemented") +} +func (UnimplementedGRPCServiceServer) TunMulti(GRPCService_TunMultiServer) error { + return status.Errorf(codes.Unimplemented, "method TunMulti not implemented") +} +func (UnimplementedGRPCServiceServer) mustEmbedUnimplementedGRPCServiceServer() {} + +// UnsafeGRPCServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GRPCServiceServer will +// result in compilation errors. +type UnsafeGRPCServiceServer interface { + mustEmbedUnimplementedGRPCServiceServer() +} + +func RegisterGRPCServiceServer(s grpc.ServiceRegistrar, srv GRPCServiceServer) { + s.RegisterService(&GRPCService_ServiceDesc, srv) +} + +func _GRPCService_Tun_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCServiceServer).Tun(&gRPCServiceTunServer{stream}) +} + +type GRPCService_TunServer interface { + Send(*Hunk) error + Recv() (*Hunk, error) + grpc.ServerStream +} + +type gRPCServiceTunServer struct { + grpc.ServerStream +} + +func (x *gRPCServiceTunServer) Send(m *Hunk) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCServiceTunServer) Recv() (*Hunk, error) { + m := new(Hunk) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _GRPCService_TunMulti_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCServiceServer).TunMulti(&gRPCServiceTunMultiServer{stream}) +} + +type GRPCService_TunMultiServer interface { + Send(*MultiHunk) error + Recv() (*MultiHunk, error) + grpc.ServerStream +} + +type gRPCServiceTunMultiServer struct { + grpc.ServerStream +} + +func (x *gRPCServiceTunMultiServer) Send(m *MultiHunk) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCServiceTunMultiServer) Recv() (*MultiHunk, error) { + m := new(MultiHunk) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCService_ServiceDesc is the grpc.ServiceDesc for GRPCService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GRPCService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "xray.transport.internet.grpc.encoding.GRPCService", + HandlerType: (*GRPCServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Tun", + Handler: _GRPCService_Tun_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "TunMulti", + Handler: _GRPCService_TunMulti_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "transport/internet/grpc/encoding/stream.proto", +} diff --git a/transport/internet/grpc/errors.generated.go b/transport/internet/grpc/errors.generated.go new file mode 100644 index 00000000..290cc82f --- /dev/null +++ b/transport/internet/grpc/errors.generated.go @@ -0,0 +1,9 @@ +package grpc + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/transport/internet/grpc/grpc.go b/transport/internet/grpc/grpc.go new file mode 100644 index 00000000..6caf155b --- /dev/null +++ b/transport/internet/grpc/grpc.go @@ -0,0 +1,3 @@ +package grpc + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/transport/internet/grpc/hub.go b/transport/internet/grpc/hub.go new file mode 100644 index 00000000..0f7ab6ba --- /dev/null +++ b/transport/internet/grpc/hub.go @@ -0,0 +1,129 @@ +package grpc + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/grpc/encoding" + "github.com/xtls/xray-core/transport/internet/tls" +) + +type Listener struct { + encoding.UnimplementedGRPCServiceServer + ctx context.Context + handler internet.ConnHandler + local net.Addr + config *Config + locker *internet.FileLocker // for unix domain socket + + s *grpc.Server +} + +func (l Listener) Tun(server encoding.GRPCService_TunServer) error { + tunCtx, cancel := context.WithCancel(l.ctx) + l.handler(encoding.NewHunkConn(server, cancel)) + <-tunCtx.Done() + return nil +} + +func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error { + tunCtx, cancel := context.WithCancel(l.ctx) + l.handler(encoding.NewMultiHunkConn(server, cancel)) + <-tunCtx.Done() + return nil +} + +func (l Listener) Close() error { + l.s.Stop() + return nil +} + +func (l Listener) Addr() net.Addr { + return l.local +} + +func Listen(ctx context.Context, address net.Address, port net.Port, settings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { + grpcSettings := settings.ProtocolSettings.(*Config) + var listener *Listener + if port == net.Port(0) { // unix + listener = &Listener{ + handler: handler, + local: &net.UnixAddr{ + Name: address.Domain(), + Net: "unix", + }, + config: grpcSettings, + } + } else { // tcp + listener = &Listener{ + handler: handler, + local: &net.TCPAddr{ + IP: address.IP(), + Port: int(port), + }, + config: grpcSettings, + } + } + + listener.ctx = ctx + + config := tls.ConfigFromStreamSettings(settings) + + var s *grpc.Server + if config == nil { + s = grpc.NewServer() + } else { + s = grpc.NewServer(grpc.Creds(credentials.NewTLS(config.GetTLSConfig(tls.WithNextProto("h2"))))) + } + listener.s = s + + if settings.SocketSettings != nil && settings.SocketSettings.AcceptProxyProtocol { + newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx)) + } + + go func() { + var streamListener net.Listener + var err error + if port == net.Port(0) { // unix + streamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{ + Name: address.Domain(), + Net: "unix", + }, settings.SocketSettings) + if err != nil { + newError("failed to listen on ", address).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx)) + return + } + locker := ctx.Value(address.Domain()) + if locker != nil { + listener.locker = locker.(*internet.FileLocker) + } + } else { // tcp + streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{ + IP: address.IP(), + Port: int(port), + }, settings.SocketSettings) + if err != nil { + newError("failed to listen on ", address, ":", port).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx)) + return + } + } + + encoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.ServiceName) + + if err = s.Serve(streamListener); err != nil { + newError("Listener for gRPC ended").Base(err).WriteToLog() + } + }() + + return listener, nil +} + +func init() { + common.Must(internet.RegisterTransportListener(protocolName, Listen)) +} diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go index e7d6242a..dc2cd8ab 100644 --- a/transport/internet/http/dialer.go +++ b/transport/internet/http/dialer.go @@ -18,8 +18,13 @@ import ( "golang.org/x/net/http2" ) +type dialerConf struct { + net.Destination + *internet.SocketConfig +} + var ( - globalDialerMap map[net.Destination]*http.Client + globalDialerMap map[dialerConf]*http.Client globalDialerAccess sync.Mutex ) @@ -28,10 +33,10 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C defer globalDialerAccess.Unlock() if globalDialerMap == nil { - globalDialerMap = make(map[net.Destination]*http.Client) + globalDialerMap = make(map[dialerConf]*http.Client) } - if client, found := globalDialerMap[dest]; found { + if client, found := globalDialerMap[dialerConf{dest, sockopt}]; found { return client, nil } @@ -87,7 +92,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C Transport: transport, } - globalDialerMap[dest] = client + globalDialerMap[dialerConf{dest, sockopt}] = client return client, nil }